Skip to content
JavaScript js arrays 4 min read

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. The Array constructor’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
ExpressionResultNotes
arr[0]First elementStandard positive index
arr[arr.length - 1]Last elementWorks everywhere, verbose
arr.at(-1)Last elementES2022, concise
arr[-1]undefinedReads 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 with push or 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 ([]); avoid new Array(n) unless you deliberately want a pre-sized, holey array.
  • Use at(-1) for the last element instead of arr[arr.length - 1] in modern environments.
  • Never index with negative numbers in brackets — that reads a property, not an element.
  • Treat length as a live value: setting it lower truncates the array immediately.
  • Avoid holes; build arrays with push or literals rather than assigning to gaps.
  • Remember arrays are reference types — copy with [...arr] or arr.slice() before mutating shared data.
  • Prefer const for array bindings; you can still mutate the contents, and it prevents accidental reassignment.
Last updated June 1, 2026
Was this helpful?