Array Methods
Arrays are one of the most common data structures in JavaScript, and the language ships with a powerful set of built-in methods for working with them. Instead of writing manual loops for every operation, array methods let you express intent clearly and concisely. In this lesson you will learn the most important ones and understand when to reach for each.
What You'll Learn
By the end of this lesson you will write the three functions the capstone's task service runs on: one that picks out the pending tasks and turns them into display titles, one that folds a whole list down to a single total, and one that answers a yes/no question about the collection. You arrive able to loop over an array and read properties off an object (05-data-structures) and to write a function and store it in a variable (03-functions); you leave able to hand that function to a method and do the same work as one expression per question.
This lesson is required for the final project. The Task Manager's service layer — the part that answers "which tasks are pending?" and "how many of them are completed?" — is built from exactly these methods, and you will assemble it in 15-capstone-project.
Transforming Data with map
Array.prototype.map creates a new array by applying a function to every element. The original array is never modified.
const products = [
{ name: "Keyboard", price: 79 },
{ name: "Mouse", price: 45 },
{ name: "Monitor", price: 320 },
];
// Build a new object per product, with a discounted price alongside the name
const discounted = products.map((product) => ({
name: product.name,
salePrice: (product.price * 0.9).toFixed(2),
}));
console.log(discounted);
// Each result object carries the name plus a salePrice
Notice that map always returns an array of the same length as the input. If you have 3 products going in, you get 3 products coming out — just transformed.
Filtering Data with filter
Array.prototype.filter creates a new array containing only the elements for which the callback returns a truthy value.
const scores = [42, 88, 61, 95, 73, 30, 100, 54];
const passing = scores.filter((score) => score >= 60);
const failing = scores.filter((score) => score < 60);
console.log("Passing scores:", passing);
console.log("Failing scores:", failing);
console.log("Pass rate:", `${Math.round((passing.length / scores.length) * 100)}%`);
You can chain map and filter together. For example, to get the names of expensive products:
const inventory = [
{ name: "USB Hub", price: 25, inStock: true },
{ name: "Webcam", price: 89, inStock: false },
{ name: "Headset", price: 149, inStock: true },
{ name: "Desk Lamp", price: 39, inStock: true },
{ name: "Chair Mat", price: 55, inStock: false },
];
const availableExpensive = inventory
.filter((item) => item.inStock && item.price > 40)
.map((item) => item.name);
console.log("Available items over $40:", availableExpensive);
// [ 'Headset' ] — Desk Lamp ($39) fails the > 40 test; Webcam and Chair Mat are out of stock
Chaining works because each method returns a new array, so you can call the next method directly on the result.
Predict
Trace this pipeline by hand before running it. What does it log?
const products = [
{ name: "Pen", price: 3, inStock: true },
{ name: "Notebook", price: 12, inStock: false },
{ name: "Backpack", price: 60, inStock: true },
{ name: "Water Bottle", price: 18, inStock: true },
];
const result = products
.filter((p) => p.inStock)
.map((p) => p.price)
.filter((price) => price > 10);
console.log(result);Reducing Arrays with reduce
Array.prototype.reduce is the most flexible array method. It collapses an array into a single value by accumulating results across each element.
The signature is: array.reduce(callback, initialValue). The callback receives the accumulator (running result) and the current element.
const orders = [
{ id: 1, total: 45.99 },
{ id: 2, total: 120.00 },
{ id: 3, total: 8.50 },
{ id: 4, total: 67.25 },
];
// Sum all order totals
const grandTotal = orders.reduce((sum, order) => sum + order.total, 0);
console.log("Grand total: $" + grandTotal.toFixed(2));
// Group orders by whether they are over $50
const grouped = orders.reduce(
(groups, order) => {
const key = order.total >= 50 ? "large" : "small";
groups[key].push(order);
return groups;
},
{ large: [], small: [] }
);
console.log("Large orders:", grouped.large.length);
console.log("Small orders:", grouped.small.length);
Always provide an explicit initialValue (the second argument to reduce). Without it, reduce uses the first array element as the accumulator, which causes confusing bugs when working with arrays of objects.
Debug
This code is supposed to sum the prices and log 'Total: $444'. It doesn't throw — it prints something worse. Predict what it actually prints, then fix it so it logs 'Total: $444'.
const assert = require('assert');
const products = [
{ name: "Keyboard", price: 79 },
{ name: "Mouse", price: 45 },
{ name: "Monitor", price: 320 },
];
const total = products.reduce((sum, product) => sum + product.price);
assert.strictEqual(total, 444, 'expected 444, got ' + total);
console.log("Total: $" + total);Expected output:
Total: $444
Arrange the code
Reassemble the body of a grouping reduce callback. It sorts each order into a large or small bucket by total and returns the accumulator so the next order sees it. Every arrangement of these four lines either crashes or runs and prints something — only one prints { large: [ 2, 4 ], small: [ 1, 3 ] }. Why must they run in this order and no other?
bucket.push(order.id);return groups;const bucket = groups[key];const key = order.total >= 50 ? "large" : "small";
Finding Elements
When you need a single element rather than a filtered list, use find and findIndex.
findreturns the first matching element (orundefinedif none match)findIndexreturns the index of the first matching element (or-1if none match)
const users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "editor" },
{ id: 3, name: "Carol", role: "viewer" },
{ id: 4, name: "Dan", role: "editor" },
];
const admin = users.find((user) => user.role === "admin");
console.log("Admin:", admin.name);
const bobIndex = users.findIndex((user) => user.name === "Bob");
console.log("Bob is at index:", bobIndex);
// Safe lookup — find returns undefined if not found
const missing = users.find((user) => user.id === 99);
console.log("Missing user:", missing); // undefined
Testing Array Contents with some and every
Sometimes you do not need the elements themselves — you just need a yes/no answer about the collection.
somereturnstrueif at least one element satisfies the conditioneveryreturnstrueif all elements satisfy the condition
const cart = [
{ name: "Notebook", price: 12, available: true },
{ name: "Pen Set", price: 8, available: true },
{ name: "Stapler", price: 15, available: false },
];
const anyUnavailable = cart.some((item) => !item.available);
const allAffordable = cart.every((item) => item.price < 20);
const allAvailable = cart.every((item) => item.available);
console.log("Any unavailable?", anyUnavailable); // true
console.log("All affordable?", allAffordable); // true
console.log("All available?", allAvailable); // false
if (anyUnavailable) {
const unavailable = cart.filter((item) => !item.available).map((i) => i.name);
console.log("Out of stock:", unavailable.join(", "));
}
Recall
Without scrolling up: recall the arrays of objects you built in 05-data-structures and the callbacks you have been handing to map, filter and reduce all through this lesson. You now call three methods on one of those arrays of objects — map, filter, and reduce (with an initial value of 0), each taking a callback. map and filter each return one thing, reduce returns another. Which row correctly describes what each RETURNS?
Transfer
You just used reduce twice with two different accumulators: a number starting at 0, and an object starting at { large: [], small: [] }. In both cases the initial value silently decided what type the accumulator is, and JavaScript never checked that the callback kept it that way. New setting: the same reduce, written in TypeScript, where the compiler does check. Which statement carries the SAME idea across?
// JavaScript: the initial value alone decides the accumulator's shape.
const total = orders.reduce((sum, order) => sum + order.total, 0);
const grouped = orders.reduce((groups, order) => { /* ... */ }, { large: [], small: [] });Try It Yourself
Reading about array methods is not the same as reaching for them by intent. This is a build task: a small program that reports its own pass/fail. You are given a task list — an array of task objects — and three empty functions to finish. Run it and it fails immediately, telling you which function is still missing. Implement each one until every check passes and it prints All checks passed.
The three functions reuse exactly the methods this lesson taught: chaining filter into map to select and reshape, reduce with an explicit initial value to aggregate, and some to answer a yes/no question about the collection. The starter already has the data, the stubs, and the checks — you write only the logic inside each function. Nothing above spells out all three answers, so you will have to assemble them yourself.
Build
Finish the build. Three functions are stubbed out and the checks below them fail until each one returns the right value. Run it as-is to see which check fails first, decide what that function is missing, then implement the three functions until it prints 'All checks passed.' The checks run top to bottom, so the first failure you see is TODO 1 — implement it first, then work down.
const assert = require('assert');
// The data: a task list for the manager service. Do NOT change this array.
const tasks = [
{ title: "Write spec", status: "done", priority: 3 },
{ title: "Review PR", status: "pending", priority: 5 },
{ title: "Fix bug", status: "pending", priority: 8 },
{ title: "Deploy", status: "done", priority: 2 },
{ title: "Update docs", status: "pending", priority: 4 },
];
// TODO 1: return a NEW array of the titles of only the pending tasks, in order.
// pendingTitles(tasks) -> ["Review PR", "Fix bug", "Update docs"]
function pendingTitles(tasks) {
// your code here
}
// TODO 2: return the sum of every task's priority as a number.
// totalPriority(tasks) -> 22
function totalPriority(tasks) {
// your code here
}
// TODO 3: return true if ANY task has a priority of 8 or higher, else false.
// hasUrgent(tasks) -> true
function hasUrgent(tasks) {
// your code here
}
// --- Build checks: these must all pass. Do not edit below this line. ---
assert.deepStrictEqual(
pendingTitles(tasks),
["Review PR", "Fix bug", "Update docs"],
"pendingTitles(tasks) should filter to pending tasks and map them to their titles, in order",
);
assert.strictEqual(
totalPriority(tasks),
22,
"totalPriority(tasks) should reduce every task's priority into a single sum (22)",
);
assert.strictEqual(
hasUrgent(tasks),
true,
"hasUrgent(tasks) should return true when at least one task has priority >= 8",
);
console.log("All checks passed.");
console.log("Pending titles:", pendingTitles(tasks));
console.log("Total priority:", totalPriority(tasks));
console.log("Any urgent?", hasUrgent(tasks));Expected output:
All checks passed. Pending titles: [ 'Review PR', 'Fix bug', 'Update docs' ] Total priority: 22 Any urgent? true
Once it passes, try two variations and predict each before running:
- Flip the filter predicate. In
pendingTitles, change thefiltertest fromstatus === "pending"tostatus === "done". Write down which titles you expect back, and in what order, before running. Themapis untouched, so whatever changes is thefilter's doing. somevsevery. InhasUrgent, swapsomeforevery, keeping the samepriority >= 8test. Decide whathasUrgent(tasks)returns before running. Look at the five priorities in the task list first:someasks "at least one?" andeveryasks "all of them?", and only one of those questions has the same answer for this data.
Capstone milestone
Milestone — the Task Manager service. The capstone's service (Step 3 of the capstone project) filters, sorts, and aggregates tasks with exactly these array methods. This lesson's exercises are that skill in miniature. Confirm you can reach for each method by intent.
Hint: You don't need the full capstone service yet — this confirms the map/filter/reduce fluency the service is built on. In the capstone, these same methods count pending tasks, filter by status, and build the stats line.
- Used filter to select a subset of items by a condition
- Used map to transform items into a new array without mutating the original
- Used reduce with an explicit initial value to compute an aggregate (a sum, count, or grouping)
- Used find (or some/every) to answer a single-item or yes/no question about the collection
Key Takeaways
maptransforms every element and returns a new array of the same length — use it when you need to reshape datafilterreturns a new array containing only elements that pass a test — the original array is never mutatedreduceis the Swiss Army knife: it can replicatemapandfilter, compute aggregates, and build complex data structuresfindandfindIndexstop at the first match, so they save exactly the part of the array they skip — a big win when the match is near the front, and no real win when it is near the end or missing, since then both they andfilterscan everythingsomeandeveryreturn booleans and short-circuit early, so they are ideal for validation checks- All of these methods accept a callback function, making them composable and chainable
- None of these methods mutate the original array (unlike
push,pop,splice, orsort)
Pro Tip: Reach for
map,filter, andreducebefore writing aforloop. They signal intent clearly — a reader seeingfilterimmediately knows you are selecting a subset, whereas aforloop requires reading the body to understand what it does. When you do need a loop (for side effects like logging or DOM updates),forEachis the idiomatic choice.
Next Steps
Now that you can transform arrays with confidence, the next lesson covers destructuring and the spread operator — two features that make working with arrays and objects dramatically more concise.
Next lesson
Destructuring and Spread
Learn JavaScript destructuring and the spread operator. Unpack arrays and objects to write cleaner, more expressive modern JavaScript.
20 min