Array Basics
Mental model: An array is an object with numeric keys and a length that always sits one past the highest index you have written.
Level: beginner · about 10 minutes
const literal = [1, 2, 3];
const empty = [];
const sized = new Array(3); // 3 holes, not 3 zeros
const filled = new Array(3).fill(0); // [0, 0, 0]
console.log(literal, empty, sized, filled);
console.log(sized.length, filled.length); // 3 3Four ways to make an array. Only one of them is a trap.
An array is an ordered list of values, indexed from 0. Under the hood it is an object with a special length property and a prototype full of useful methods. The engine optimises it heavily, but the object nature leaks through in a few places, and those places are this lesson.
length is writable, and that has consequences
const letters = ['a', 'b', 'c', 'd'];
letters.length = 2;
console.log(letters); // ['a', 'b'], the rest are gone
letters[5] = 'f';
console.log(letters.length); // 6, length jumped to fit the new index
console.log(letters); // ['a', 'b', <3 empty items>, 'f']
const sparse = [1, , 3]; // one hole at index 1
console.log(1 in sparse); // false, no key there
console.log(sparse[1]); // undefined, reading a missing key
console.log(sparse.map((n) => 9)); // [9, <1 empty item>, 9] (map skipped the hole)
console.log([...sparse]); // [1, undefined, 3] (spread fills the hole in)The same sparse array, seen three ways.
Reading: brackets forward, at() backward
const queue = ['ada', 'grace', 'alan'];
console.log(queue[0]); // 'ada'
console.log(queue[queue.length - 1]); // 'alan', the old way
console.log(queue.at(-1)); // 'alan', the readable way
console.log(queue.at(-2)); // 'grace'
console.log(queue[-1]); // undefined, a plain property, not an index
Nested arrays are just arrays holding arrays
const grid = [
[1, 2, 3],
[4, 5, 6],
];
console.log(grid[1][2]); // 6, row first, then column
console.log(grid.length); // 2 rows
console.log(grid[0].length); // 3 columns
const [firstRow] = grid; // destructuring works at every level
console.log(firstRow); // [1, 2, 3]
Array.from, Array.of, and array-likes
console.log(Array.of(3)); // [3], exactly what you passed
console.log(new Array(3)); // [<3 empty items>], the odd one out
console.log(Array.from('hey')); // ['h', 'e', 'y'], strings are iterable
console.log(Array.from({ length: 4 }, (_, i) => i * i)); // [0, 1, 4, 9]
console.log(Array.from(new Set([1, 1, 2]))); // [1, 2]
An array-like is any object with a length and numeric keys but no array methods: arguments, a NodeList from querySelectorAll, a jQuery-ish wrapper. Array.from converts them. Array.isArray tells you which kind you are holding.
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
console.log(Array.isArray(arrayLike)); // false
console.log(Array.from(arrayLike)); // ['a', 'b']
// console.log([...arrayLike]); // TypeError: not iterable
console.log(typeof [1, 2]); // 'object', typeof cannot help you here
`Array.isArray(x)`- the only reliable array check
`Array.from(x)`- works on iterables AND array-likes
`[...x]`- iterables only, so it rejects
{ length: 2 } `Array.of(3)`[3], unlikenew Array(3)`arr.at(-1)`- last element, no length arithmetic
const a = [1, 2, 3];
a[7] = 8;
console.log(a.length, a[5]);Writing index 7 stretches length to 8, because length is always highest index plus one. Indexes 3 to 6 are holes, so a[5] reads as undefined. Nothing was zero-filled: JavaScript never invents values for you.
Try it yourself
Poke at the shape
const scores = [10, 20, 30];
scores[6] = 70;
console.log(scores.length, scores);
console.log('holes are skipped by forEach:');
scores.forEach((n, i) => console.log(i, n));
const grid = Array.from({ length: 3 }, () => Array.from({ length: 3 }, () => 0));
grid[0][0] = 1;
console.log(grid);
Set length to 0 and check what happens to the contents. Then build a 3x3 grid with Array.from and set the diagonal to 1.
Exercises
Build a grid without shared rows
Write makeGrid(rows, cols) that returns an array of rows arrays, each holding cols zeros. Every row must be an independent array, so changing one cell never changes another row.
Check yourself
- What does this log?
- [1] — Assigning a smaller
lengthtruncates the array in place and discards the removed elements.lengthis a real writable property, not a read-only count, which is why this works at all. - What does
console.log(Array(3).length, Array.of(3).length)print? - 3 1 —
Array(3)treats a single number as a length and gives you three holes.Array.of(3)always treats its arguments as elements, so you get[3]with length 1.Array.ofexists precisely to remove that ambiguity. - Which check reliably tells you a value is an array?
Array.isArray(value)—typeofreturns'object'for arrays, and alengthproperty is also present on strings, functions and array-likes.Array.isArrayis the only check that answers the actual question, and it works across frames.
Common mistakes
new Array(3)gives three holes, not three zeros. Add.fill(0)if you want values.- Assigning past the end (
arr[99] = 1) creates a sparse array whose holes some methods skip. new Array(2).fill([])shares one inner array across every slot.
Takeaways
lengthis writable: shrinking it deletes elements, and writing a high index stretches it.- Holes are missing keys, not
undefinedvalues, and iteration methods disagree about them. at(-1)reads from the end;arr[-1]just reads a property named"-1".Array.fromhandles both iterables and array-likes; spread only handles iterables.