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 of x, or -1. lastIndexOf(x) searches from the end.
  • includes(x) — true or false.
  • find(test) — the first item for which the function test returns a truthy value, or undefined. findIndex(test) returns its index (or -1); findLast and findLastIndex search 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.