What does Array.prototype.at(-1) return, and how does it differ from writing arr[-1]?
answer
- counting positions from the end
- bracket keys are strings
- a negative key is just a property name
- no wrap-around, only undefined
- an ES2022 method, also on strings
basics
~20 sArray.prototype.at(-1) returns the last element, because a negative argument counts back from the end. Bracket notation does not: arr[-1] is an ordinary property lookup for the key "-1", which normally yields undefined, and assigning to it adds a property without changing length.
solid answer
~40 s`at` is an ES2022 method that takes an integer index and returns the element there, but a negative index counts from the end: `arr.at(-1)` is the last element, `arr.at(-2)` the second-to-last, and an out-of-range index gives `undefined`. Bracket access works completely differently — property keys are strings, so `arr[-1]` looks up a property literally named `"-1"`, which no normal array has, so you get `undefined`. Worse, `arr[-1] = 'x'` succeeds: it attaches a regular property to the array object, leaves `length` untouched, and is skipped by every index-based array method. Before `at` the idiom was `arr[arr.length - 1]`, which works but has to name the array twice — awkward when the array is the result of an expression. `at` is also available on strings and typed arrays.
code
javascript · 11 linesconst arr = ['a', 'b', 'c'];
console.log(arr.at(-1)); // 'c'
console.log(arr[-1]); // undefined
console.log(arr.at(0), arr.at(3)); // 'a' undefined
// Assigning to a negative index makes a plain property, not an element
arr[-1] = 'z';
console.log(arr.length, arr[-1], arr.at(-1)); // 3 'z' 'c'
console.log(arr.includes('z')); // false
console.log('hello'.at(-1)); // 'o'go deeper
Know that at(-1) gives the last element while arr[-1] gives undefined, and be able to state the older equivalent, arr[arr.length - 1].
Explain why brackets behave that way: property keys are strings, and "-1" is not a valid array index, so the lookup misses and an assignment creates a plain property that leaves length alone.
Point out the practical consequences — index-based methods never see a value stored under a non-index key, at has no assignment form, and the method is an ES2022 addition worth checking against your runtime baseline.
Treat it as a baseline decision: agree which ECMAScript edition the codebase targets, so newer built-ins are adopted deliberately across services rather than appearing ad hoc and forcing a polyfill discussion late.
## Why arr[-1] does not work In JavaScript, arrays are objects, and object property keys are strings (or symbols). When you write `arr[-1]`, the number `-1` is converted to the string `"-1"` and used as a property name. An array's elements live under the keys `"0"`, `"1"`, `"2"` and so on, so `"-1"` matches nothing and the lookup produces `undefined`. This is why negative indexing — familiar from other languages — was simply absent from the language for most of its life. The traditional workaround is arithmetic: ```js const last = arr[arr.length - 1]; ``` That is correct, but it forces you to name the array twice. When the array is produced by an expression you must first store it in a variable, which is exactly the friction `at` removes. ## What at actually does `Array.prototype.at(index)`, added in ES2022, converts its argument to an integer, and if that integer is negative adds it to `length`. If the resulting position falls inside `0 .. length - 1` it returns the element there; otherwise it returns `undefined`. ```js const arr = ['a', 'b', 'c']; arr.at(0); // 'a' arr.at(-1); // 'c' arr.at(-3); // 'a' arr.at(-4); // undefined — out of range, no wrap-around arr.at(3); // undefined ``` A few details worth knowing. The argument is truncated toward zero, so `at(1.7)` reads index 1. `at()` with no argument treats the index as 0. There is no wrap-around: a negative index beyond `-length` is out of range, not a second lap. And `at` reads the index directly, so a hole in a sparse array comes back as `undefined` just like an out-of-range index. The method is not array-specific: `String.prototype.at` and the typed-array `at` exist too, so `'hello'.at(-1)` is `'o'`. `String.prototype.at` returns a one-character string or `undefined`, which is a small improvement over `charAt`, whose out-of-range result is the empty string. ## The assignment trap The read side of `arr[-1]` is merely useless. The write side is a genuine bug factory: ```js const arr = ['a', 'b', 'c']; arr[-1] = 'z'; arr.length; // 3 — unchanged arr[-1]; // 'z' — the property exists arr.at(-1); // 'c' — at ignores it entirely arr.includes('z'); // false — index-based methods never see it ``` Because `"-1"` is not an array index, `length` is not updated, and every index-based method — `includes`, `indexOf`, `some`, `every`, `find`, `map` — walks `0 .. length - 1` and never sees the value. The array now carries a hidden property that only a direct bracket read or a key enumeration will reveal. The same happens for any non-index key: `arr['1.5']` or `arr['last']` store data on the array object without participating in the array at all. It is worth being precise about *why*: an array index must be a canonical string form of an integer in `0 .. 2^32 - 2`. `"-1"` fails that test, so the array's special `length`-maintaining behaviour is not triggered and the key is stored like any other object property. ## at is a read, not a write `at` has no assignment counterpart. There is no way to write "set the last element" with a negative index; you still need `arr[arr.length - 1] = value`. Keep that asymmetry in mind before a refactor that mechanically swaps bracket access for `at`. ## Where at earns its keep The clearest win is on expression results, where the `length - 1` form needs a temporary: ```js // before: needs a variable to avoid computing the array twice const parts = path.split('/'); const file = parts[parts.length - 1]; // with at: no temporary needed const file2 = path.split('/').at(-1); ``` Because `at` is ES2022, name that baseline if you are targeting older runtimes — it is one of the ES2022 additions most likely to come up as a support question.
- What happens to `length` when you assign to `arr[-1]`?Nothing — it stays the same. An array index has to be the canonical string form of an integer from 0 up to 2^32 - 2, and `"-1"` is not one, so the assignment creates an ordinary object property instead of an element. Every index-based method walks 0 to `length - 1`, so the value is invisible to `includes`, `find`, `map` and the rest.
- What does `arr.at(-10)` return on a three-element array?`undefined`. A negative index is added to `length`, and if the result is still below 0 the position is simply out of range — there is no wrap-around to the end of the array. The same applies on the positive side: any index at or above `length` returns `undefined` rather than throwing.
- Can you use `at` to assign to the last element?No. `at` is a read-only accessor method with no assignment form, so writing the last element still requires `arr[arr.length - 1] = value`. That asymmetry matters during a refactor: a mechanical swap of bracket access for `at` breaks anywhere the bracket expression was on the left-hand side of an assignment.
- Is `at` only on arrays?No — `String.prototype.at` and the typed-array version exist too, all with the same negative-index rule, so `'hello'.at(-1)` gives `'o'`. On strings it returns a one-character string or `undefined` when out of range, which is tidier than `charAt`, whose out-of-range result is an empty string that quietly passes along as a value.
saying these in an interview costs you the question
- Says arr[-1] returns the last element
- Thinks at(-4) wraps around on a three-element array
- Believes arr[-1] = x updates length
- Expects at to work as an assignment target
- Assumes at has always been in the language