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.

[0]Apple
[1]Banana
[2]Cherry
[3]Date
[4]Elderberry
Old way: array[array.length - 1]
items[items.length - 1]
Result: Elderberry

Verbose. Have to calculate index. Ugly.

New way: array.at(-1)
items.at(-1)
Result: Elderberry

Clean. Readable. Works like Python.

Negative indices

at() supports negative indices. Count from the end of the array.

ExpressionResultExplanation
items.at(-1)ElderberryLast element
items.at(-2)DateSecond to last
items.at(-3)CherryThird to last
items.at(0)AppleFirst 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 items twice
  • Noisy - [items.length - 1] does not exactly read "last item"
  • Easy to get wrong - - 2 instead 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); // undefined

With 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 empty

Same 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 a for loop 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) beats items[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-only

Mutating 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 array

Or 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

Copy. Paste. Access. Your array indexing just got a little less noisy.