Array.at(-1) - Elegant Last Element Access
Get the last array element without calculating length. Negative indices that actually work.
Finally, something that feels like Python in JavaScript. No more
array[array.length - 1].
Live demo
Add or remove items to see how at(-1) always returns the last element.
items[items.length - 1]ElderberryVerbose. Have to calculate index. Ugly.
items.at(-1)ElderberryClean. Readable. Works like Python.
Negative indices
at() supports negative indices. Count from the end of the array.
| Expression | Result | Explanation |
|---|---|---|
items.at(-1) | Elderberry | Last element |
items.at(-2) | Date | Second to last |
items.at(-3) | Cherry | Third to last |
items.at(0) | Apple | First element (same as items[0]) |
Practical use cases
Get the last item
// Old way
const lastItem = items[items.length - 1];
// New way
const lastItem = items.at(-1);Get the last N items
// Old way
const lastTwo = items.slice(items.length - 2);
// New way
const lastTwo = items.slice(-2);
// Get second-to-last
const secondLast = items.at(-2);Safe access (returns undefined if out of bounds)
const empty = [];
// Old way - throws error or needs guard
const last = empty.length > 0 ? empty[empty.length - 1] : undefined;
// New way - returns undefined automatically
const last = empty.at(-1); // undefined💡 Browser support: Chrome 92+, Firefox 90+, Safari 15.4+. Node.js 16.6.0+. All modern environments since 2021.
What is Array.at()?
Array.prototype.at() returns the element at a given index.
The useful part: it supports negative indices.
const items = ["a", "b", "c", "d"];
items.at(-1); // "d" (last element)
items.at(-2); // "c" (second-to-last)
items.at(0); // "a" (first element, same as items[0])Negative indices count from the end of the array. -1 is last, -2 is second-to-last, and so on.
Why this matters
Getting the last element of an array has always been awkward in JavaScript:
// Old way
const last = items[items.length - 1];This works, but:
- Verbose - you repeat
itemstwice - Noisy -
[items.length - 1]does not exactly read "last item" - Easy to get wrong -
- 2instead of- 1, or a copy-paste of the wrong variable
With at():
// New way
const last = items.at(-1);Clean. Readable. Does what it says.
Basic usage
Get the last element
const fruits = ["apple", "banana", "cherry"];
fruits.at(-1); // "cherry"Same as:
fruits[fruits.length - 1]; // "cherry"Just shorter and clearer.
Get the second-to-last element
fruits.at(-2); // "banana"Same as:
fruits[fruits.length - 2]; // "banana"Positive indices still work
fruits.at(0); // "apple"
fruits.at(1); // "banana"Same as:
fruits[0]; // "apple"
fruits[1]; // "banana"at() is not only about negative indices - it just makes those nicer.
Real-world use cases
Get the last item in a list
Before:
const messages = getMessages();
const lastMessage = messages[messages.length - 1];After:
const messages = getMessages();
const lastMessage = messages.at(-1);Get the most recent entry
const history = getUserHistory();
const mostRecent = history.at(-1);Handy for undo/redo stacks, navigation history, or any ordered list where you care about the last thing that happened.
Compare first and last
const scores = [85, 90, 78, 92, 88];
const firstScore = scores.at(0);
const lastScore = scores.at(-1);
if (lastScore > firstScore) {
console.log("Improvement!");
}Symmetric and easy to read. No [0] versus [scores.length - 1] asymmetry.
Safe access to dynamic arrays
at() returns undefined if the index is out of bounds:
const empty = [];
empty.at(-1); // undefined (no error)
empty.at(10); // undefinedWith bracket notation you often end up writing guards like:
const last = items.length > 0 ? items[items.length - 1] : undefined;With at():
const last = items.at(-1); // undefined if emptySame result, less ceremony.
When NOT to use at()
at() is great, but not a replacement for every [].
Skip it for:
- Simple positive indices -
items[0]is perfectly fine - Looping -
items[i]inside aforloop is idiomatic and faster - Tight hot paths -
at()is a method call, bracket access is as direct as it gets
Use it for:
- Negative indices -
items.at(-1)beatsitems[items.length - 1]every time - Code where readability matters more than micro-optimisation
- APIs that accept "relative from end" indices
If you ever write array[array.length - X], it is at least worth asking if array.at(-X) would be clearer.
Comparison with other patterns
vs. slice(-1)
You can get the last element with slice():
const last = items.slice(-1)[0];This works, but:
- More verbose - extra
slice()call and[0] - Less efficient - creates a new array
- Less obvious - that trailing
[0]is easy to miss
at(-1) is simpler:
const last = items.at(-1);vs. pop()
pop() removes and returns the last element:
const last = items.pop();This mutates the array. If you only want to read the last element, use at(-1):
const last = items.at(-1); // read-onlyMutating and reading are very different operations; pop() is for stacks, at() is for access.
Works with strings too
String.prototype.at() behaves the same way:
const text = "hello";
text.at(-1); // "o"
text.at(-2); // "l"
text.at(0); // "h"Same relative indexing, just on characters.
Browser support
Array.prototype.at() is supported in:
- Chrome 92+
- Firefox 90+
- Safari 15.4+
- Edge 92+
- Node.js 16.6.0+
In other words: anything reasonably current.
Polyfill
If you need to support older browsers, you can polyfill:
if (!Array.prototype.at) {
Array.prototype.at = function (index) {
const i = index < 0 ? this.length + index : index;
return this[i];
};
}Or pull it from a polyfill library like core-js:
import "core-js/actual/array/at";Performance
Under the hood, at() is a tiny bit slower than direct bracket access because it is a method call that does a range check for you.
For everyday code, the difference is in the noise - network calls, DOM updates, and database queries will dominate long before at() does.
If you are writing a tight inner loop over millions of items in performance-critical code, use bracket notation. If you are writing normal application logic, prefer the version that is easier to read.
Common patterns
Get the last N elements
const last3 = items.slice(-3); // last 3 elements as a new arrayOr when you only care about a few specific ones:
const lastThree = [items.at(-3), items.at(-2), items.at(-1)];Remove the last element immutably
const withoutLast = items.slice(0, -1);Pairs nicely with items.at(-1) when you want both the last element and the rest:
const last = items.at(-1);
const rest = items.slice(0, -1);Quick sanity checks
if (items.at(-1) === undefined) {
console.log("Array is empty or the last element is undefined");
}Or, when you actually care about emptiness, be explicit:
if (items.length === 0) {
console.log("Array is empty");
}Try it yourself
Open your browser console and paste:
const items = ["a", "b", "c", "d", "e"];
console.log("Last:", items.at(-1));
console.log("Second-to-last:", items.at(-2));
console.log("First:", items.at(0));Then compare with the old way:
console.log("Last (old way):", items[items.length - 1]);Same result, fewer opportunities to get the math wrong.
Resources
- MDN: Array.prototype.at() - official documentation
- TC39 Proposal: Relative Indexing Method - the spec that introduced
at() - Can I Use: Array.prototype.at() - browser support table
Copy. Paste. Access. Your array indexing just got a little less noisy.