Part of our complete guide: JavaScript Array Methods →
Array Method · ES2022
BaselineArray.prototype.at()
Returns the element at a given index. Accepts negative indices to count from the end — the one thing bracket notation can't do.
at() returns the element at a given index. Unlike bracket notation, it accepts negative indices — arr.at(-1) returns the last element. Use it when you need the nth-from-end item without .length - n.
Full definition: Array.prototype.at() returns the element at the specified index. Negative indices count back from the end of the array. Out-of-range indices return undefined instead of throwing. Added in ES2022.
Why at() exists
Before ES2022, getting the last element of an array required one of these workarounds:
// Workaround 1 — verbose
const last = arr[arr.length - 1];
// Workaround 2 — creates a copy
const last = arr.slice(-1)[0];
// Workaround 3 — mutates the array!
const last = arr.pop();
// What at() gives you
const last = arr.at(-1);
The first is verbose. The second allocates an intermediate array. The third destroys the original array. at() solves all three — it's concise, allocation-free, and non-destructive.
The proposal was authored by Shu-yu Guo and Tab Atkins Jr. and shipped in the ES2022 spec. It was designed specifically to mirror how Python, Ruby, and PHP handle negative indexing, so developers coming from those languages would have a familiar tool.
Syntax
array.at(index)
| Parameter | Type | Required | Description |
|---|---|---|---|
index |
number | Yes | The index of the element to return. Negative values count from the end. Non-integers are truncated (3.7 → 3). |
Return value
Returns the element at the specified index, or undefined if the index is out of range. Never throws.
| Index | Returns | Never throws |
|---|---|---|
| Valid positive (0 to length-1) | Element | ✓ |
| Valid negative (-1 to -length) | Element | ✓ |
| Out of range (positive) | undefined | ✓ |
| Out of range (negative) | undefined | ✓ |
| Non-numeric string | undefined | ✓ |
NaN | undefined | ✓ |
10 examples
From basic usage to real-world patterns. Every example is runnable.
const fruits = ['apple', 'banana', 'cherry'];
fruits.at(0); // 'apple'
fruits.at(1); // 'banana'
fruits.at(2); // 'cherry'
const fruits = ['apple', 'banana', 'cherry'];
fruits.at(-1); // 'cherry' (last)
fruits.at(-2); // 'banana' (second-to-last)
fruits.at(-3); // 'apple' (first)
const arr = [10, 20, 30];
arr[-1]; // undefined (bracket doesn't support negative)
arr.at(-1); // 30
arr[arr.length - 1]; // 30 (the old workaround)
arr.at(-1); // 30 (cleaner)
const arr = [1, 2, 3];
arr.at(100); // undefined
arr.at(-100); // undefined
arr.at(3); // undefined (3 is out of range for length 3)
// No error. Check for undefined before using:
if (arr.at(100) === undefined) {
console.log('out of range');
}
const str = 'hello';
str.at(0); // 'h'
str.at(-1); // 'o' (last character)
// Handles Unicode correctly
const emoji = '👋🌍';
emoji.at(0); // '👋' (full emoji, not broken surrogate)
emoji.at(-1); // '🌍'
const bytes = new Uint8Array([10, 20, 30]);
bytes.at(0); // 10
bytes.at(-1); // 30
// Same behavior as on regular arrays
const users = ['alice', 'bob', 'carol'];
// Last user, uppercased
users.at(-1).toUpperCase();
// 'CAROL'
// Second-to-last, sliced
users.at(-2).slice(0, 2);
// 'bo'
const items = ['a', 'b', 'c'];
for (let i = -1; i >= -items.length; i--) {
console.log(items.at(i));
}
// c, b, a
// This is what you'd do without at():
for (let i = items.length - 1; i >= 0; i--) {
console.log(items[i]);
}
const arr = [1, 2, 3, 4];
arr.at(1.9); // 2 (truncates to 1)
arr.at(0.5); // 1 (truncates to 0)
arr.at(-1.9); // 4 (truncates to -1)
// Before at()
function getLastItem(items) {
if (!items || items.length === 0) return null;
return items[items.length - 1];
}
// After at() — still check for undefined, but cleaner
function getLastItem(items) {
return items?.at(-1) ?? null;
}
getLastItem([1, 2, 3]); // 3
getLastItem([]); // null
getLastItem(null); // null
getLastItem(undefined); // null
🔬 Edge cases
The cases most tutorials skip. These are what cause production bugs.
[].at(0); // undefined
[].at(-1); // undefined
// Never throws
const arr = [1, 2, 3];
arr.at(-4); // undefined (beyond start)
arr.at(-5); // undefined
// The -1 index maps to length - 1 = 2
// The -3 index maps to length - 3 = 0
// Anything beyond -3 is undefined
const sparse = [1, , 3]; // note the hole at index 1
sparse.at(0); // 1
sparse.at(1); // undefined (hole)
sparse.at(2); // 3
// Same behavior as bracket notation
sparse[1]; // undefined
'hello'.at(0); // 'h' (string)
['h','e'].at(0); // 'h' (array)
// Both work. at() is on both String.prototype
// and Array.prototype.
function f() {
arguments.at(0); // ❌ TypeError: arguments.at is not a function
}
// Convert first
Array.from(arguments).at(0); // ✅
// NodeList similarly
Array.from(document.querySelectorAll('p')).at(0);
null.at(0); // ❌ TypeError
undefined.at(0); // ❌ TypeError
// Use optional chaining
someArray?.at(0); // ✅ returns undefined if null/undefined
const arr = [undefined, 1, 2];
arr.at(0); // undefined
arr[0]; // undefined
// Both return undefined, but the element EXISTS
// Distinguish with: 0 in arr → true
const arr = [10, 20, 30];
arr.at(0); // 10
arr.at(-0); // 10 (-0 === 0 in JavaScript)
// Both access the first element
⚡ Performance
| Operation | Time complexity | Space | Notes |
|---|---|---|---|
at(n) | O(1) | O(1) | Same as bracket |
at(-n) | O(1) | O(1) | Subtracts from length |
arr[arr.length - 1] | O(1) | O(1) | Equivalent speed |
arr.slice(-1)[0] | O(n) | O(n) | Allocates a copy |
arr.pop() | O(1) | O(1) | Mutates the array |
In practice: at() vs bracket performance is indistinguishable. Modern JS engines inline at() calls. Choose at() for clarity (especially with negative indices), not for speed.
🎯 at() vs bracket vs slice() vs pop()
| Method | Returns | Negative index | Mutates | Use when |
|---|---|---|---|---|
at() |
Element or undefined |
✓ | No | You need a specific index, possibly from the end |
arr[n] |
Element or undefined |
✗ | No | You need a known positive index |
slice() |
New array | ✓ | No | You need a range, not one item |
pop() |
Last element | N/A | Yes | You want to remove and return the last item |
find() |
Element or undefined |
N/A | No | You need to search by value, not index |
🌐 Browser support
Baseline since March 2022. Fully available in all modern browsers.
| Environment | Version | Support |
|---|---|---|
| Chrome | 92+ | ✓ |
| Firefox | 90+ | ✓ |
| Safari | 15.4+ | ✓ |
| Edge | 92+ | ✓ |
| Node.js | 16.6+ | ✓ |
| Deno | All | ✓ |
| IE 11 | — | ✗ No polyfill |
Need IE11 support? Skip at() and use arr[arr.length - 1]. A polyfill would add weight for a dead browser.
⚠️ Common mistakes
- Using arr[-1] instead of arr.at(-1). Bracket notation doesn't support negative indices — you get
undefined, not the last element. This is the #1 mistake. - Forgetting to check for undefined.
at()returnsundefinedon out-of-range, not an error. Always check before using the result. - Using at() on arguments or NodeList. These are array-like objects, not arrays. They don't have
at(). Convert withArray.from()first. - Thinking at() is faster than bracket. Both are O(1). Choose
at()for readability, not performance. - Assuming it works on null/undefined.
null.at(0)throws. Use optional chaining:arr?.at(0). - Confusing at() with find().
at()gets an index;find()searches by value. - Using at() on large arrays in tight loops for positive indices. The overhead is small but real — for hot loops, bracket notation is marginally faster.
- Expecting error on bad index.
at()never throws. Out-of-range is alwaysundefined.
❓ Frequently asked questions
What does Array.at() do in JavaScript?
Array.at(index) returns the element at the given index. Unlike bracket notation, it accepts negative indices where -1 returns the last element, -2 the second-to-last, and so on. Returns undefined if the index is out of range.
What is the difference between at() and bracket notation?
at() accepts negative indices to count from the end; bracket notation does not. arr[-1] returns undefined, while arr.at(-1) returns the last element. For non-negative indices, they behave identically.
When was Array.at() added to JavaScript?
Array.prototype.at() was added in ES2022. It is available in all modern browsers since early 2022, including Chrome 92+, Firefox 90+, Safari 15.4+, and Node.js 16.6+.
Does at() work on strings?
Yes. String.prototype.at() was added at the same time. 'hello'.at(-1) returns 'o'. It handles Unicode code points correctly, unlike bracket notation which can split surrogate pairs.
Is at() faster than bracket notation?
No. Both are O(1) and perform identically for non-negative indices. at() has a small overhead of a function call plus a range check, but this is negligible in practice. Use whichever is clearer.
What does at() return for out-of-range indices?
undefined. It never throws. arr.at(100) on a 5-element array returns undefined. arr.at(-100) also returns undefined.
Can I use at() on array-like objects?
Not directly. at() must be called on an actual Array or TypedArray. For array-like objects (like arguments or NodeList), convert first with Array.from() or the spread operator.
What is the difference between at() and slice()?
at() returns a single element (or undefined). slice() returns a new array containing multiple elements. Use at() for one item, slice() for a range.