DevToolBox無料
ブログ

JavaScript map(), filter(), reduce() 例集

11分by DevToolBox

JavaScript's map(), filter(), and reduce() are the three pillars of functional array processing. They enable you to transform, select, and aggregate data without mutating the original array. Combined with method chaining, they create expressive data pipelines that are easier to read, test, and maintain than imperative loops. This guide covers every method in depth with runnable examples showing input and output.

Format and inspect your JSON data with our JSON Formatter →

Beautify your JavaScript code with our JS/HTML Formatter →

1. Overview — Functional Programming in JavaScript

Functional programming in JavaScript revolves around pure functions, immutability, and declarative data transformations. The three array methods — map(), filter(), and reduce() — embody these principles: they never mutate the source array, always return a new value, and can be chained together to build complex operations from simple building blocks.

Why use them instead of for loops? They separate the what from the how. When you see .filter(), you immediately know the intent is to select elements. When you see .map(), you know the intent is to transform each element. This makes code self-documenting and reduces bugs caused by off-by-one errors or accidental mutations.

// Imperative (for loop) vs Declarative (map/filter/reduce)

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Imperative: get sum of squares of even numbers
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    sum += numbers[i] ** 2;
  }
}
console.log(sum); // 220

// Declarative: same result, but reads like a sentence
const result = numbers
  .filter(n => n % 2 === 0)       // [2, 4, 6, 8, 10]
  .map(n => n ** 2)                // [4, 16, 36, 64, 100]
  .reduce((acc, n) => acc + n, 0); // 220

console.log(result); // 220

// Key principle: the original array is NEVER modified
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] — unchanged

2. map() Basics — Syntax and Callback Parameters

Array.prototype.map() creates a new array by calling a callback function on every element of the original array. It always returns an array of the same length. The callback receives three arguments: the current value, the index, and the entire array.

// Syntax:
// array.map(callback(currentValue, index, array), thisArg?)

// Basic: double every number
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2);
console.log(doubled);
// Output: [2, 4, 6, 8, 10]

// Using all three callback parameters
const indexed = ['a', 'b', 'c'].map((value, index, array) => {
  return `${index}/${array.length}: ${value}`;
});
console.log(indexed);
// Output: ["0/3: a", "1/3: b", "2/3: c"]

// map() ALWAYS returns a new array of the same length
const original = [10, 20, 30];
const mapped = original.map(x => x);
console.log(mapped === original); // false (different reference)
console.log(mapped.length === original.length); // true

// Common gotcha: map() with parseInt
console.log(['1', '2', '3'].map(Number));     // [1, 2, 3] ✅
console.log(['1', '2', '3'].map(parseInt));    // [1, NaN, NaN] ❌
// Because parseInt receives (value, index) → parseInt('2', 1) is NaN
// Fix: wrap in arrow function
console.log(['1', '2', '3'].map(s => parseInt(s, 10))); // [1, 2, 3] ✅

3. map() Real-World Examples

Below are production-ready patterns for map(): transforming objects, extracting fields, formatting data, and using TypeScript generics for type safety.

// 3a. Transform objects — add computed fields
const users = [
  { firstName: 'Alice', lastName: 'Smith', age: 30 },
  { firstName: 'Bob', lastName: 'Jones', age: 25 },
  { firstName: 'Charlie', lastName: 'Brown', age: 35 },
];

const enriched = users.map(user => ({
  ...user,
  fullName: `${user.firstName} ${user.lastName}`,
  isAdult: user.age >= 18,
}));

console.log(enriched);
// Output:
// [
//   { firstName: 'Alice', lastName: 'Smith', age: 30, fullName: 'Alice Smith', isAdult: true },
//   { firstName: 'Bob', lastName: 'Jones', age: 25, fullName: 'Bob Jones', isAdult: true },
//   { firstName: 'Charlie', lastName: 'Brown', age: 35, fullName: 'Charlie Brown', isAdult: true },
// ]
// 3b. Extract specific fields (projection)
const products = [
  { id: 1, name: 'Laptop', price: 999, stock: 50 },
  { id: 2, name: 'Phone', price: 699, stock: 120 },
  { id: 3, name: 'Tablet', price: 499, stock: 80 },
];

const names = products.map(p => p.name);
console.log(names);
// Output: ["Laptop", "Phone", "Tablet"]

// Extract id-name pairs for a dropdown
const options = products.map(({ id, name }) => ({ value: id, label: name }));
console.log(options);
// Output: [{ value: 1, label: 'Laptop' }, { value: 2, label: 'Phone' }, { value: 3, label: 'Tablet' }]
// 3c. Format data for display
const prices = [1299.5, 49.99, 0.5, 1000];

const formatted = prices.map(p =>
  new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p)
);
console.log(formatted);
// Output: ["$1,299.50", "$49.99", "$0.50", "$1,000.00"]

// Format dates
const timestamps = [1700000000000, 1710000000000, 1720000000000];
const dates = timestamps.map(ts =>
  new Date(ts).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })
);
console.log(dates);
// Output: ["Nov 14, 2023", "Mar 9, 2024", "Jul 3, 2024"]
// 3d. TypeScript typing with map()
interface User {
  id: number;
  name: string;
  email: string;
}

interface UserDTO {
  id: number;
  displayName: string;
}

const usersData: User[] = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
];

// TypeScript infers the return type as UserDTO[]
const dtos: UserDTO[] = usersData.map((user): UserDTO => ({
  id: user.id,
  displayName: user.name.toUpperCase(),
}));

console.log(dtos);
// Output: [{ id: 1, displayName: 'ALICE' }, { id: 2, displayName: 'BOB' }]

4. filter() Basics — Truthy/Falsy and Immutability

Array.prototype.filter() creates a new array containing only the elements for which the callback returns a truthy value. It does not mutate the original array, and the returned array may be shorter (or even empty).

// Syntax:
// array.filter(callback(element, index, array), thisArg?)

// Basic: keep only even numbers
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = nums.filter(n => n % 2 === 0);
console.log(evens);
// Output: [2, 4, 6, 8, 10]

// The callback must return a truthy or falsy value
// These are FALSY in JavaScript: false, 0, '', null, undefined, NaN
const mixed = [0, 1, '', 'hello', null, undefined, false, true, NaN, 42];
const truthy = mixed.filter(Boolean);
console.log(truthy);
// Output: [1, "hello", true, 42]

// filter() does NOT mutate the original
const original = [10, 20, 30, 40, 50];
const filtered = original.filter(n => n > 25);
console.log(filtered);  // [30, 40, 50]
console.log(original);  // [10, 20, 30, 40, 50] — unchanged

// filter() can return an empty array
const empty = [1, 2, 3].filter(n => n > 100);
console.log(empty); // []

5. filter() Real-World Examples

Common patterns: removing nulls/undefined, searching arrays, filtering objects by property, and extracting unique values.

// 5a. Remove nulls and undefined (type-safe)
const data = ['Alice', null, 'Bob', undefined, 'Charlie', null];

const clean = data.filter((item): item is string => item != null);
console.log(clean);
// Output: ["Alice", "Bob", "Charlie"]

// Without type guard (still works at runtime)
const clean2 = data.filter(Boolean);
console.log(clean2);
// Output: ["Alice", "Bob", "Charlie"]
// 5b. Search / text matching
const fruits = ['Apple', 'Banana', 'Avocado', 'Blueberry', 'Apricot', 'Cherry'];

const startsWithA = fruits.filter(f => f.startsWith('A'));
console.log(startsWithA);
// Output: ["Apple", "Avocado", "Apricot"]

// Case-insensitive search
function search(items: string[], query: string): string[] {
  const q = query.toLowerCase();
  return items.filter(item => item.toLowerCase().includes(q));
}

console.log(search(fruits, 'berry'));
// Output: ["Blueberry"]

console.log(search(fruits, 'an'));
// Output: ["Banana"]
// 5c. Filter objects by property
const products = [
  { name: 'Laptop', price: 999, inStock: true },
  { name: 'Phone', price: 699, inStock: false },
  { name: 'Tablet', price: 499, inStock: true },
  { name: 'Watch', price: 299, inStock: true },
  { name: 'Headphones', price: 199, inStock: false },
];

// In stock and under $500
const affordable = products.filter(p => p.inStock && p.price < 500);
console.log(affordable);
// Output: [{ name: 'Tablet', price: 499, inStock: true }, { name: 'Watch', price: 299, inStock: true }]

// Multiple conditions with a predicate builder
function createFilter(minPrice: number, maxPrice: number, mustBeInStock: boolean) {
  return (p: typeof products[0]) =>
    p.price >= minPrice &&
    p.price <= maxPrice &&
    (!mustBeInStock || p.inStock);
}

const filtered = products.filter(createFilter(200, 700, true));
console.log(filtered.map(p => p.name));
// Output: ["Tablet", "Watch"]
// 5d. Extract unique values
const tags = ['js', 'ts', 'react', 'js', 'node', 'ts', 'react', 'vue'];

// Method 1: filter with indexOf
const unique1 = tags.filter((tag, index) => tags.indexOf(tag) === index);
console.log(unique1);
// Output: ["js", "ts", "react", "node", "vue"]

// Method 2: Set (preferred for performance)
const unique2 = [...new Set(tags)];
console.log(unique2);
// Output: ["js", "ts", "react", "node", "vue"]

// Unique objects by property
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice (duplicate)' },
  { id: 3, name: 'Charlie' },
];

const seen = new Set();
const uniqueUsers = users.filter(u => {
  if (seen.has(u.id)) return false;
  seen.add(u.id);
  return true;
});
console.log(uniqueUsers);
// Output: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]

6. reduce() Basics — Accumulator, CurrentValue, Initial Value

Array.prototype.reduce() executes a callback on each element, passing the return value from the previous call as the accumulator. The second argument to reduce() is the initial value — always provide it to avoid unexpected behavior with empty arrays.

// Syntax:
// array.reduce(callback(accumulator, currentValue, index, array), initialValue)

// Basic: sum of numbers
const nums = [10, 20, 30, 40, 50];
const sum = nums.reduce((acc, curr) => acc + curr, 0);
console.log(sum);
// Output: 150

// Step-by-step trace:
// Step 0: acc = 0 (initial),  curr = 10 → return 10
// Step 1: acc = 10,           curr = 20 → return 30
// Step 2: acc = 30,           curr = 30 → return 60
// Step 3: acc = 60,           curr = 40 → return 100
// Step 4: acc = 100,          curr = 50 → return 150

// ⚠️ Without initial value — risky!
const sum2 = [10, 20, 30].reduce((acc, curr) => acc + curr);
console.log(sum2); // 60 — works, but...

// Empty array WITHOUT initial value → TypeError!
try {
  [].reduce((acc, curr) => acc + curr);
} catch (e) {
  console.log(e.message);
  // "Reduce of empty array with no initial value"
}

// Empty array WITH initial value → safe
const sum3 = [].reduce((acc, curr) => acc + curr, 0);
console.log(sum3); // 0

7. reduce() Patterns — Sum, GroupBy, Flatten, Pipeline

reduce() is the most versatile array method. It can implement virtually any array operation: sum, average, groupBy, flatten, count occurrences, and even function composition pipelines.

// 7a. Sum and Average
const scores = [85, 92, 78, 96, 88];

const total = scores.reduce((acc, s) => acc + s, 0);
const average = total / scores.length;

console.log(`Total: ${total}, Average: ${average}`);
// Output: "Total: 439, Average: 87.8"

// One-liner average
const avg = scores.reduce((a, b, i, arr) => a + b / arr.length, 0);
console.log(avg); // 87.8
// 7b. groupBy — group objects by a key
const people = [
  { name: 'Alice', department: 'Engineering' },
  { name: 'Bob', department: 'Marketing' },
  { name: 'Charlie', department: 'Engineering' },
  { name: 'Diana', department: 'Marketing' },
  { name: 'Eve', department: 'Design' },
];

const grouped = people.reduce((acc, person) => {
  const key = person.department;
  if (!acc[key]) acc[key] = [];
  acc[key].push(person.name);
  return acc;
}, {} as Record<string, string[]>);

console.log(grouped);
// Output:
// {
//   Engineering: ['Alice', 'Charlie'],
//   Marketing: ['Bob', 'Diana'],
//   Design: ['Eve']
// }

// ES2024+ alternative: Object.groupBy()
// const grouped2 = Object.groupBy(people, p => p.department);
// 7c. Flatten nested arrays
const nested = [[1, 2], [3, 4], [5, [6, 7]]];

// Flatten one level
const flat1 = nested.reduce((acc, curr) => acc.concat(curr), []);
console.log(flat1);
// Output: [1, 2, 3, 4, 5, [6, 7]]

// Modern alternative: Array.flat()
const flat2 = nested.flat(1);
console.log(flat2);
// Output: [1, 2, 3, 4, 5, [6, 7]]

// Deep flatten with reduce (recursive)
function deepFlatten(arr) {
  return arr.reduce((acc, val) =>
    Array.isArray(val) ? acc.concat(deepFlatten(val)) : acc.concat(val),
    []
  );
}

console.log(deepFlatten([[1, [2, [3, [4]]]], [5]]));
// Output: [1, 2, 3, 4, 5]
// 7d. Count occurrences
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const counts = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {} as Record<string, number>);

console.log(counts);
// Output: { apple: 3, banana: 2, orange: 1 }

// Find the most common element
const mostCommon = Object.entries(counts)
  .reduce((max, [key, val]) => val > max[1] ? [key, val] : max, ['', 0]);

console.log(mostCommon);
// Output: ["apple", 3]
// 7e. Function composition pipeline
const pipeline = [
  (s: string) => s.trim(),
  (s: string) => s.toLowerCase(),
  (s: string) => s.replace(/\s+/g, '-'),
  (s: string) => s.replace(/[^a-z0-9-]/g, ''),
];

const slugify = (input: string) =>
  pipeline.reduce((acc, fn) => fn(acc), input);

console.log(slugify('  Hello World! @2024  '));
// Output: "hello-world-2024"

console.log(slugify('  JavaScript Map, Filter & Reduce  '));
// Output: "javascript-map-filter--reduce"

// Generic pipe function
function pipe<T>(...fns: ((arg: T) => T)[]): (arg: T) => T {
  return (input: T) => fns.reduce((acc, fn) => fn(acc), input);
}

const process = pipe(
  (n: number) => n * 2,
  (n: number) => n + 10,
  (n: number) => n / 3,
);

console.log(process(5));  // (5 * 2 + 10) / 3 = 6.666...
console.log(process(10)); // (10 * 2 + 10) / 3 = 10

8. Chaining map + filter + reduce — Data Processing Pipelines

The real power of functional array methods emerges when you chain them together. Each method in the chain receives the output of the previous one, creating a clean, readable data pipeline.

// Real-world example: process e-commerce order data
const orders = [
  { id: 1, customer: 'Alice', total: 250, status: 'completed', date: '2024-01-15' },
  { id: 2, customer: 'Bob', total: 120, status: 'cancelled', date: '2024-01-16' },
  { id: 3, customer: 'Alice', total: 350, status: 'completed', date: '2024-02-01' },
  { id: 4, customer: 'Charlie', total: 80, status: 'completed', date: '2024-02-10' },
  { id: 5, customer: 'Alice', total: 500, status: 'completed', date: '2024-03-05' },
  { id: 6, customer: 'Bob', total: 200, status: 'completed', date: '2024-03-15' },
  { id: 7, customer: 'Diana', total: 150, status: 'refunded', date: '2024-03-20' },
];

// Pipeline: completed orders → calculate revenue per customer → sort by revenue
const revenueByCustomer = orders
  .filter(order => order.status === 'completed')
  .map(order => ({ customer: order.customer, total: order.total }))
  .reduce((acc, { customer, total }) => {
    acc[customer] = (acc[customer] || 0) + total;
    return acc;
  }, {} as Record<string, number>);

console.log(revenueByCustomer);
// Output: { Alice: 1100, Charlie: 80, Bob: 200 }

// Convert to sorted array
const ranked = Object.entries(revenueByCustomer)
  .map(([customer, revenue]) => ({ customer, revenue }))
  .sort((a, b) => b.revenue - a.revenue);

console.log(ranked);
// Output:
// [
//   { customer: 'Alice', revenue: 1100 },
//   { customer: 'Bob', revenue: 200 },
//   { customer: 'Charlie', revenue: 80 },
// ]
// Another pipeline: process API response data
const apiResponse = [
  { id: 1, title: '  Hello World  ', tags: ['js', 'tutorial'], views: 1500 },
  { id: 2, title: '  Advanced TypeScript  ', tags: ['ts', 'advanced'], views: 800 },
  { id: 3, title: '  React Hooks Guide  ', tags: ['react', 'hooks'], views: 3200 },
  { id: 4, title: '  CSS Grid Tutorial  ', tags: ['css', 'layout'], views: 200 },
  { id: 5, title: '  Node.js Best Practices  ', tags: ['node', 'backend'], views: 2100 },
];

const result = apiResponse
  .filter(post => post.views >= 500)            // popular posts only
  .map(post => ({
    ...post,
    title: post.title.trim(),                    // clean up titles
    slug: post.title.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-'),
    isViral: post.views > 2000,
  }))
  .reduce((acc, post) => {
    const category = post.isViral ? 'viral' : 'popular';
    if (!acc[category]) acc[category] = [];
    acc[category].push(post.title);
    return acc;
  }, {} as Record<string, string[]>);

console.log(result);
// Output:
// {
//   popular: ["Hello World", "Advanced TypeScript"],
//   viral: ["React Hooks Guide", "Node.js Best Practices"]
// }

9. flatMap() — Map + Flatten in One Step

Array.prototype.flatMap() (ES2019) is equivalent to calling map() followed by flat(1). It is useful when your mapping function returns an array for each element and you want the results flattened into a single array.

// flatMap() = map() + flat(1)

// Split sentences into words
const sentences = ['Hello World', 'Foo Bar Baz', 'One Two'];
const words = sentences.flatMap(s => s.split(' '));
console.log(words);
// Output: ["Hello", "World", "Foo", "Bar", "Baz", "One", "Two"]

// Without flatMap (two steps):
const words2 = sentences.map(s => s.split(' ')).flat();
console.log(words2);
// Output: ["Hello", "World", "Foo", "Bar", "Baz", "One", "Two"]

// Use flatMap to conditionally expand or remove items
const nums = [1, 2, 3, 4, 5];

// Double even numbers, remove odd numbers
const result = nums.flatMap(n => n % 2 === 0 ? [n, n] : []);
console.log(result);
// Output: [2, 2, 4, 4]

// Expand ranges
const ranges = [[1, 3], [5, 7], [10, 12]];
const expanded = ranges.flatMap(([start, end]) => {
  const arr = [];
  for (let i = start; i <= end; i++) arr.push(i);
  return arr;
});
console.log(expanded);
// Output: [1, 2, 3, 5, 6, 7, 10, 11, 12]

// Extract all tags from posts
const posts = [
  { title: 'Post A', tags: ['js', 'react'] },
  { title: 'Post B', tags: ['ts', 'node'] },
  { title: 'Post C', tags: ['js', 'vue', 'css'] },
];

const allTags = posts.flatMap(p => p.tags);
console.log(allTags);
// Output: ["js", "react", "ts", "node", "js", "vue", "css"]

const uniqueTags = [...new Set(allTags)];
console.log(uniqueTags);
// Output: ["js", "react", "ts", "node", "vue", "css"]

10. for...of vs forEach vs map/filter/reduce

Each iteration approach has trade-offs in performance, readability, and capabilities. Here is a comprehensive comparison to help you choose the right tool.

// Comparison table:
// ┌─────────────────────┬───────────┬──────────┬───────────┬──────────────┐
// │ Feature             │ for...of  │ forEach  │ map       │ filter/reduce│
// ├─────────────────────┼───────────┼──────────┼───────────┼──────────────┤
// │ Returns value       │ No        │ No       │ Yes       │ Yes          │
// │ Chainable           │ No        │ No       │ Yes       │ Yes          │
// │ break/continue      │ Yes       │ No*      │ No        │ No           │
// │ async/await         │ Yes       │ No**     │ No**      │ No**         │
// │ Performance         │ Fastest   │ Fast     │ Fast      │ Fast         │
// │ Readability (intent)│ Low       │ Medium   │ High      │ High         │
// │ Mutates original    │ Can       │ Can      │ No        │ No           │
// └─────────────────────┴───────────┴──────────┴───────────┴──────────────┘
// * forEach: throwing is the only way to "break" — not recommended
// ** These work but require Promise.all() or special patterns

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 1. for...of — best when you need break/continue or async/await
let firstOver5: number | undefined;
for (const n of data) {
  if (n > 5) {
    firstOver5 = n;
    break; // early exit — impossible with map/filter/forEach
  }
}
console.log(firstOver5); // 6

// 2. forEach — for side effects only (logging, DOM updates)
data.forEach(n => console.log(n));
// Prints 1-10, returns undefined

// 3. map — when you want to TRANSFORM each element
const squares = data.map(n => n ** 2);
console.log(squares); // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

// 4. filter — when you want to SELECT elements
const bigNums = data.filter(n => n > 7);
console.log(bigNums); // [8, 9, 10]

// 5. reduce — when you want to AGGREGATE into a single value
const sum = data.reduce((a, b) => a + b, 0);
console.log(sum); // 55

// 6. find() — like filter but returns only the FIRST match
const found = data.find(n => n > 5);
console.log(found); // 6

// 7. some() / every() — boolean checks
console.log(data.some(n => n > 9));  // true
console.log(data.every(n => n > 0)); // true
// Performance benchmark (rough comparison)
const bigArray = Array.from({ length: 1_000_000 }, (_, i) => i);

console.time('for...of');
let sum1 = 0;
for (const n of bigArray) sum1 += n;
console.timeEnd('for...of');
// ~3-5ms

console.time('forEach');
let sum2 = 0;
bigArray.forEach(n => { sum2 += n; });
console.timeEnd('forEach');
// ~5-8ms

console.time('reduce');
const sum3 = bigArray.reduce((a, b) => a + b, 0);
console.timeEnd('reduce');
// ~5-8ms

// Takeaway: the performance difference is negligible for most applications.
// Choose based on readability and intent, not micro-optimization.

11. TypeScript with map/filter/reduce

TypeScript enhances these methods with type narrowing, generics, and type guards that catch errors at compile time instead of runtime.

// 11a. Type narrowing with filter()
// Problem: filter doesn't narrow types by default
const mixed: (string | number | null)[] = ['hello', 42, null, 'world', null, 7];

// ❌ TypeScript still thinks result is (string | number | null)[]
const noNulls = mixed.filter(x => x !== null);

// ✅ Use a type predicate (type guard) to narrow the type
const strings = mixed.filter((x): x is string => typeof x === 'string');
// TypeScript knows: strings is string[]
console.log(strings); // ["hello", "world"]

const numbers = mixed.filter((x): x is number => typeof x === 'number');
// TypeScript knows: numbers is number[]
console.log(numbers); // [42, 7]

// Reusable type guard
function isNotNull<T>(value: T | null | undefined): value is T {
  return value != null;
}

const cleaned = mixed.filter(isNotNull);
// TypeScript knows: cleaned is (string | number)[]
console.log(cleaned); // ["hello", 42, "world", 7]
// 11b. Generic reduce with proper types
interface CartItem {
  name: string;
  price: number;
  quantity: number;
}

const cart: CartItem[] = [
  { name: 'Laptop', price: 999, quantity: 1 },
  { name: 'Mouse', price: 29, quantity: 2 },
  { name: 'Cable', price: 15, quantity: 3 },
];

// TypeScript infers accumulator type from initial value
const totalPrice = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
console.log(totalPrice); // 1102

// Reduce to a different type — initial value determines type
interface Summary {
  itemCount: number;
  totalPrice: number;
  items: string[];
}

const summary = cart.reduce<Summary>(
  (acc, item) => ({
    itemCount: acc.itemCount + item.quantity,
    totalPrice: acc.totalPrice + item.price * item.quantity,
    items: [...acc.items, item.name],
  }),
  { itemCount: 0, totalPrice: 0, items: [] }
);

console.log(summary);
// Output: { itemCount: 6, totalPrice: 1102, items: ["Laptop", "Mouse", "Cable"] }
// 11c. Generic map utility with TypeScript
function mapBy<T, K extends keyof T>(arr: T[], key: K): T[K][] {
  return arr.map(item => item[key]);
}

interface Product {
  id: number;
  name: string;
  price: number;
}

const products: Product[] = [
  { id: 1, name: 'A', price: 10 },
  { id: 2, name: 'B', price: 20 },
];

const ids = mapBy(products, 'id');       // number[]
const names = mapBy(products, 'name');   // string[]
const prices = mapBy(products, 'price'); // number[]

console.log(ids);    // [1, 2]
console.log(names);  // ["A", "B"]
console.log(prices); // [10, 20]

// Generic groupBy with TypeScript
function groupBy<T>(arr: T[], keyFn: (item: T) => string): Record<string, T[]> {
  return arr.reduce<Record<string, T[]>>((acc, item) => {
    const key = keyFn(item);
    (acc[key] ??= []).push(item);
    return acc;
  }, {});
}

const byPrice = groupBy(products, p => p.price >= 15 ? 'expensive' : 'cheap');
console.log(byPrice);
// Output: { cheap: [{ id: 1, name: 'A', price: 10 }], expensive: [{ id: 2, name: 'B', price: 20 }] }

12. Common Mistakes and How to Fix Them

Even experienced developers fall into these traps. Here are the most common mistakes with map(), filter(), and reduce() and how to fix each one.

// Mistake 1: Forgetting to return in map() with curly braces
const nums = [1, 2, 3];

// ❌ WRONG: curly braces without return → returns undefined
const bad = nums.map(n => { n * 2 });
console.log(bad);
// Output: [undefined, undefined, undefined]

// ✅ FIX: add return statement
const good1 = nums.map(n => { return n * 2; });
console.log(good1); // [2, 4, 6]

// ✅ FIX: use concise arrow body (no curly braces)
const good2 = nums.map(n => n * 2);
console.log(good2); // [2, 4, 6]

// ⚠️ Returning an object literal? Wrap in parentheses!
// ❌ WRONG: JS thinks {} is a block
const bad2 = nums.map(n => { value: n });
// Output: [undefined, undefined, undefined]

// ✅ FIX: parentheses around the object
const good3 = nums.map(n => ({ value: n }));
console.log(good3); // [{ value: 1 }, { value: 2 }, { value: 3 }]
// Mistake 2: Missing initial value in reduce()

// ❌ Works but dangerous with empty arrays
const sum = [1, 2, 3].reduce((a, b) => a + b); // 6 — OK
// [].reduce((a, b) => a + b); // TypeError!

// ❌ Wrong type: accumulator starts as first element (number), not object
const items = [{ price: 10 }, { price: 20 }];
// items.reduce((acc, item) => acc + item.price); // NaN!
// Because acc starts as { price: 10 }, and { price: 10 } + 20 = NaN

// ✅ FIX: always provide initial value
const total = items.reduce((acc, item) => acc + item.price, 0);
console.log(total); // 30
// Mistake 3: Accidentally mutating objects inside map/filter

const users = [
  { name: 'Alice', score: 85 },
  { name: 'Bob', score: 92 },
];

// ❌ WRONG: mutating the original objects!
const bad = users.map(user => {
  user.grade = user.score >= 90 ? 'A' : 'B';  // mutates original!
  return user;
});
console.log(users[0]); // { name: 'Alice', score: 85, grade: 'B' } — MUTATED!

// ✅ FIX: create new objects with spread
const users2 = [
  { name: 'Alice', score: 85 },
  { name: 'Bob', score: 92 },
];
const good = users2.map(user => ({
  ...user,
  grade: user.score >= 90 ? 'A' : 'B',
}));
console.log(users2[0]); // { name: 'Alice', score: 85 } — unchanged
console.log(good[0]);   // { name: 'Alice', score: 85, grade: 'B' }
// Mistake 4: Using map() when you don't need the result

// ❌ WRONG: using map just for side effects, wasting memory
const data = [1, 2, 3, 4, 5];
data.map(n => console.log(n)); // creates an unused array of undefineds

// ✅ FIX: use forEach for side effects
data.forEach(n => console.log(n));

// Mistake 5: Chaining filter().map() when you can use reduce()
const nums2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// ❌ Creates an intermediate array (wastes memory for large arrays)
const result1 = nums2
  .filter(n => n % 2 === 0)
  .map(n => n ** 2);

// ✅ Single pass with reduce (better for very large arrays)
const result2 = nums2.reduce<number[]>((acc, n) => {
  if (n % 2 === 0) acc.push(n ** 2);
  return acc;
}, []);

console.log(result1); // [4, 16, 36, 64, 100]
console.log(result2); // [4, 16, 36, 64, 100]
// Note: filter+map is more readable; use reduce only if performance matters

13. Frequently Asked Questions

What is the difference between map() and forEach() in JavaScript?

map() returns a new array with the transformed elements, while forEach() returns undefined and is used only for side effects. Use map() when you need the result array; use forEach() when you only need to perform actions (like logging or DOM updates) without collecting results. map() is chainable; forEach() is not.

When should I use reduce() instead of a for loop?

Use reduce() when you need to aggregate an array into a single value (sum, object, string, etc.) and the logic is concise. For complex multi-step logic with early exits, side effects, or async operations, a for loop may be clearer. reduce() shines for patterns like groupBy, counting, flattening, and function composition.

Does filter() modify the original array?

No. filter() always returns a new array and leaves the original array unchanged. However, if the array contains objects, the new array holds references to the same objects — so mutating an object inside the filtered array will also affect the original. To avoid this, combine filter() with map() and spread/Object.assign to create shallow copies.

What happens if I forget the initial value in reduce()?

If you omit the initial value, reduce() uses the first element as the initial accumulator and starts iteration from the second element. This works for simple numeric sums but causes a TypeError on empty arrays and produces wrong results when the accumulator type differs from the element type (e.g., reducing to an object). Always provide an initial value.

Can I use map(), filter(), and reduce() with async/await?

map() with async callbacks returns an array of Promises — use Promise.all(arr.map(async fn)) to await all results. filter() does not work directly with async callbacks because a Promise is always truthy; instead, map to get boolean results first, then filter. reduce() can chain async operations using await on the accumulator: arr.reduce(async (accP, item) => { const acc = await accP; ... }, Promise.resolve(initial)).

Master these three methods and you will write cleaner, more maintainable JavaScript. For quick data inspection, try our tools below.

Try the JSON Formatter →

Try the JS/HTML Formatter →

𝕏 Twitterin LinkedIn
この記事は役に立ちましたか?

最新情報を受け取る

毎週の開発ヒントと新ツール情報。

スパムなし。いつでも解除可能。

Try These Related Tools

{ }JSON FormatterJSJS/HTML Formatter.*Regex Tester

Related Articles

JavaScript 配列メソッド チートシート

JavaScript配列メソッドの完全リファレンス:map、filter、reduce、find、some、every、flat、splice、sliceなど実例付き。

JavaScript文字列置換とRegex:replaceAll、キャプチャグループ&例

JavaScriptのRegexを使った文字列置換をマスター。replace vs replaceAll、グローバルフラグ、キャプチャグループ、実践例。