Skip to main content
JavaScript beginner Lesson 9 of 24

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

Frequently Asked Questions

What is the difference between slice and splice?
slice returns a new array containing elements from a start index to an end index, without modifying the original. splice modifies the original array in place — it can remove elements, insert new ones, or both.
Does map/filter/reduce mutate the original array?
No. map, filter, and reduce all return new arrays (or a single value for reduce) and leave the original untouched. This makes them safe to chain and easy to reason about.
When should I use find vs filter?
Use find when you want the first matching element (returns the element or undefined). Use filter when you want all matching elements (returns an array, possibly empty).