Arrays in JavaScript
Learn JavaScript arrays — creation, functional methods like map/filter/reduce, destructuring, sorting, and common transformation patterns.
Arrays are JavaScript’s go-to ordered collection. They can hold any mix of values, grow and shrink dynamically, and come with a rich set of built-in methods. The functional iteration methods — map, filter, reduce, and their companions — are the heart of modern JavaScript data transformation, letting you express what you want rather than spelling out every loop step by step.
Creating Arrays
The array literal [] is by far the most common way to create an array and should be your default. Array.from is the right tool when you need to create an array from something that isn’t already one — a string, a NodeList, or a range generated from a length.
// Array literal — the standard way
const fruits = ["apple", "banana", "cherry"];
const numbers = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null, { id: 1 }]; // arrays can hold any type
// Array constructor (rarely needed — prefer the literal)
const empty = new Array(3); // [empty × 3] — length 3, no actual values
// Array.from — create from any iterable or array-like
const chars = Array.from("hello"); // ["h","e","l","l","o"]
const doubled = Array.from([1, 2, 3], x => x * 2); // [2, 4, 6] — map in one step
const range = Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]
// Spread to copy or combine
const copy = [...fruits];
const combined = [...fruits, ...numbers];
map — Transform Every Element
map is the go-to method when you want to convert each element of an array into something else. It always returns a new array of exactly the same length, with each element replaced by whatever your callback returns. It never modifies the original array. Use it whenever you need to reshape data — converting raw values to display strings, extracting fields from objects, or computing derived values.
const prices = [10, 25, 8, 42, 15];
// Apply a transformation to every element
const withTax = prices.map(p => p * 1.1);
const formatted = prices.map(p => `$${p.toFixed(2)}`);
console.log(withTax); // [11, 27.5, 8.8, 46.2, 16.5]
console.log(formatted); // ["$10.00", "$25.00", "$8.80", "$46.20", "$15.00"]
// Real-world: transform an API response into a view model
const users = [
{ id: 1, firstName: "Alice", lastName: "Smith", age: 30 },
{ id: 2, firstName: "Bob", lastName: "Jones", age: 25 },
];
const viewModels = users.map(u => ({
id: u.id,
fullName: `${u.firstName} ${u.lastName}`, // combine fields
isAdult: u.age >= 18, // derive a boolean
}));
filter — Keep Matching Elements
filter returns a new array containing only the elements for which the callback returns a truthy value. It’s the right choice whenever you need a subset of a collection — removing out-of-stock items, keeping only recent records, or narrowing search results. Like map, it never touches the original array.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = numbers.filter(n => n % 2 === 0); // [2, 4, 6, 8, 10]
const bigEvens = numbers.filter(n => n % 2 === 0 && n > 5); // [6, 8, 10]
// Real-world: filter a product list by availability and price
const products = [
{ name: "Laptop", price: 999, inStock: true },
{ name: "Mouse", price: 29, inStock: false },
{ name: "Desk", price: 349, inStock: true },
{ name: "Chair", price: 199, inStock: true },
];
const affordable = products.filter(p => p.inStock && p.price < 500);
// [{ name: "Desk", ... }, { name: "Chair", ... }]
reduce — Aggregate to a Single Value
reduce is the most flexible array method: it processes the entire array and accumulates a single result — a sum, an object, a nested structure, or even another array. The accumulator starts at the initial value you provide and is passed into each callback call along with the current element. It’s the tool to reach for when map and filter aren’t enough.
const cart = [
{ name: "Laptop", price: 999, qty: 1 },
{ name: "Mouse", price: 29, qty: 2 },
{ name: "Cable", price: 9, qty: 3 },
];
// Sum a computed value across all elements
const total = cart.reduce((sum, item) => sum + item.price * item.qty, 0);
console.log(total); // 1084
// Group elements by a property — accumulator becomes an object
const orders = [
{ id: 1, status: "shipped" },
{ id: 2, status: "pending" },
{ id: 3, status: "shipped" },
{ id: 4, status: "pending" },
{ id: 5, status: "delivered" },
];
const grouped = orders.reduce((acc, order) => {
const key = order.status;
if (!acc[key]) acc[key] = [];
acc[key].push(order);
return acc;
}, {});
// { shipped: [...], pending: [...], delivered: [...] }
// Flatten one level (flatMap is cleaner — see below)
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5]
Chaining map, filter, reduce
Because these methods all return new arrays (or a value), you can chain them together into a readable data pipeline. Each step clearly expresses one transformation, and the chain reads almost like prose. This is one of the most powerful patterns in modern JavaScript.
const transactions = [
{ amount: 120, type: "income" },
{ amount: 45, type: "expense" },
{ amount: 200, type: "income" },
{ amount: 80, type: "expense" },
{ amount: 60, type: "income" },
];
const netIncome = transactions
.filter(t => t.type === "income") // step 1: keep only income
.map(t => t.amount) // step 2: extract the amounts
.reduce((sum, amount) => sum + amount, 0); // step 3: sum them
console.log(netIncome); // 380
find, findIndex, some, every
These four methods answer questions about the contents of an array without creating a new one. find and findIndex locate the first matching element. some and every test whether any or all elements satisfy a condition. All four stop as soon as they have their answer, so they’re efficient on large arrays.
const users = [
{ id: 1, name: "Alice", admin: false },
{ id: 2, name: "Bob", admin: true },
{ id: 3, name: "Carol", admin: false },
];
users.find(u => u.id === 2); // { id: 2, name: "Bob", admin: true }
users.find(u => u.id === 99); // undefined — not found
users.findIndex(u => u.name === "Carol"); // 2 — index in the array
users.findIndex(u => u.name === "Dave"); // -1 — not found
users.some(u => u.admin); // true — at least one admin
users.every(u => u.admin); // false — not all are admins
users.every(u => u.id > 0); // true — all have positive id
flat and flatMap
flat solves the common problem of arrays nested inside arrays — something that arises naturally from one-to-many relationships in data. flatMap combines a map and a flat(1) in a single, more efficient pass, making it the right tool when each element should expand into multiple results.
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat(); // [1, 2, 3, 4, [5, 6]] — one level deep
nested.flat(2); // [1, 2, 3, 4, 5, 6] — two levels deep
nested.flat(Infinity); // fully flattened regardless of depth
// flatMap = map + flat(1) in one efficient pass
const sentences = ["hello world", "foo bar"];
const words = sentences.flatMap(s => s.split(" "));
// ["hello", "world", "foo", "bar"]
// Real-world: expand one-to-many relationships
const categories = [
{ name: "JS", tags: ["frontend", "backend"] },
{ name: "Python", tags: ["backend", "data"] },
];
const allTags = categories.flatMap(c => c.tags);
// ["frontend", "backend", "backend", "data"]
Destructuring
Array destructuring lets you unpack values from an array into named variables in a single statement. This makes code that returns or passes multiple values much cleaner — no more result[0], result[1]. The rest syntax (...) collects any remaining elements into a new array.
const [first, second, ...rest] = [10, 20, 30, 40, 50];
console.log(first); // 10
console.log(second); // 20
console.log(rest); // [30, 40, 50]
// Skip elements with empty commas
const [,, third] = [10, 20, 30];
console.log(third); // 30
// Swap variables without a temp variable
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
// Destructure a function's array return value
function minMax(arr) {
return [Math.min(...arr), Math.max(...arr)];
}
const [min, max] = minMax([3, 1, 4, 1, 5, 9]);
console.log(min, max); // 1 9
Sorting
sort reorders the elements of an array in place and returns the same array. This in-place mutation is the most important thing to know about it. The second most important: without a comparator function, sort converts everything to strings and compares them lexicographically — which produces wrong results for numbers.
const nums = [10, 2, 30, 4, 20];
// Wrong — lexicographic sort treats numbers as strings
nums.sort(); // [10, 2, 20, 30, 4] ← "10" < "2" as strings
// Correct — numeric ascending: subtract gives negative/zero/positive
nums.sort((a, b) => a - b); // [2, 4, 10, 20, 30]
// Numeric descending
nums.sort((a, b) => b - a); // [30, 20, 10, 4, 2]
// Sort objects by a property
const people = [
{ name: "Charlie", age: 35 },
{ name: "Alice", age: 28 },
{ name: "Bob", age: 42 },
];
people.sort((a, b) => a.age - b.age); // ascending by age
people.sort((a, b) => a.name.localeCompare(b.name)); // alphabetical by name
// Non-mutating sort — spread to copy first
const sorted = [...people].sort((a, b) => a.age - b.age);
// original `people` array is unchanged
slice vs splice
slice and splice look similar but behave very differently. slice is non-destructive: it returns a portion of the array as a new array and leaves the original untouched. splice modifies the original array in place and is the tool for inserting or removing elements at a specific position.
const arr = [0, 1, 2, 3, 4, 5];
// slice — does NOT mutate, returns new array
arr.slice(2, 4); // [2, 3] — from index 2 up to (not including) 4
arr.slice(-2); // [4, 5] — last two elements
console.log(arr); // [0,1,2,3,4,5] — unchanged
// splice — MUTATES in place, returns the removed elements
const removed = arr.splice(2, 2); // remove 2 elements starting at index 2
console.log(removed); // [2, 3]
console.log(arr); // [0, 1, 4, 5] — original is modified
// splice to insert at a position
arr.splice(2, 0, 10, 11); // at index 2, remove 0 elements, insert 10 and 11
console.log(arr); // [0, 1, 10, 11, 4, 5]
Common Pitfalls
Mutating the original objects inside map:
// Objects in arrays are references — creating a new array doesn't clone the objects
const users = [{ name: "Alice" }, { name: "Bob" }];
const names = users.map(u => {
u.active = true; // mutates the original objects — side effect!
return u.name;
});
// Fix: create new objects with spread instead of modifying in place
const safe = users.map(u => ({ ...u, active: true }));
sort mutates in place — sort a copy when you need the original:
const original = [3, 1, 2];
const sorted = [...original].sort((a, b) => a - b);
// original is still [3, 1, 2]
Array.from({ length: n }) gives undefined elements, not null:
Array.from({ length: 3 }); // [undefined, undefined, undefined]
Array.from({ length: 3 }, () => 0); // [0, 0, 0] — use a mapper for a real value