← JS Mastery | Module 3: Functions Callbacks & Higher-Order Functions
Module 3

Callbacks & Higher-Order Functions

⏱ 20 min read ● Intermediate 🆓 Free

Functions as First-Class Citizens

In JavaScript, functions are first-class values. This means you can assign them to variables, pass them as arguments, and return them from other functions. This is the foundation of functional programming.

// Functions can be stored in variables
const sayHello = function() { return "Hello!"; };

// Passed as arguments (callbacks):
function executeAfterDelay(callback, delay) {
  setTimeout(callback, delay);
}
executeAfterDelay(() => console.log("Executed!"), 1000);

// Returned from functions:
function createGreeter(greeting) {
  return (name) => `${greeting}, ${name}!`;
}
const hello = createGreeter("Hello");
const goodbye = createGreeter("Goodbye");
console.log(hello("Alice"));    // "Hello, Alice!"
console.log(goodbye("Bob"));    // "Goodbye, Bob!"

// Stored in arrays and objects:
const operations = {
  add: (a, b) => a + b,
  sub: (a, b) => a - b,
  mul: (a, b) => a * b,
  div: (a, b) => b !== 0 ? a / b : null
};
console.log(operations.add(3, 4));  // 7

Higher-Order Functions

A higher-order function either takes a function as an argument or returns a function. They're the core of JavaScript's array methods and functional patterns.

// Built-in higher-order functions:
const numbers = [1, 2, 3, 4, 5];

// map — transform each element
const doubled = numbers.map(n => n * 2);

// filter — keep elements matching predicate
const evens = numbers.filter(n => n % 2 === 0);

// reduce — fold into single value
const sum = numbers.reduce((acc, n) => acc + n, 0);

// sort — with custom comparator
const words = ["banana", "apple", "cherry"];
words.sort((a, b) => a.localeCompare(b));  // alphabetical

const scores = [85, 92, 78, 96, 88];
scores.sort((a, b) => b - a);  // descending: [96,92,88,85,78]

// Building your own higher-order function:
function applyTwice(fn, value) {
  return fn(fn(value));
}
const double = n => n * 2;
console.log(applyTwice(double, 3));  // 12 (3*2=6, 6*2=12)

// Function composition
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);

const addOne = n => n + 1;
const square = n => n * n;
const negate = n => -n;

const transform = pipe(addOne, square, negate);
console.log(transform(3));  // -((3+1)²) = -16

Callbacks in Async Code

// Before promises, callbacks were the only way to handle async:
function fetchData(url, onSuccess, onError) {
  // Simulating async operation:
  setTimeout(() => {
    if (url.startsWith("https")) {
      onSuccess({ data: "some data" });
    } else {
      onError(new Error("Must use HTTPS"));
    }
  }, 1000);
}

fetchData(
  "https://api.example.com/users",
  (result) => console.log("Success:", result),
  (error) => console.error("Failed:", error)
);

// Callback Hell — the problem with nested callbacks:
fetchUser(userId, (user) => {
  fetchPosts(user.id, (posts) => {
    fetchComments(posts[0].id, (comments) => {
      // 3 levels deep already... imagine 5 or 6
      processComments(comments, (result) => {
        console.log(result); // buried!
      });
    });
  });
});
// This is why Promises were invented (next module!)

⚡ Key Takeaways

🎯 Practice Exercises

EXERCISE 1

Implement map, filter, and reduce from scratch without using the built-in versions.

EXERCISE 2 — CHALLENGE

Build a simple event system: createEventEmitter() that returns an object with on(event, callback) and emit(event, data) methods.

← Closures