Higher-Order Functions: Complete Guide for Developers
Master JavaScript higher-order functions: callbacks, functional programming, map, filter, reduce, composition, and practical examples with detailed explanations.
Higher-Order Functions: Complete Guide for Developers
π Table of Contents
- What are Higher-Order Functions?
- Why Do We Need Higher-Order Functions?
- Real-World Use Cases
- Syntax
- Internal Working
- Memory Diagram
- Data Flow Diagram
- Code Examples
- Common Mistakes
- Performance Considerations
- Interview Questions
- Cheat Sheet
- Summary
1. What are Higher-Order Functions?
A Higher-Order Function (HOF) is a function that either accepts functions as arguments or returns a function as its result.
Simple Definition
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HIGHER-ORDER FUNCTION DEFINITION β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
A Higher-Order Function is a function that:
1. Takes one or more functions as arguments
OR
2. Returns a function as its result
OR
3. Both
Key Concept:
ββββββββββββ
Functions that operate on other functions
Visual Representation
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HOF TYPES β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Type 1: Function as Argument
βββββββββββββββββββββββββββββ
ββββββββββββββββββββββββ
β Higher-Order Functionβ
β ββββββββββββββββββββ β
β β Callback Functionβ β
β ββββββββββββββββββββ β
ββββββββββββββββββββββββ
Type 2: Function as Return Value
βββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββ
β Higher-Order Functionβ
β β β
β ββββββββββββββββββββ β
β β Returned Functionβ β
β ββββββββββββββββββββ β
ββββββββββββββββββββββββ
Type 3: Both
ββββββββββββ
ββββββββββββββββββββββββ
β Higher-Order Functionβ
β ββββββββββββββββββββ β
β β Callback Functionβ β
β ββββββββββββββββββββ β
β β β
β ββββββββββββββββββββ β
β β Returned Functionβ β
β ββββββββββββββββββββ β
ββββββββββββββββββββββββ
First-Class Functions
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FUNCTIONS AS FIRST-CLASS CITIZENS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
In JavaScript, functions are first-class citizens, meaning:
β Assigned to variables
β Passed as arguments
β Returned from functions
β Stored in data structures
β Have properties and methods
This enables Higher-Order Functions!
Example:
ββββββββ
const greet = function() { // Assigned to variable
return "Hello";
};
const functions = [greet]; // Stored in array
function execute(fn) { // Passed as argument
return fn();
}
function create() { // Returned from function
return function() {
return "Created";
};
}
2. Why Do We Need Higher-Order Functions?
Higher-Order Functions enable powerful programming patterns and cleaner code.
Problem Without HOFs
// Without HOFs - repetitive code
const numbers = [1, 2, 3, 4, 5];
// Double all numbers
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
// Triple all numbers
const tripled = [];
for (let i = 0; i < numbers.length; i++) {
tripled.push(numbers[i] * 3);
}
// Square all numbers
const squared = [];
for (let i = 0; i < numbers.length; i++) {
squared.push(numbers[i] * numbers[i]);
}
Solution With HOFs
// With HOFs - reusable and clean
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const tripled = numbers.map(n => n * 3);
const squared = numbers.map(n => n * n);
// Or create a reusable transformer
function transform(arr, operation) {
return arr.map(operation);
}
const doubled = transform(numbers, n => n * 2);
const tripled = transform(numbers, n => n * 3);
const squared = transform(numbers, n => n * n);
Benefits
- Code Reusability: Write once, use many times
- Abstraction: Hide implementation details
- Composition: Combine simple functions into complex ones
- Declarative Code: Focus on what, not how
- Functional Programming: Enable FP paradigm
- Cleaner Code: Less boilerplate, more readable
- Testability: Easier to test pure functions
- Maintainability: Easier to understand and modify
3. Real-World Use Cases
1. Data Transformation Pipeline
// E-commerce order processing
const orders = [
{ id: 1, items: [{ price: 10 }, { price: 20 }], status: 'pending' },
{ id: 2, items: [{ price: 15 }], status: 'completed' },
{ id: 3, items: [{ price: 30 }, { price: 40 }], status: 'completed' }
];
// Process orders using HOFs
const completedOrderTotals = orders
.filter(order => order.status === 'completed')
.map(order => ({
id: order.id,
total: order.items.reduce((sum, item) => sum + item.price, 0)
}))
.filter(order => order.total > 20);
console.log(completedOrderTotals);
// [{ id: 3, total: 70 }]
2. Event Handling
// DOM event handling with HOFs
function createClickHandler(message) {
return function(event) {
console.log(message, event.target);
};
}
const buttons = document.querySelectorAll('.btn');
buttons.forEach((button, index) => {
button.addEventListener('click', createClickHandler(`Button ${index} clicked`));
});
3. API Request Handler
// Reusable API request handler
function createApiHandler(baseUrl) {
return function(endpoint) {
return function(options = {}) {
return fetch(`${baseUrl}${endpoint}`, options)
.then(response => response.json());
};
};
}
const apiHandler = createApiHandler('https://api.example.com');
const getUsers = apiHandler('/users');
const getPosts = apiHandler('/posts');
// Usage
getUsers().then(users => console.log(users));
getPosts().then(posts => console.log(posts));
4. Validation Pipeline
// Form validation using HOFs
const validators = {
required: (message) => (value) =>
value ? null : message,
minLength: (min, message) => (value) =>
value.length >= min ? null : message,
email: (message) => (value) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : message
};
function validate(value, ...validatorFns) {
for (const validator of validatorFns) {
const error = validator(value);
if (error) return error;
}
return null;
}
// Usage
const emailError = validate(
'[email protected]',
validators.required('Email is required'),
validators.email('Invalid email format')
);
console.log(emailError); // null (valid)
5. Memoization
// Performance optimization with HOF
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit!');
return cache.get(key);
}
console.log('Computing...');
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// Expensive calculation
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(40)); // Computing... (slow)
console.log(memoizedFib(40)); // Cache hit! (instant)
4. Syntax
Type 1: Function Accepting Function
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// FUNCTION AS ARGUMENT (CALLBACK)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Basic callback
function execute(callback) {
console.log("Before callback");
callback();
console.log("After callback");
}
execute(() => console.log("Inside callback"));
// Callback with parameters
function process(data, transformer) {
return transformer(data);
}
const result = process(5, x => x * 2);
console.log(result); // 10
// Multiple callbacks
function chain(value, ...operations) {
return operations.reduce((acc, operation) => operation(acc), value);
}
const result = chain(
5,
x => x * 2, // 10
x => x + 3, // 13
x => x / 2 // 6.5
);
Type 2: Function Returning Function
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// FUNCTION RETURNING FUNCTION
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Basic return
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Currying
function add(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(add(1)(2)(3)); // 6
// Arrow function syntax
const multiply = a => b => c => a * b * c;
console.log(multiply(2)(3)(4)); // 24
Built-in Array HOFs
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// BUILT-IN HIGHER-ORDER FUNCTIONS
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const numbers = [1, 2, 3, 4, 5];
// map() - Transform each element
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10]
// filter() - Select elements
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4]
// reduce() - Aggregate to single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 15
// forEach() - Execute for each element
numbers.forEach(n => console.log(n));
// find() - Find first match
const found = numbers.find(n => n > 3);
// 4
// findIndex() - Find index of first match
const index = numbers.findIndex(n => n > 3);
// 3
// some() - Check if any match
const hasEven = numbers.some(n => n % 2 === 0);
// true
// every() - Check if all match
const allPositive = numbers.every(n => n > 0);
// true
// sort() - Sort with comparator
const sorted = numbers.sort((a, b) => b - a);
// [5, 4, 3, 2, 1]
5. Internal Working
Callback Execution Flow
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CALLBACK EXECUTION MECHANISM β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Code:
βββββ
function execute(callback) {
console.log("Start");
callback();
console.log("End");
}
execute(() => console.log("Callback"));
Execution Steps:
ββββββββββββββββ
1. execute() called
β
2. "Start" logged
β
3. callback() invoked
β
4. "Callback" logged
β
5. callback() returns
β
6. "End" logged
β
7. execute() returns
Call Stack:
βββββββββββ
βββββββββββββββββββ
β callback() β β Step 3-5
βββββββββββββββββββ€
β execute() β β Step 1-7
βββββββββββββββββββ€
β Global β
βββββββββββββββββββ
Function Return Mechanism
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FUNCTION RETURN MECHANISM β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Code:
βββββ
function createMultiplier(x) {
return function(y) {
return x * y;
};
}
const double = createMultiplier(2);
const result = double(5);
Execution Steps:
ββββββββββββββββ
1. createMultiplier(2) called
β
2. Inner function created
β
3. Inner function captures 'x' (closure)
β
4. Inner function returned
β
5. Assigned to 'double'
β
6. double(5) called
β
7. Accesses captured 'x' (2)
β
8. Returns 2 * 5 = 10
Closure Formation:
ββββββββββββββββββ
ββββββββββββββββββββββββββββββββ
β double (returned function) β
β ββ Code: return x * y β
β ββ [[Scope]]: β
β ββ x: 2 β Captured! β
ββββββββββββββββββββββββββββββββ
6. Memory Diagram
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HOF MEMORY LAYOUT β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Code:
βββββ
function createCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter1 = createCounter();
const counter2 = createCounter();
Memory Structure:
βββββββββββββββββ
Global Memory:
βββββββββββββββββββββββββββββββββββββββ
β createCounter: [Function] β
β counter1: [Object] β
β counter2: [Object] β
βββββββββββββββββββββββββββββββββββββββ
counter1 Closure:
βββββββββββββββββββββββββββββββββββββββ
β Object { β
β increment: [Function] β
β decrement: [Function] β
β getCount: [Function] β
β } β
β [[Scope]]: β
β ββ count: 0 β 1 β 2 β ... β
βββββββββββββββββββββββββββββββββββββββ
counter2 Closure:
βββββββββββββββββββββββββββββββββββββββ
β Object { β
β increment: [Function] β
β decrement: [Function] β
β getCount: [Function] β
β } β
β [[Scope]]: β
β ββ count: 0 β 1 β 2 β ... β
βββββββββββββββββββββββββββββββββββββββ
Each counter has its own 'count' variable!
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ARRAY METHOD MEMORY β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Code:
βββββ
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
Memory During map():
ββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββ
β numbers: [1, 2, 3] β
β doubled: [] β Being built β
β β
β map() Iteration 1: β
β ββ n: 1 β
β ββ result: 2 β
β ββ doubled: [2] β
β β
β map() Iteration 2: β
β ββ n: 2 β
β ββ result: 4 β
β ββ doubled: [2, 4] β
β β
β map() Iteration 3: β
β ββ n: 3 β
β ββ result: 6 β
β ββ doubled: [2, 4, 6] β
βββββββββββββββββββββββββββββββββββββββ
7. Data Flow Diagram
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MAP FLOW β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Array [1, 2, 3]
β
ββββββββββββββββ
β map() β
ββββββββββββββββ
β
For each element:
β
ββββββββββββββββ
β Call callbackβ
β with element β
ββββββββββββββββ
β
ββββββββββββββββ
β Get result β
ββββββββββββββββ
β
ββββββββββββββββ
β Add to new β
β array β
ββββββββββββββββ
β
New Array [2, 4, 6]
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FILTER FLOW β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Array [1, 2, 3, 4, 5]
β
ββββββββββββββββ
β filter() β
ββββββββββββββββ
β
For each element:
β
ββββββββββββββββ
β Call callbackβ
β with element β
ββββββββββββββββ
β
ββββββββββββββββ
β Check result β
ββββββββββββββββ
β
ββββββ΄βββββ
β true? β
ββββββ¬βββββ
Yes β No
β β
Include Skip
β
New Array [2, 4]
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REDUCE FLOW β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Array [1, 2, 3, 4]
Initial: 0
β
ββββββββββββββββ
β reduce() β
ββββββββββββββββ
β
Iteration 1:
acc: 0, curr: 1
result: 1
β
Iteration 2:
acc: 1, curr: 2
result: 3
β
Iteration 3:
acc: 3, curr: 3
result: 6
β
Iteration 4:
acc: 6, curr: 4
result: 10
β
Final Result: 10
8. Code Examples
Example 1: Custom map Implementation
// Understanding map() by implementing it
function customMap(array, callback) {
const result = [];
for (let i = 0; i < array.length; i++) {
result.push(callback(array[i], i, array));
}
return result;
}
const numbers = [1, 2, 3, 4, 5];
const doubled = customMap(numbers, n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// With index
const withIndex = customMap(numbers, (n, i) => `${i}: ${n}`);
console.log(withIndex); // ["0: 1", "1: 2", "2: 3", "3: 4", "4: 5"]
Example 2: Function Composition
// Composing multiple functions
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
// Helper functions
const add5 = x => x + 5;
const multiply3 = x => x * 3;
const subtract2 = x => x - 2;
// Compose them
const calculate = compose(
subtract2, // Applied last
multiply3, // Applied second
add5 // Applied first
);
console.log(calculate(10)); // (10 + 5) * 3 - 2 = 43
// Pipe (left to right)
function pipe(...fns) {
return function(value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}
const calculate2 = pipe(
add5, // Applied first
multiply3, // Applied second
subtract2 // Applied last
);
console.log(calculate2(10)); // (10 + 5) * 3 - 2 = 43
Example 3: Partial Application
// Partial application with HOF
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}
// Original function
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`;
}
// Create specialized versions
const sayHello = partial(greet, "Hello");
const sayHelloJohn = partial(greet, "Hello", "John");
console.log(sayHello("Alice", "!")); // "Hello, Alice!"
console.log(sayHelloJohn("!")); // "Hello, John!"
// Practical example: API calls
function apiCall(method, endpoint, data) {
return fetch(endpoint, {
method,
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
});
}
const postRequest = partial(apiCall, 'POST');
const getRequest = partial(apiCall, 'GET');
// Usage
postRequest('/api/users', { name: 'John' });
getRequest('/api/users', null);
Example 4: Debounce and Throttle
// Debounce - Execute after delay
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn(...args);
}, delay);
};
}
// Usage: Search input
const search = debounce((query) => {
console.log('Searching for:', query);
// API call here
}, 300);
// Throttle - Execute at most once per interval
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn(...args);
}
};
}
// Usage: Scroll event
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', handleScroll);
Example 5: Complex Data Processing
// Real-world data processing pipeline
const users = [
{ id: 1, name: 'John', age: 25, active: true, orders: 5 },
{ id: 2, name: 'Jane', age: 30, active: false, orders: 3 },
{ id: 3, name: 'Bob', age: 35, active: true, orders: 8 },
{ id: 4, name: 'Alice', age: 28, active: true, orders: 12 }
];
// Complex processing with HOFs
const result = users
.filter(user => user.active) // Active users only
.filter(user => user.orders > 5) // High-value customers
.map(user => ({ // Transform data
...user,
category: user.orders > 10 ? 'VIP' : 'Regular'
}))
.sort((a, b) => b.orders - a.orders) // Sort by orders
.map(user => user.name); // Extract names
console.log(result); // ["Alice", "Bob"]
// Alternative: Using reduce for everything
const result2 = users.reduce((acc, user) => {
if (user.active && user.orders > 5) {
acc.push({
...user,
category: user.orders > 10 ? 'VIP' : 'Regular'
});
}
return acc;
}, [])
.sort((a, b) => b.orders - a.orders)
.map(user => user.name);
console.log(result2); // ["Alice", "Bob"]
9. Common Mistakes
Mistake 1: Not Returning in map/filter
// β Wrong: Forgetting to return
const numbers = [1, 2, 3];
const doubled = numbers.map(n => {
n * 2; // Missing return!
});
console.log(doubled); // [undefined, undefined, undefined]
// β
Correct: Return the value
const doubled = numbers.map(n => {
return n * 2;
});
// Or use implicit return
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6]
Mistake 2: Mutating Original Array
// β Wrong: Mutating original data
const users = [{ name: 'John', age: 25 }];
const updated = users.map(user => {
user.age = 26; // Mutates original!
return user;
});
// β
Correct: Create new objects
const updated = users.map(user => ({
...user,
age: 26
}));
Mistake 3: Overusing Chaining
// β Wrong: Too many operations, hard to read
const result = data
.map(x => x * 2)
.filter(x => x > 10)
.map(x => x + 5)
.filter(x => x < 50)
.map(x => x / 2)
.filter(x => x % 2 === 0);
// β
Correct: Combine operations or use reduce
const result = data.reduce((acc, x) => {
const doubled = x * 2;
if (doubled > 10) {
const added = doubled + 5;
if (added < 50) {
const halved = added / 2;
if (halved % 2 === 0) {
acc.push(halved);
}
}
}
return acc;
}, []);
Mistake 4: Incorrect reduce Initial Value
// β Wrong: Missing initial value
const numbers = [1, 2, 3];
const sum = numbers.reduce((acc, n) => acc + n);
// Works, but dangerous if array is empty
// β Wrong: Wrong initial value type
const objects = [{ value: 1 }, { value: 2 }];
const sum = objects.reduce((acc, obj) => acc + obj.value);
// NaN - acc starts as object, not number
// β
Correct: Always provide initial value
const sum = numbers.reduce((acc, n) => acc + n, 0);
const sum2 = objects.reduce((acc, obj) => acc + obj.value, 0);
Mistake 5: Confusing forEach with map
// β Wrong: Using forEach expecting return value
const numbers = [1, 2, 3];
const doubled = numbers.forEach(n => n * 2);
console.log(doubled); // undefined
// β
Correct: Use map for transformation
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6]
// forEach is for side effects only
numbers.forEach(n => console.log(n));
10. Performance Considerations
1. Avoid Multiple Iterations
// β Slow: Multiple iterations
const numbers = [1, 2, 3, 4, 5];
const result = numbers
.map(n => n * 2) // Iteration 1
.filter(n => n > 5) // Iteration 2
.map(n => n + 1); // Iteration 3
// β
Fast: Single iteration with reduce
const result = numbers.reduce((acc, n) => {
const doubled = n * 2;
if (doubled > 5) {
acc.push(doubled + 1);
}
return acc;
}, []);
2. Use for Loop for Performance-Critical Code
// β Slower: HOF overhead
const sum = numbers.reduce((acc, n) => acc + n, 0);
// β
Faster: Traditional loop
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
3. Avoid Creating Functions in Loops
// β Slow: Creates new function each iteration
array.forEach((item, index) => {
setTimeout(() => console.log(item), index * 1000);
});
// β
Fast: Reuse function
function logItem(item) {
console.log(item);
}
array.forEach((item, index) => {
setTimeout(logItem.bind(null, item), index * 1000);
});
11. Interview Questions
Q1: What is a higher-order function?
Answer: A higher-order function is a function that either takes one or more functions as arguments, returns a function, or both. They enable functional programming patterns and code reusability.
// Takes function as argument
function execute(callback) {
return callback();
}
// Returns function
function createMultiplier(x) {
return function(y) {
return x * y;
};
}
// Both
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
Q2: What's the difference between map() and forEach()?
Answer:
- map(): Returns a new array with transformed elements
- forEach(): Returns undefined, used for side effects only
const numbers = [1, 2, 3];
// map returns new array
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6]
// forEach returns undefined
const result = numbers.forEach(n => n * 2);
console.log(result); // undefined
Q3: How does reduce() work?
Answer: reduce() executes a reducer function on each array element, passing the return value from the previous iteration, resulting in a single output value.
const numbers = [1, 2, 3, 4];
// Sum example
const sum = numbers.reduce((accumulator, current) => {
return accumulator + current;
}, 0); // 0 is initial value
// Execution:
// acc: 0, curr: 1 β return 1
// acc: 1, curr: 2 β return 3
// acc: 3, curr: 3 β return 6
// acc: 6, curr: 4 β return 10
Q4: What is function composition?
Answer: Function composition is combining multiple functions to create a new function, where the output of one function becomes the input of the next.
const add5 = x => x + 5;
const multiply3 = x => x * 3;
// Manual composition
const result = multiply3(add5(10)); // 45
// Compose function
const compose = (f, g) => x => f(g(x));
const calculate = compose(multiply3, add5);
console.log(calculate(10)); // 45
Q5: What are pure functions and why are they important in HOFs?
Answer: Pure functions always return the same output for the same input and have no side effects. They're important because they're predictable, testable, and enable functional programming patterns.
// β
Pure function
const double = x => x * 2;
// β Impure function (side effect)
let count = 0;
const increment = () => count++;
// β Impure function (depends on external state)
const addToCount = x => x + count;
Q6: How do closures work with higher-order functions?
Answer: When a HOF returns a function, the returned function maintains access to the outer function's variables through closure, even after the outer function has returned.
function createCounter() {
let count = 0; // Captured by closure
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// 'count' is preserved through closure
12. Cheat Sheet
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// HIGHER-ORDER FUNCTIONS QUICK REFERENCE
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// DEFINITION
// ββββββββββ
HOF = Function that:
β’ Takes function(s) as argument(s)
β’ Returns a function
β’ Or both
// COMMON ARRAY HOFS
// βββββββββββββββββ
map() β Transform each element
filter() β Select elements
reduce() β Aggregate to single value
forEach() β Execute for each (no return)
find() β Find first match
findIndex()β Find index of first match
some() β Check if any match
every() β Check if all match
sort() β Sort with comparator
// SYNTAX PATTERNS
// βββββββββββββββ
// Callback
array.map(item => item * 2)
// Return function
const multiply = x => y => x * y
// Composition
const compose = (f, g) => x => f(g(x))
// COMMON PATTERNS
// βββββββββββββββ
// Transform
const doubled = numbers.map(n => n * 2)
// Filter
const evens = numbers.filter(n => n % 2 === 0)
// Aggregate
const sum = numbers.reduce((acc, n) => acc + n, 0)
// Chain
const result = data
.filter(x => x > 0)
.map(x => x * 2)
.reduce((acc, x) => acc + x, 0)
// BEST PRACTICES
// ββββββββββββββ
β Use const/let, not var
β Prefer pure functions
β Always provide initial value to reduce
β Use map for transformation
β Use filter for selection
β Use reduce for aggregation
β Avoid mutating original data
β Keep functions small and focused
β Don't overuse chaining
β Don't forget to return in map/filter
// PERFORMANCE
// βββββββββββ
β’ Multiple iterations = slower
β’ Single reduce = faster
β’ Traditional loop = fastest
β’ Consider trade-off: readability vs performance
13. Summary
Key Takeaways
β
HOF = Function operating on functions
β
Two types: Accept functions, return functions
β
First-class functions enable HOFs
β
map() transforms elements
β
filter() selects elements
β
reduce() aggregates to single value
β
Composition combines functions
β
Closures preserve scope in returned functions
β
Pure functions are predictable and testable
β
Functional programming relies on HOFs
Best Practices
- Use map() for transformations
- Use filter() for selections
- Use reduce() for aggregations
- Provide initial values to reduce
- Avoid mutations in callbacks
- Keep functions pure when possible
- Use composition for complex operations
- Consider performance for large datasets
- Write readable code over clever code
- Test HOFs with various inputs
Higher-order functions are fundamental to modern JavaScript and enable powerful functional programming patterns. Mastering them is essential for writing clean, maintainable, and expressive code.