Ad – 728Γ—90
βš™οΈ Functions

JavaScript Higher-Order Functions – map, filter, reduce and Beyond

Higher-order functions are functions that accept other functions as arguments or return functions as output. They are the backbone of functional JavaScript programming and power the elegant array transformations you use every day.

⏱️ 24 min read 🎯 Beginner πŸ“… Updated 2026

What Is a Higher-Order Function?

A higher-order function (HOF) is a function that either:

  • Takes one or more functions as arguments (callbacks), or
  • Returns a function as its result, or
  • Both
JavaScript
// HOF: takes a function as argument
function applyToArray(arr, fn) {
  return arr.map(fn);
}

const doubled = applyToArray([1, 2, 3, 4], n => n * 2);
console.log(doubled); // [2, 4, 6, 8]

// HOF: returns a function
function createMultiplier(factor) {
  return n => n * factor; // Returns a function
}

const times5 = createMultiplier(5);
console.log(times5(6)); // 30
β–Ά Output
[2, 4, 6, 8] 30

Array.map()

map() transforms every element of an array and returns a new array of the same length. The original array is not mutated.

JavaScript
const prices = [10, 25, 50, 100];

// Apply tax
const pricesWithTax = prices.map(p => p * 1.2);
console.log(pricesWithTax); // [12, 30, 60, 120]

// Extract property from objects
const users = [
  { id: 1, name: "Alice", age: 30 },
  { id: 2, name: "Bob",   age: 25 },
  { id: 3, name: "Carol", age: 35 }
];

const names = users.map(u => u.name);
console.log(names); // ["Alice", "Bob", "Carol"]

// Map with index
const indexed = users.map((u, i) => `${i + 1}. ${u.name}`);
console.log(indexed); // ["1. Alice", "2. Bob", "3. Carol"]

// Transform to a different shape
const userCards = users.map(({ id, name }) => ({
  key: id,
  display: name.toUpperCase()
}));
console.log(userCards);
β–Ά Output
[12, 30, 60, 120] [ 'Alice', 'Bob', 'Carol' ] [ '1. Alice', '2. Bob', '3. Carol' ] [ { key: 1, display: 'ALICE' }, { key: 2, display: 'BOB' }, { key: 3, display: 'CAROL' } ]

Array.filter()

filter() keeps only elements for which the callback returns truthy. Returns a new (potentially shorter) array.

JavaScript
const products = [
  { name: "Laptop", price: 999, inStock: true },
  { name: "Mouse",  price: 29,  inStock: false },
  { name: "Desk",   price: 350, inStock: true },
  { name: "Chair",  price: 150, inStock: true },
  { name: "Monitor",price: 400, inStock: false }
];

// In-stock products under $400
const affordable = products
  .filter(p => p.inStock)
  .filter(p => p.price < 400);

console.log(affordable.map(p => p.name)); // ["Chair"]

// Multiple conditions in one filter
const available = products.filter(p => p.inStock && p.price <= 999);
console.log(available.map(p => p.name));  // ["Laptop", "Desk", "Chair"]

// Remove falsy values
const mixed = [0, 1, "", "hello", null, "world", undefined, 42];
const truthy = mixed.filter(Boolean);
console.log(truthy); // [1, "hello", "world", 42]
β–Ά Output
[ 'Chair' ] [ 'Laptop', 'Desk', 'Chair' ] [ 1, 'hello', 'world', 42 ]

Array.reduce()

reduce() accumulates array elements into a single value. It is the most powerful array method β€” map and filter can be implemented with reduce.

JavaScript
const orders = [
  { product: "Book",   qty: 3, price: 15 },
  { product: "Pen",    qty: 10, price: 2 },
  { product: "Bag",    qty: 1,  price: 45 }
];

// Sum total
const total = orders.reduce((acc, order) => acc + order.qty * order.price, 0);
console.log("Total: $" + total); // Total: $110

// Group by (transform array into object)
const inventory = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const counts = inventory.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
console.log(counts); // { apple: 3, banana: 2, cherry: 1 }

// Flatten nested array
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5, 6]
β–Ά Output
Total: $110 { apple: 3, banana: 2, cherry: 1 } [ 1, 2, 3, 4, 5, 6 ]

Chaining map, filter, reduce

JavaScript
const employees = [
  { name: "Alice",  department: "Engineering", salary: 90000 },
  { name: "Bob",    department: "Marketing",   salary: 75000 },
  { name: "Carol",  department: "Engineering", salary: 95000 },
  { name: "Dave",   department: "Engineering", salary: 80000 },
  { name: "Eve",    department: "Marketing",   salary: 70000 }
];

// Total salary for Engineering department
const engTotal = employees
  .filter(e => e.department === "Engineering")
  .map(e => e.salary)
  .reduce((sum, s) => sum + s, 0);

console.log("Engineering total salary: $" + engTotal); // $265,000

// Average salary across all departments
const avg = employees
  .map(e => e.salary)
  .reduce((sum, s, _, arr) => sum + s / arr.length, 0);

console.log("Average salary: $" + avg.toFixed(0)); // $82,000
β–Ά Output
Engineering total salary: $265000 Average salary: $82000

forEach vs map

FeatureforEachmap
ReturnsundefinedNew array
PurposeSide effects (logging, DOM updates)Transforming values
ChainableNoYes
Mutates originalCan (you control it)No
Can break/continueNoNo

find() and findIndex()

JavaScript
const users = [
  { id: 1, name: "Alice", active: true },
  { id: 2, name: "Bob",   active: false },
  { id: 3, name: "Carol", active: true }
];

// find β€” returns the first matching element (or undefined)
const bob = users.find(u => u.id === 2);
console.log(bob); // { id: 2, name: 'Bob', active: false }

// findIndex β€” returns the index of the first match (-1 if not found)
const carolIdx = users.findIndex(u => u.name === "Carol");
console.log(carolIdx); // 2

// Practical: find and update
const idx = users.findIndex(u => u.id === 2);
if (idx !== -1) {
  users[idx] = { ...users[idx], active: true }; // Immutable update
}
console.log(users[1].active); // true
β–Ά Output
{ id: 2, name: 'Bob', active: false } 2 true

some() and every()

JavaScript
const scores = [78, 92, 55, 88, 45, 97];

// some β€” true if at least one element passes
const hasFailure = scores.some(s => s < 60);
console.log("Has failure:", hasFailure); // true

const hasPerfect = scores.some(s => s === 100);
console.log("Has perfect:", hasPerfect); // false

// every β€” true only if ALL elements pass
const allPassed = scores.every(s => s >= 40);
console.log("All passed (>=40):", allPassed); // true

const allExcellent = scores.every(s => s >= 90);
console.log("All excellent:", allExcellent); // false
β–Ά Output
Has failure: true Has perfect: false All passed (>=40): true All excellent: false

Custom Higher-Order Functions

JavaScript
// repeat β€” run a function n times
function repeat(n, fn) {
  for (let i = 0; i < n; i++) fn(i);
}

repeat(3, i => console.log("Iteration " + i));

// once β€” a function that can only be called once
function once(fn) {
  let called = false;
  let result;
  return function(...args) {
    if (!called) {
      called = true;
      result = fn(...args);
    }
    return result;
  };
}

const initOnce = once(() => {
  console.log("Initialized!");
  return 42;
});

console.log(initOnce()); // Initialized! then 42
console.log(initOnce()); // 42 β€” no "Initialized!" again
β–Ά Output
Iteration 0 Iteration 1 Iteration 2 Initialized! 42 42

Compose and Pipe Pattern

compose chains functions right-to-left; pipe chains them left-to-right (more intuitive).

JavaScript
// pipe: applies functions left to right
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);

// compose: applies functions right to left
const compose = (...fns) => (value) => fns.reduceRight((acc, fn) => fn(acc), value);

// Individual transformations
const trim       = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const removeSpaces = str => str.replace(/\s+/g, "-");

// Pipe: left to right (trim β†’ lowercase β†’ replace spaces)
const slugify = pipe(trim, toLowerCase, removeSpaces);

console.log(slugify("  Hello World  "));       // "hello-world"
console.log(slugify(" Learn JavaScript! "));   // "learn-javascript!"

// Numeric pipeline
const processNumber = pipe(
  n => n * 2,      // double
  n => n + 10,     // add 10
  n => n ** 2      // square
);

console.log(processNumber(3)); // (3*2 + 10)^2 = 16^2 = 256
β–Ά Output
hello-world learn-javascript! 256
Ad – 336Γ—280

πŸ‹οΈ Practical Exercise

Given this dataset of transactions:

JavaScript
const transactions = [
  { id: 1, type: "credit", amount: 500 },
  { id: 2, type: "debit",  amount: 120 },
  { id: 3, type: "credit", amount: 1000 },
  { id: 4, type: "debit",  amount: 350 },
  { id: 5, type: "credit", amount: 200 }
];

Use map, filter, and reduce to find:

  1. Total credits
  2. Total debits
  3. Net balance (credits βˆ’ debits)
  4. List of credit transaction IDs

πŸ”₯ Challenge Exercise

Implement these higher-order functions from scratch (without using the built-in versions):

  1. myMap(arr, fn)
  2. myFilter(arr, fn)
  3. myReduce(arr, fn, initial)

Then create a groupBy(arr, keyFn) function that groups array elements into an object by the result of keyFn. Use reduce internally.

πŸ“‹ Summary

  • Higher-order functions accept functions as arguments or return functions.
  • map() transforms every element β€” returns same-length array.
  • filter() keeps matching elements β€” returns shorter array.
  • reduce() accumulates to one value β€” most powerful of the three.
  • Chain map/filter/reduce for readable data pipelines.
  • some()/every() check array-level boolean conditions efficiently.
  • find()/findIndex() locate the first matching element.
  • Compose and pipe let you build transformation pipelines from simple functions.

Interview Questions

  • What is a higher-order function? Give two real examples.
  • Explain the difference between map() and forEach().
  • How does Array.reduce() work? What is the accumulator?
  • When would you use find() vs filter()?
  • What is the compose/pipe pattern and why is it useful?

Frequently Asked Questions

Does map() mutate the original array?+

No. map(), filter(), and reduce() do not mutate the original array β€” they return new arrays (or a new value). However, if the callback mutates objects inside the array, those objects are shared by reference and can be modified.

What happens if reduce() is called on an empty array with no initial value?+

It throws a TypeError: Reduce of empty array with no initial value. Always provide an initial value (the second argument to reduce) when the array might be empty.

Is reduce() really more powerful than map and filter?+

Yes β€” both map and filter can be implemented using reduce. reduce is essentially a general-purpose array-to-anything function. However, using map and filter when applicable is more readable and semantically clear.