Callbacks and Higher-Order Functions in JavaScript

Introduction

A callback is a function passed into another function to run later—on clicks, timers, or when data arrives. A higher-order function takes or returns functions. You already used these with map and addEventListener; this chapter ties the pattern together for async-style APIs.

Prerequisites

Callback Basics

javascript
// greet calls fn after building message
function greet(name, callback) {
  const message = `Hello, ${name}`;
  callback(message);
}
 
greet("Ada", (msg) => console.log(msg));

Higher-Order Function

javascript
// repeat runs action n times
function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    action(i);
  }
}
 
repeat(3, (i) => console.log(`step ${i}`));

Array Methods as HOFs

map, filter, forEach are built-in higher-order functions:

javascript
const ids = [1, 2, 3].map((id) => `user-${id}`);
console.log(ids);

Timing Callbacks

javascript
// setTimeout schedules callback
setTimeout(() => {
  console.log("2 seconds later");
}, 2000);

In Node, timers need the process to stay alive or use clearTimeout in tests.

Error-First Callback Style (Node Tradition)

javascript
// (err, data) pattern — still seen in older APIs
function fakeRead(path, callback) {
  if (!path) return callback(new Error("missing path"));
  callback(null, `contents of ${path}`);
}
 
fakeRead("/tmp/demo.txt", (err, data) => {
  if (err) return console.error(err.message);
  console.log(data);
});

Modern code often prefers Promises—covered in later async chapters.

Returning Functions from HOFs

javascript
// compose two unary functions
function compose(f, g) {
  return (x) => f(g(x));
}
 
const add1 = (n) => n + 1;
const double = (n) => n * 2;
const add1ThenDouble = compose(double, add1);
 
console.log(add1ThenDouble(3));

Mini Example: once Callback Wrapper

javascript
// Run callback only first time
function once(fn) {
  let called = false;
  return (...args) => {
    if (called) return;
    called = true;
    return fn(...args);
  };
}
 
const init = once(() => console.log("init once"));
init();
init();

FAQ

Callback hell?

Deep nesting hurts readability—use named functions, Promises, or async/await.

Is every function passed a callback?

Loosely yes—in precise terms, callbacks are invoked by the callee you pass them to.

Sync vs async callbacks?

Sync runs immediately; async runs later—know which to avoid race bugs.

What comes next?

Object-oriented programming.