Array Basics
An array is JavaScript’s built-in type for an ordered, integer-indexed collection of values. Whenever you have a list of things — user names, scores, DOM nodes, rows from a database — an array is the natural container. Arrays are objects under the hood, but they come with a special length property and a rich set of methods that make working with sequences fast and expressive. This page covers how to create arrays, read and write elements by index, and the basic mutations every developer relies on.
Creating arrays
The clearest way to make an array is an array literal — a comma-separated list of values inside square brackets. This is what you should reach for almost every time.
const fruits = ["apple", "banana", "cherry"];
const empty = [];
const scores = [98, 72, 85];
There is also an Array constructor, but it has a sharp edge: new Array(3) creates an array of length 3 with no actual elements (called holes), rather than [3]. Prefer literals to avoid this confusion.
const a = new Array(3); // [ <3 empty items> ], length 3
const b = new Array(1, 2); // [1, 2]
Use
Array.of(3)when you want a single-element array[3]from a number, and the literal[]for everything else. TheArrayconstructor’s length-vs-elements ambiguity is a classic source of bugs.
Mixed types
JavaScript arrays are not typed. A single array can hold strings, numbers, booleans, objects, other arrays, null, and even functions all at once. The engine does not enforce homogeneity.
const mixed = [1, "two", true, null, { id: 9 }, [10, 20]];
console.log(typeof mixed[1], typeof mixed[4]);
Output:
string object
In practice you’ll usually keep one kind of value per array for sanity, but the flexibility is occasionally useful — for example, a tuple-like [status, payload] pair.
Indexing
Elements are accessed by a zero-based index in square brackets: the first item is at index 0, the last at length - 1. Reading an index that doesn’t exist returns undefined rather than throwing.
const colors = ["red", "green", "blue"];
console.log(colors[0]); // first
console.log(colors[2]); // last
console.log(colors[5]); // out of range
Output:
red
blue
undefined
Negative indexing with at()
Bracket notation does not accept negative indexes — colors[-1] reads a regular property named "-1" and returns undefined. The modern at() method (ES2022) fixes this by accepting negative offsets that count back from the end, which is the cleanest way to grab the last element.
const colors = ["red", "green", "blue"];
console.log(colors.at(-1)); // last element
console.log(colors.at(-2)); // second from last
console.log(colors[-1]); // not what you want
Output:
blue
green
undefined
| Expression | Result | Notes |
|---|---|---|
arr[0] | First element | Standard positive index |
arr[arr.length - 1] | Last element | Works everywhere, verbose |
arr.at(-1) | Last element | ES2022, concise |
arr[-1] | undefined | Reads a string key, not an index |
The length property
Every array exposes a length property holding the count of elements. It is not just a read-only counter — it is live and writable, which is the key to understanding how arrays grow and shrink.
const nums = [1, 2, 3, 4];
console.log(nums.length); // 4
nums.length = 2; // truncate
console.log(nums); // [1, 2]
nums.length = 4; // extend with holes
console.log(nums); // [1, 2, <2 empty items>]
Output:
4
[ 1, 2 ]
[ 1, 2, <2 empty items> ]
Assigning past the current end also stretches length automatically:
const a = ["x"];
a[3] = "y";
console.log(a.length, a); // 4 [ 'x', <2 empty items>, 'y' ]
Avoid creating holes by assigning to far-out indexes. Holey arrays behave inconsistently across methods (some skip holes, some treat them as
undefined) and are slower in many engines. Build arrays withpushor literals instead.
Basic mutation
Arrays are mutable: you can replace, append, and remove elements in place. The simplest write is assigning to an index. To add to the ends, use push (append) and unshift (prepend); to remove from the ends, use pop and shift.
const stack = ["a", "b"];
stack[0] = "A"; // replace in place
stack.push("c"); // append → length 3
const last = stack.pop(); // remove last → "c"
stack.unshift("z"); // prepend
const first = stack.shift(); // remove first → "z"
console.log(stack, last, first);
Output:
[ 'A', 'b' ] c z
Because arrays are objects, two variables can reference the same array, and mutating through one is visible through the other. When you need an independent copy, spread the array or use slice().
const original = [1, 2, 3];
const alias = original;
const copy = [...original]; // shallow copy
alias.push(4);
copy.push(99);
console.log(original); // [1, 2, 3, 4] ← aliased mutation leaks
console.log(copy); // [1, 2, 3, 99] ← independent
Output:
[ 1, 2, 3, 4 ]
[ 1, 2, 3, 99 ]
Best Practices
- Create arrays with literals (
[]); avoidnew Array(n)unless you deliberately want a pre-sized, holey array. - Use
at(-1)for the last element instead ofarr[arr.length - 1]in modern environments. - Never index with negative numbers in brackets — that reads a property, not an element.
- Treat
lengthas a live value: setting it lower truncates the array immediately. - Avoid holes; build arrays with
pushor literals rather than assigning to gaps. - Remember arrays are reference types — copy with
[...arr]orarr.slice()before mutating shared data. - Prefer
constfor array bindings; you can still mutate the contents, and it prevents accidental reassignment.