Full Stack • Java • System Design • Cloud • AI Engineering

JavaScript2026-06-06

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

  1. What are Higher-Order Functions?
  2. Why Do We Need Higher-Order Functions?
  3. Real-World Use Cases
  4. Syntax
  5. Internal Working
  6. Memory Diagram
  7. Data Flow Diagram
  8. Code Examples
  9. Common Mistakes
  10. Performance Considerations
  11. Interview Questions
  12. Cheat Sheet
  13. 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

  1. Code Reusability: Write once, use many times
  2. Abstraction: Hide implementation details
  3. Composition: Combine simple functions into complex ones
  4. Declarative Code: Focus on what, not how
  5. Functional Programming: Enable FP paradigm
  6. Cleaner Code: Less boilerplate, more readable
  7. Testability: Easier to test pure functions
  8. 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

  1. Use map() for transformations
  2. Use filter() for selections
  3. Use reduce() for aggregations
  4. Provide initial values to reduce
  5. Avoid mutations in callbacks
  6. Keep functions pure when possible
  7. Use composition for complex operations
  8. Consider performance for large datasets
  9. Write readable code over clever code
  10. 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.