Arrays
Ordered lists of values: creating and changing arrays, the methods map, filter, reduce and find, sorting correctly, destructuring and the spread syntax.
Creating arrays
An array stores values in a fixed order. In JavaScript you write one as an array literal: values between square brackets, separated by commas. The values can be of any type, and an array grows and shrinks as needed — it is a dynamic array, like a Python list.
const readings = [412.5, 413.1, 415.8]; // kWh meter readings
const ticket = ["INC-20481", "Rotterdam", 3, true];
const empty = [];
console.log(readings);
console.log(ticket.length, empty.length);
console.log(typeof readings, Array.isArray(readings));
[ 412.5, 413.1, 415.8 ] 4 0 object true
Arrays are objects, so typeof says
"object". To test whether a value really is an array,
use Array.isArray(). Declaring an array with
const only fixes the variable: you can still change the
array's contents, you just cannot assign a different array to the
same name.
Indexing and length
Items are numbered from 0. readings[0] is the first
item and readings[readings.length - 1] the last. The
method at() (ES2022) also accepts negative numbers,
which count from the end.
const readings = [412.5, 413.1, 415.8];
console.log(readings[0]);
console.log(readings[readings.length - 1]);
console.log(readings.at(-1));
console.log(readings[3]); // past the end
console.log(readings[-1]); // not "the last item"
readings[1] = 413.4; // replace an item
console.log(readings);
412.5 415.8 415.8 undefined undefined [ 412.5, 413.4, 415.8 ]
Writing to an index beyond the end makes the array longer and leaves
empty slots in between, which print as
<1 empty item>. That is rarely what you want; add
items with push() instead.
Array-like objects
Some values look like arrays but are not. An
array-like object has a
length and numbered items, but not the array methods:
examples are a string, the arguments object inside a
function, and the list of elements returned by
document.querySelectorAll() in a browser.
Array.from() turns any array-like or
iterable value into a real array.
const fake = { 0: "Delft", 1: "Leiden", length: 2 };
console.log(Array.isArray(fake));
console.log(Array.from(fake));
console.log(Array.from("kWh"));
console.log(Array.from({ length: 4 }, (_, i) => i * 15)); // minutes past the hour
false [ 'Delft', 'Leiden' ] [ 'k', 'W', 'h' ] [ 0, 15, 30, 45 ]
Adding and removing items
Four methods work at the ends of an array. All four change the array in place.
| Method | What it does | Returns |
|---|---|---|
push(x, …) |
adds items at the end | the new length |
pop() |
removes the last item | the removed item |
unshift(x, …) |
adds items at the start | the new length |
shift() |
removes the first item | the removed item |
const queue = ["INC-20481", "INC-20482"];
console.log(queue.push("INC-20483")); // new length
console.log(queue.unshift("INC-20479")); // urgent ticket at the front
console.log(queue);
console.log(queue.shift()); // handle the first one
console.log(queue.pop()); // and the last one
console.log(queue);
console.log([].pop()); // nothing to remove
3 4 [ 'INC-20479', 'INC-20481', 'INC-20482', 'INC-20483' ] INC-20479 INC-20483 [ 'INC-20481', 'INC-20482' ] undefined
Changing the middle with splice
splice(start, deleteCount, ...newItems) removes and
inserts items anywhere. It changes the array and returns an array of
the removed items.
const shifts = ["Amara", "Mei", "Hamid", "Sanne"];
const removed = shifts.splice(1, 2); // remove 2 items from index 1
console.log(removed, shifts);
shifts.splice(1, 0, "Joost", "Tomasz"); // remove 0, insert 2
console.log(shifts);
[ 'Mei', 'Hamid' ] [ 'Amara', 'Sanne' ] [ 'Amara', 'Joost', 'Tomasz', 'Sanne' ]
Slicing, combining and reversing
slice(start, end) returns a new array with the items
from start up to, but not including, end —
the same rule as Python slices. Negative positions count from the
end; leaving out end means "to the end".
concat() returns a new array with other arrays (or
values) added; join() glues the items into one string.
Neither changes the original. reverse(), however,
reverses the array in place and returns that same array.
const temps = [8.2, 9.1, 11.4, 12.0, 10.3]; // °C, Monday to Friday
console.log(temps.slice(1, 3));
console.log(temps.slice(-2));
console.log(temps.concat([7.9]));
console.log(temps.join(" | "));
console.log(temps); // unchanged so far
const backwards = temps.reverse();
console.log(backwards, backwards === temps);
console.log(temps); // changed!
[ 9.1, 11.4 ] [ 12, 10.3 ] [ 8.2, 9.1, 11.4, 12, 10.3, 7.9 ] 8.2 | 9.1 | 11.4 | 12 | 10.3 [ 8.2, 9.1, 11.4, 12, 10.3 ] [ 10.3, 12, 11.4, 9.1, 8.2 ] true [ 10.3, 12, 11.4, 9.1, 8.2 ]
Since ES2023 there are non-changing versions of the in-place
methods: toReversed(), toSorted(),
toSpliced(), and with(index, value) for
replacing one item. They return a new array, which is handy when
other code still uses the original.
const temps = [8.2, 9.1, 11.4];
console.log(temps.toReversed());
console.log(temps.with(0, 8.5));
console.log(temps);
[ 11.4, 9.1, 8.2 ] [ 8.5, 9.1, 11.4 ] [ 8.2, 9.1, 11.4 ]
Finding items
Which method to use depends on what you want back: a position, a yes or no, or the item itself.
-
indexOf(x)— the first index ofx, or-1.lastIndexOf(x)searches from the end. -
includes(x)—trueorfalse. -
find(test)— the first item for which the functiontestreturns a truthy value, orundefined.findIndex(test)returns its index (or-1);findLastandfindLastIndexsearch from the end. -
some(test)— is the test true for at least one item?every(test)— for all items?
indexOf and includes compare with
=== (see
Operators, Comparison and Type Coercion), so they work for strings and numbers but not for finding an
object by its contents. For that, pass a test function to
find.
const tickets = [
{ id: "INC-20481", city: "Rotterdam", priority: 3 },
{ id: "INC-20482", city: "Delft", priority: 1 },
{ id: "INC-20483", city: "Leiden", priority: 2 },
];
const cities = tickets.map((t) => t.city);
console.log(cities.indexOf("Delft"), cities.indexOf("Utrecht"));
console.log(cities.includes("Leiden"));
console.log(tickets.find((t) => t.priority === 1));
console.log(tickets.findIndex((t) => t.city === "Leiden"));
console.log(tickets.some((t) => t.priority === 1));
console.log(tickets.every((t) => t.priority <= 2));
1 -1
true
{ id: 'INC-20482', city: 'Delft', priority: 1 }
2
true
false
One small difference: [NaN].includes(NaN) is
true, but [NaN].indexOf(NaN) is
-1, because includes treats
NaN as equal to itself.
Iterating and transforming
for...of and forEach
To visit every item, use a for...of loop (see
Control Flow and Loops) or
the method forEach, which calls a function once per
item with the item, its index and the whole array. A
for...of loop can stop early with break;
forEach cannot. Use entries() when the
loop needs the index too.
const temps = [8.2, 9.1, 11.4];
for (const [day, t] of temps.entries()) {
console.log(`Day ${day}: ${t} °C`);
}
temps.forEach((t, i) => console.log(i, t));
Day 0: 8.2 °C Day 1: 9.1 °C Day 2: 11.4 °C 0 8.2 1 9.1 2 11.4
The methods below take a function as an argument, which makes them higher-order functions. The function you pass is usually an arrow function (see Functions). The general idea of map, filter and reduce is explained in Programming Paradigms.
map
map(fn) calls
fn on every item and returns a new array of
the results, always with the same length as the original.
const pricesExVat = [19.95, 4.5, 120];
const withVat = pricesExVat.map((p) => Math.round(p * 1.21 * 100) / 100);
console.log(withVat);
const ids = [20481, 20482].map((n) => `INC-${n}`);
console.log(ids);
[ 24.14, 5.45, 145.2 ] [ 'INC-20481', 'INC-20482' ]
filter
filter(test) returns a
new array with only the items for which test
returns a truthy value. The result can be shorter, or empty.
const readings = [412.5, -1, 413.1, null, 415.8]; // -1 and null: meter errors
const valid = readings.filter((r) => typeof r === "number" && r >= 0);
console.log(valid);
[ 412.5, 413.1, 415.8 ]
reduce
reduce(fn, initial)
combines all items into one value. It keeps an
accumulator: it starts as initial, and for
every item fn(accumulator, item) returns the next
accumulator. The last accumulator is the result.
const usage = [3.2, 4.1, 2.7, 5.0]; // kWh per day
const total = usage.reduce((sum, kwh) => sum + kwh, 0);
console.log(total.toFixed(1));
const peak = usage.reduce((max, kwh) => (kwh > max ? kwh : max), -Infinity);
console.log(peak);
// Count tickets per city: the accumulator is an object
const cities = ["Delft", "Leiden", "Delft", "Rotterdam", "Delft"];
const perCity = cities.reduce((counts, c) => {
counts[c] = (counts[c] ?? 0) + 1;
return counts;
}, {});
console.log(perCity);
15.0
5
{ Delft: 3, Leiden: 1, Rotterdam: 1 }
For simple totals a for...of loop is just as good and
often easier to read; use reduce when it makes the
intent clearer, not to be clever. Grouping items, as in the last
example, also has a dedicated function since ES2024:
Object.groupBy(items, fn) (Node 21+ and current
browsers).
Chaining
Because map and filter return arrays, you
can chain them. Read the chain top to bottom, like a pipeline.
const tickets = [
{ id: "INC-20481", city: "Rotterdam", minutes: 95, open: false },
{ id: "INC-20482", city: "Delft", minutes: 40, open: true },
{ id: "INC-20483", city: "Leiden", minutes: 210, open: false },
{ id: "INC-20484", city: "Delft", minutes: 30, open: false },
];
const closedHours = tickets
.filter((t) => !t.open)
.map((t) => t.minutes / 60)
.reduce((sum, h) => sum + h, 0);
console.log(`${closedHours.toFixed(2)} hours on closed tickets`);
5.58 hours on closed tickets
flat() flattens nested arrays one level (or more, with
a depth argument), and flatMap(fn) is a
map followed by flat().
Sorting
sort() sorts the array in place and returns it. Its
default surprises everybody once: without a compare function it
converts the items to strings and compares those, character by
character.
const minutes = [95, 40, 210, 30, 100];
console.log(minutes.sort());
[ 100, 210, 30, 40, 95 ]
As strings, "100" comes before "210",
which comes before "30", because the first characters
are compared first. For numbers, pass a compare function
(a, b) => … that returns a negative number when
a belongs before b, a positive number when
it belongs after, and 0 when their order does not
matter. For numbers, a - b does exactly that.
const minutes = [95, 40, 210, 30, 100];
console.log(minutes.sort((a, b) => a - b)); // ascending
console.log(minutes.sort((a, b) => b - a)); // descending
const cities = ["Leiden", "delft", "Assen", "Zwolle"];
console.log(cities.toSorted()); // uppercase first
console.log(cities.toSorted((a, b) => a.localeCompare(b)));
[ 30, 40, 95, 100, 210 ] [ 210, 100, 95, 40, 30 ] [ 'Assen', 'Leiden', 'Zwolle', 'delft' ] [ 'Assen', 'delft', 'Leiden', 'Zwolle' ]
Strings sort by their UTF-16 code units, so all capitals come before
all lowercase letters. localeCompare sorts the way
people expect for a language. To sort objects, compare one of their
properties:
const tickets = [
{ id: "INC-20481", priority: 3 },
{ id: "INC-20482", priority: 1 },
{ id: "INC-20483", priority: 3 },
{ id: "INC-20484", priority: 1 },
];
const byPriority = tickets.toSorted((a, b) => a.priority - b.priority);
console.log(byPriority.map((t) => t.id));
[ 'INC-20482', 'INC-20484', 'INC-20481', 'INC-20483' ]
Since ES2019 sort must be a
stable sort: items that compare as equal keep their original order, which is
why INC-20482 still comes before INC-20484. Use
toSorted() (ES2023) when you need a sorted copy and
want to keep the original as it was.
Copies and references
An array is an object, and a variable holds a reference to it, not the array itself. Assigning the variable to another name copies the reference: both names now point to the same array, and a change through one is visible through the other.
const original = ["Amara", "Mei"];
const alias = original; // same array
const copy = [...original]; // new array with the same items
alias.push("Hamid");
console.log(original);
console.log(copy);
console.log(alias === original, copy === original);
console.log([1, 2] === [1, 2]);
[ 'Amara', 'Mei', 'Hamid' ] [ 'Amara', 'Mei' ] true false false
=== on arrays compares references, not contents: two
separately created arrays are never equal, even with the same items.
To compare contents, compare the lengths and the items one by one
(or, for simple data, compare
JSON.stringify(a) === JSON.stringify(b)).
[...original], original.slice() and
Array.from(original) all make a
shallow copy: a new outer array, but nested arrays and objects inside it are
still shared. Deep copies are covered in
Objects and JSON.
Destructuring and spread
Destructuring
Destructuring assignment
unpacks values from an array (or an object) into separate variables
in one statement. The pattern on the left mirrors the shape of the
value on the right. It works like
unpacking
in Python, with a few extras: you can skip positions with an empty
comma, give default values, and collect the rest with
....
const reading = ["MTR-0042", 415.8, "kWh"];
const [meter, value, unit] = reading;
console.log(meter, value, unit);
const [first, , third] = ["Delft", "Leiden", "Rotterdam"];
console.log(first, third);
const [status = "open", owner = "unassigned"] = ["closed"];
console.log(status, owner);
const [latest, ...older] = [415.8, 413.1, 412.5];
console.log(latest, older);
let a = 1, b = 2;
[a, b] = [b, a]; // swap
console.log(a, b);
MTR-0042 415.8 kWh Delft Rotterdam closed unassigned 415.8 [ 413.1, 412.5 ] 2 1
The spread syntax
The spread syntax
...value expands an iterable into its separate items,
at a place where a list of items is expected: inside an array
literal or in the arguments of a function call. It looks the same as
a rest element, but does the opposite: rest collects items
into an array, spread expands an array into items.
const monday = [8.2, 9.1];
const tuesday = [11.4, 12.0];
const week = [...monday, ...tuesday, 10.3];
console.log(week);
console.log(Math.max(...week)); // Math.max takes separate numbers
console.log([..."kWh"]); // a string is iterable too
const withNewFirst = ["INC-20479", ...["INC-20481", "INC-20482"]];
console.log(withNewFirst);
[ 8.2, 9.1, 11.4, 12, 10.3 ] 12 [ 'k', 'W', 'h' ] [ 'INC-20479', 'INC-20481', 'INC-20482' ]
Spread works on any iterable: arrays, strings, Maps and Sets.
Objects have their own spread form, { ...obj },
explained in Objects and JSON.