An array is an ordered list of values — a shopping cart, a list of names, a set of scores. It’s one of the most-used data structures in JavaScript, and it comes with a rich set of built-in tools for reading, changing, and transforming the values it holds.
You create an array with square brackets, separating each value with a
comma. Each value gets a numeric index starting at 0:
const colors = ["red", "green", "blue"];
colors[0]; // "red" — first item
colors[2]; // "blue" — third item
colors.length; // 3 — how many items it holds
colors.at(-1); // "blue" — last item, without doing length - 1
The most common changes happen at the end of the array:
const queue = ["Alice", "Bob"];
queue.push("Carol"); // adds to the end → ["Alice", "Bob", "Carol"]
queue.pop(); // removes from the end, returns it → "Carol"
queue.unshift("Zara"); // adds to the start → ["Zara", "Alice", "Bob"]
queue.shift(); // removes from the start, returns it → "Zara"
for...of is the most common, readable way to visit every item in an array:
const colors = ["red", "green", "blue"];
for (const color of colors) {
console.log(color);
}
// "red"
// "green"
// "blue"
.forEach does the same thing in method form, handing each item to a
function you provide:
colors.forEach((color) => console.log(color));
These two methods are everywhere in modern JavaScript — they each take a function and return a brand new array, leaving the original untouched:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
// [2, 4, 6, 8, 10]
const evens = numbers.filter((n) => n % 2 === 0);
// [2, 4]
const labeled = numbers.map((n) => `Number ${n}`);
// ["Number 1", "Number 2", ...]
.map transforms every item into something new; .filter keeps only the
items that pass a test (a function that returns true or false).
Destructuring lets you pull values out of an array straight into named variables, which is far more readable than indexing one at a time:
const [first, second] = ["Alice", "Bob", "Carol"];
console.log(first); // "Alice"
console.log(second); // "Bob"
const [leader, ...rest] = ["Alice", "Bob", "Carol"];
console.log(leader); // "Alice"
console.log(rest); // ["Bob", "Carol"]
The ... spread syntax expands an array’s items in place — perfect for
copying an array or merging several together without mutating the originals:
const base = [1, 2, 3];
const copy = [...base]; // [1, 2, 3] — a new, independent array
const combined = [...base, 4, 5]; // [1, 2, 3, 4, 5]
const merged = [...base, ...[9, 9]]; // [1, 2, 3, 9, 9]
const numbers = [4, 1, 7, 3];
numbers.includes(7); // true
numbers.find((n) => n > 5); // 7 — first match
numbers.some((n) => n > 5); // true — does at least one match?
numbers.every((n) => n > 0); // true — do all of them match?
numbers.sort((a, b) => a - b); // [1, 3, 4, 7] — sorted ascending
numbers.reduce((total, n) => total + n, 0); // 15 — combine into one value
.reduce is the most flexible — and least intuitive at first glance — of
the bunch. It walks through the array building up a single result (here, a
running total that starts at 0), which makes it useful once .map and
.filter aren’t quite enough on their own.
Before for...of and the array methods above were common, loops over
arrays were written with a counter and the array’s length:
var colors = ["red", "green", "blue"];
for (var i = 0; i < colors.length; i++) {
console.log(colors[i]);
}
This works, but it introduces a counter variable (i) you have to manage
yourself, and it’s easy to get the start, end, or increment slightly wrong —
a classic source of “off-by-one” bugs. for...of removes the counter
entirely, and .map/.filter/.reduce go a step further by describing
what you want (“a new array of doubled numbers”) rather than how to loop
to get it — which is why they’re the style you’ll see in most JavaScript
written today. You’ll still come across the classic counting for loop —
the Loops lesson later in this series covers when it’s still the right tool.