JavaScript Arrays
An array is an ordered, indexed list of values. It can hold anything — numbers, strings, objects, even other arrays — and grows or shrinks on demand. This chapter covers creation, indexing, mutation, destructuring, and the spread operator. The next chapter is dedicated to the iteration methods (map, filter, reduce, …).
Creating an Array
const empty = []; // empty
const nums = [1, 2, 3]; // literal — preferred
const mixed = [1, "two", true, null]; // any types allowed
const grid = [[1, 2], [3, 4]]; // nested = 2D
// Constructor form — rarely needed
const a = new Array(3); // [empty × 3] ← length 3, no values
const b = new Array(1, 2, 3); // [1, 2, 3]
// From an iterable / array-like
Array.from("abc"); // ["a", "b", "c"]
Array.from({ length: 3 }, (_, i) => i * 2); // [0, 2, 4]
// Helpers
Array.of(7); // [7] ← unambiguous
Array(7).fill(0); // [0, 0, 0, 0, 0, 0, 0]
Warning: new Array(3) creates an array with length 3 and no elements (called “sparse”). [3] creates a single-element array. The two are very different — always use literals or Array.of(n).
Accessing Elements
const arr = ["a", "b", "c", "d"];
arr[0] // "a"
arr[3] // "d"
arr[99] // undefined ← no error for out-of-range
arr.length // 4
arr.at(-1) // "d" ← negative index (ES2022)
arr.at(-2) // "c"
arr[-1] // undefined ← bracket doesn't support negatives
Modifying In Place
Arrays are mutable. The following methods change the array and return useful info:
const arr = [1, 2, 3];
arr[0] = 99; // [99, 2, 3]
arr[5] = 5; // [99, 2, 3, <2 empty>, 5] ← creates holes
arr.length = 2; // [99, 2] ← truncates
arr.length = 5; // [99, 2, <3 empty>] ← extends with holes
Tip: Avoid setting indexes far past the current length. Sparse arrays behave subtly differently from dense ones and break many methods.
Add and Remove — Push, Pop, Shift, Unshift
The four mutating workhorses. Remember them as a 2×2:
ADD REMOVE
END push(x) pop() ← returns the value
BEGIN unshift(x) shift() ← returns the value
const arr = ["b", "c"];
arr.push("d"); // returns 3 (new length) → ["b", "c", "d"]
arr.unshift("a"); // returns 4 → ["a", "b", "c", "d"]
arr.pop(); // returns "d" → ["a", "b", "c"]
arr.shift(); // returns "a" → ["b", "c"]
arr.push("x", "y", "z"); // adds multiple
arr.unshift("p", "q"); // adds multiple at the start
| Method | Position | Returns | Mutates |
|---|---|---|---|
push(...items) |
end | new length | ✅ |
pop() |
end | removed value | ✅ |
unshift(...items) |
start | new length | ✅ |
shift() |
start | removed value | ✅ |
Performance note: push and pop are O(1). shift and unshift are O(n) — every other element has to move. Prefer pushing to the end when possible.
Splice — The Swiss Army Knife
splice(start, deleteCount, ...itemsToInsert) removes and/or inserts at any position. Mutates the array and returns the removed items.
const arr = ["a", "b", "c", "d"];
// Remove
arr.splice(1, 2); // returns ["b", "c"] → arr is ["a", "d"]
// Insert
arr.splice(1, 0, "x", "y"); // returns [] → ["a", "x", "y", "d"]
// Replace
arr.splice(0, 1, "Z"); // returns ["a"] → ["Z", "x", "y", "d"]
// Remove from index to end
arr.splice(2); // returns ["y", "d"] → ["Z", "x"]
Slice — Non-Mutating Copy
slice(start?, end?) returns a new array containing a section. The original is untouched.
const arr = ["a", "b", "c", "d"];
arr.slice(1, 3) // ["b", "c"] ← end is exclusive
arr.slice(2) // ["c", "d"]
arr.slice(-2) // ["c", "d"] ← negative from end
arr.slice() // ["a", "b", "c", "d"] ← shallow copy of the whole array
console.log(arr); // ["a", "b", "c", "d"] ← unchanged
slice vs splice is a one-letter difference that catches every beginner:
slice |
splice |
|
|---|---|---|
| Mutates | ❌ No | ✅ Yes |
| Returns | New array | Removed items |
| Args | (start, end) |
(start, count, ...inserts) |
Concat and the Spread Operator
const a = [1, 2];
const b = [3, 4];
// Concat — returns new array
a.concat(b); // [1, 2, 3, 4]
a.concat(b, [5, 6]); // [1, 2, 3, 4, 5, 6]
a.concat(99); // [1, 2, 99] ← flattens one level
// Spread — modern, more flexible
[...a, ...b] // [1, 2, 3, 4]
[...a, 99, ...b, 100] // [1, 2, 99, 3, 4, 100]
[...a] // [1, 2] ← shallow copy
Spread also clones inside function arguments and object literals (chapter 34).
Searching
const arr = [10, 20, 30, 20, 40];
arr.indexOf(20) // 1
arr.indexOf(20, 2) // 3 ← start from index 2
arr.indexOf(99) // -1
arr.lastIndexOf(20) // 3
arr.includes(30) // true
arr.includes(99) // false
// find / findIndex — use a predicate
arr.find(x => x > 25) // 30
arr.findIndex(x => x > 25) // 2
arr.findLast(x => x > 15) // 40
arr.findLastIndex(x => x > 15) // 4
| Method | Returns | Predicate |
|---|---|---|
indexOf / lastIndexOf |
index or -1 | exact value |
includes |
boolean | exact value |
find / findLast |
value or undefined | function |
findIndex / findLastIndex |
index or -1 | function |
Reversing and Sorting
reverse and sort mutate the array (also return it). Use the non-mutating cousins toReversed / toSorted (ES2023) when you want a copy.
const arr = [3, 1, 2];
arr.reverse(); // mutates → [2, 1, 3]
arr.sort(); // mutates → [1, 2, 3] ← string-ish comparison
arr.toReversed(); // returns NEW [3, 2, 1] (ES2023)
arr.toSorted(); // returns NEW [1, 2, 3] (ES2023)
Default sort is lexicographic — surprising for numbers:
[10, 2, 1, 20].sort() // [1, 10, 2, 20] ← string compare!
[10, 2, 1, 20].sort((a, b) => a - b); // [1, 2, 10, 20]
[10, 2, 1, 20].sort((a, b) => b - a); // [20, 10, 2, 1]
Comparator returns:
< 0→abeforeb0→ keep order> 0→aafterb
Sort an array of objects:
users.sort((a, b) => a.age - b.age); // by number
users.sort((a, b) => a.name.localeCompare(b.name)); // by string
Flat and FlatMap
[1, [2, [3, [4]]]].flat() // [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].flat(2) // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]
["one two", "three"].flatMap(s => s.split(" "));
// ["one", "two", "three"]
flatMap(fn) is map(fn).flat(1) — useful for transforms that may produce 0, 1, or many outputs per input.
Array Destructuring
const [a, b, c] = [1, 2, 3];
// a=1, b=2, c=3
// Skip elements
const [, second] = [1, 2, 3];
// second = 2
// Defaults
const [x = 10, y = 20] = [1];
// x=1, y=20
// Rest
const [head, ...rest] = [1, 2, 3, 4];
// head=1, rest=[2, 3, 4]
// Swap
let i = 1, j = 2;
[i, j] = [j, i];
// i=2, j=1
// Nested
const [[a1, a2], b1] = [[1, 2], 3];
Full destructuring chapter is 33.
Iterating an Array
(Full chapter is 19 — these are the basics.)
const arr = ["a", "b", "c"];
// Classic for — when you need the index
for (let i = 0; i < arr.length; i++) {
console.log(i, arr[i]);
}
// for…of — values, no index
for (const v of arr) {
console.log(v);
}
// for…in — KEYS / indexes, includes inherited (rarely what you want for arrays)
for (const k in arr) {
console.log(k); // strings: "0", "1", "2"
}
// forEach — functional, no break
arr.forEach((v, i) => console.log(i, v));
// entries — get [index, value] pairs
for (const [i, v] of arr.entries()) {
console.log(i, v);
}
| Loop | Gives You | Can break |
Async-aware |
|---|---|---|---|
for |
index → manual access | ✅ | ✅ |
for…of |
value | ✅ | ✅ (await inside works) |
for…in |
index as string, plus inherited keys | ✅ | ✅ |
forEach |
value, index | ❌ | ❌ |
entries |
[index, value] |
✅ | ✅ |
Tip: For arrays, prefer for…of (values) or for…of arr.entries() (index + value). Never for…in on an array — it iterates string keys and includes inherited properties.
Multidimensional Arrays
JavaScript has no real 2D arrays — just arrays of arrays.
const grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
grid[1][2] // 6
grid[0].length // 3
// Create an N×M matrix filled with 0
const rows = 3, cols = 4;
const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
// [
// [0, 0, 0, 0],
// [0, 0, 0, 0],
// [0, 0, 0, 0],
// ]
Warning: Array(3).fill([]) creates three references to the same array, not three separate ones. Use the Array.from(...) pattern above when you need independent inner arrays.
A Realistic Example
// Tally how often each word appears, sorted by frequency desc
function topWords(text, limit = 5) {
const counts = {};
for (const word of text.toLowerCase().match(/\w+/g) ?? []) {
counts[word] = (counts[word] ?? 0) + 1;
}
return Object.entries(counts)
.sort(([, a], [, b]) => b - a) // sort by count desc
.slice(0, limit)
.map(([word, count]) => ({ word, count }));
}
topWords("the cat sat on the mat the mat the");
// [
// { word: "the", count: 4 },
// { word: "mat", count: 2 },
// { word: "cat", count: 1 },
// { word: "sat", count: 1 },
// { word: "on", count: 1 },
// ]
Destructuring, sort with a comparator, slice, map — five array operations stitched into one pipeline.
Best Practices Checklist
- ✅ Always use literals (
[1, 2, 3]) orArray.of/Array.from - ✅ Use
slice()to copy, spread[...arr]to copy/merge - ✅ Use
at(-1)for the last element - ✅ Pass a comparator to
sort((a, b) => a - bfor numbers) - ✅ Use
Array.isArray(x)to test if a value is an array - ❌ Never use
for…inon arrays - ❌ Avoid sparse arrays — they break many methods quietly
- ❌ Don’t mutate arrays you didn’t create (prefer non-mutating copies)
What’s Next?
You can build and edit arrays. Time to wield the iteration methods that make modern JS so powerful — map, filter, reduce, every, some, and friends.