Arrays & Typed Arrays
Arrays are JavaScript's workhorse. This chapter sorts the methods into "changes the array" and "returns something new", covers the ES2023 copy-returning methods that make immutable code easy, and explains the default sort() trap. It ends with typed arrays for raw binary data.
You will learn
- Creating arrays: literals,
Array.from,Array.ofES2015, and thenew Array(n)trap. - Which methods mutate and which return a new array, including
toSorted,toReversed,toSplicedandwithES2023. map,filter,reduce,find/findLast,some/every,flat/flatMap.- Sorting correctly: the default string order, comparators and stability.
- Typed arrays and
ArrayBufferfor binary data.
Creating arrays
new Array(3)creates three holes (empty slots, notundefined), which many methods skip.Array.ofexists precisely to avoid that one-number confusion.Array.from(iterableOrArrayLike, mapFn)converts strings, Sets, Maps, NodeLists and{ length: n }objects into real arrays.
⚠️ Pitfall: new Array(3).fill([]) puts the same array in all three slots. Use Array.from({ length: 3 }, () => []) for separate ones.
Reading and length
at() ES2022 accepts negative indexes. Setting length truncates the array. includes ES2016 uses SameValueZero, so it finds NaN, while indexOf uses === and doesn't.
Mutating vs non-mutating methods
This is the most useful table in the chapter. In React, Redux, and any code that shares data, you want the right-hand column.
| Mutates the array | Returns a new array or value (original untouched) |
|---|---|
push, pop, shift, unshift | [...arr, x], arr.slice(0, -1), arr.slice(1), [x, ...arr] |
splice(i, n, ...items) | toSpliced(i, n, ...items) ES2023 |
sort(cmp) | toSorted(cmp) ES2023 |
reverse() | toReversed() ES2023 |
arr[i] = x | with(i, x) ES2023 |
fill, copyWithin | map, filter, slice, concat, flat, flatMap |
💡 Return values: push and unshift return the new length; pop and shift return the removed item; splice returns an array of the removed items; sort and reverse return the same (mutated) array.
Iteration methods
| Method | Returns | Stops early? |
|---|---|---|
forEach(fn) | undefined | no |
map(fn) | a new array of results, same length | no |
filter(fn) | a new array of items where fn is truthy | no |
reduce(fn, init) | a single accumulated value | no |
find / findIndex ES2015 | the first match / its index (undefined / -1) | yes |
findLast / findLastIndex ES2023 | the same, searching from the end | yes |
some / every | true if any / all items pass | yes |
flat(depth) / flatMap(fn) ES2019 | flattened arrays | no |
⚠️ Pitfall: reduce without an initial value. [].reduce((a, b) => a + b) throws a TypeError, and with objects the first item becomes the accumulator. Always pass the initial value.
⚠️ Pitfall: map(parseInt). ["1", "2", "3"].map(parseInt) gives [1, NaN, NaN], because map passes (item, index) and parseInt treats the index as a radix. Write .map((s) => parseInt(s, 10)) or .map(Number).
Sorting
Without a comparator, sort converts items to strings and compares UTF-16 code units. That's fine for plain ASCII words, and wrong for numbers.
A comparator returns a negative number if a comes first, a positive number if b does, and 0 for "equal". Since ES2019, sort is stable: equal items keep their order, so sorting by several keys can be done by chaining, or more clearly with ||:
Holes and array-likes
Holes come from new Array(n), [1, , 3], or writing past the end. forEach, map and filter skip them, while for…of, spread and Array.from treat them as undefined. Avoid creating holes.
Typed arrays
A typed array is a fixed-length array of raw numbers of one type, stored in an ArrayBuffer (a block of bytes). They're used for binary file formats, network protocols, images, audio, WebGL and WebAssembly. Several views can look at the same buffer.
| Type | Bytes per item | Range or notes |
|---|---|---|
Uint8Array / Int8Array | 1 | 0–255 / −128–127; overflow wraps around |
Uint8ClampedArray | 1 | clamps to 0–255 (canvas pixels) |
Int16/Uint16, Int32/Uint32 | 2, 4 | integers |
Float16Array ES2025, Float32Array, Float64Array | 2, 4, 8 | floats |
BigInt64Array, BigUint64Array | 8 | BigInt values |
DataView | any | mixed types at any offset, with explicit endianness |
words[0] is 16711935 (0x00FF00FF) because this machine is little-endian: the first byte is the least significant one. Typed arrays have most array methods (map, slice, toSorted, …), but a fixed length, so there's no push.
📝 Advanced: SharedArrayBuffer plus Atomics let several workers share memory safely. MDN covers this in "Agent communication and coordination". It's outside the scope of this book, but now you know where to look.
More examples
1. Order totals with reduce
2. Chunking an array into pages
3. Immutable cart updates
4. Zipping two arrays
5. Checksum of a byte array
Recap
- Use
Array.fromto convert iterables and build arrays, andArray.offor one number; avoid holes. push/pop/shift/unshift/splice/sort/reverse/fillmutate; prefertoSorted/toReversed/toSpliced/with, spread andmap/filterfor shared data.- Always give
reducean initial value.find/some/everystop early.flatMapmaps and flattens one level. - The default
sortcompares strings, so pass(a, b) => a - bfor numbers andlocaleComparefor text. Sorting is stable. - Typed arrays are fixed-size numeric views over an
ArrayBuffer; integer views wrap on overflow.
Exercises
Exercise 1 🟢 easy predict the output
What is [5, 25, 100, 1].sort()?
Show solution
[1, 100, 25, 5]. The default sort compares the items as strings: "1" < "100" < "25" < "5".
Exercise 2 🟢 easy build it
From [{ title: "Dune", price: 1299 }, { title: "Emma", price: 899 }], get the titles of the books under 1000 cents.
Show solution
Filter first, then map: each step returns a new array.
Exercise 3 🟢 easy fix the bug
The function should return a sorted copy, but it also changes the caller's array. Fix it.
Show solution
sort mutates in place and returns the same array. toSorted returns a new one.
Exercise 4 🟢 easy predict the output
What does ["10", "10", "10"].map(parseInt) return?
Show solution
[10, NaN, 2]. map calls parseInt("10", 0) (radix 0 means auto, so 10), then parseInt("10", 1) (an invalid radix, so NaN), then parseInt("10", 2) (binary, so 2).
Exercise 5 🟡 medium refactor it
Rewrite this loop as a single reduce that builds { classic: 2, "sci-fi": 1, … }.
Show solution
flatMap gathers all the tags, and reduce counts them. For large data, mutating the accumulator (acc[t] = …; return acc) avoids copying on every step. Chapter 9's Map or Object.groupBy are alternatives.
Exercise 6 🟡 medium predict the output
What does this print?
Show solution
4 [ 2, 3 ] [ 1, 4 ] [ 9, 4 ]. push returns the new length; splice returns the removed items and changes a; with returns a copy.
Exercise 7 🟡 medium build it · playground
Create playground/catalog.js exporting search(books, { text, maxPrice, sortBy = "title" }). It filters by a case-insensitive title substring and by maximum price (each optional), sorts by "title" (locale-aware) or "price", and never mutates the input.
Show solution
maxPrice != null accepts 0 as a real limit, and a lookup table of comparators avoids a switch.
Exercise 8 🟡 medium explain why
Why does Array(3).map((_, i) => i) give [ <3 empty items> ], while Array.from({ length: 3 }, (_, i) => i) gives [0, 1, 2]?
Show solution
Array(3) has three holes, and map skips holes, so the callback never runs. Array.from reads indexes 0 to length−1 explicitly, so it calls the function for each one.
Exercise 9 🔴 hard predict the output
What does this print?
Show solution
[ 4, 251 ] false. 260 wraps to 4, and −5 wraps to 251 (modulo 256). Storing 0.1 in 32 bits loses precision, so it no longer equals the 64-bit 0.1.
Exercise 10 🔴 hard mini-project · playground
Create playground/sales.js. From a list of sale records { day, bookId, qty, price }, compute (without mutating the input): total revenue; the top-2 books by revenue as [id, cents] pairs; the last day each book sold (findLast); and the days with no sales between the first and last day.
Show solution
It uses reduce with an initial value, toSorted on entries, findLast, Set for uniqueness, Array.from to build a range, and Object.fromEntries (Chapter 10).