JavaScript Callbacks

A Function You Pass For Later

JavaScript Callbacks

A callback is a function you hand to someone else, to call later. The original pattern for async — and still everywhere in browser APIs.

4 min read Level 2/5 #callbacks#async#patterns
What you'll learn
  • Recognize the callback pattern
  • Pass functions to `setTimeout` and event handlers
  • See why nested callbacks ("callback hell") motivated Promises

A callback is a function you pass as an argument, to be called later — often after something async finishes. It’s the oldest async pattern in JavaScript, predating Promises by a decade.

Synchronous Callbacks

You’ve been writing these since the iteration lessons. map, filter, reduce, forEach, find all take a callback that runs once per item:

Synchronous callbacks script.js
[1, 2, 3].forEach((n) => console.log(n));
const doubled = [1, 2, 3].map((n) => n * 2);
console.log(doubled);
▶ Preview: console

Asynchronous Callbacks

The other use: hand a function to a feature that will call it back once something finishes — a timer, an event, a network response.

setTimeout script.js
console.log("before");

setTimeout(() => {
  console.log("inside callback (after 0ms)");
}, 0);

console.log("after");

// Logs:
// "before"
// "after"            ← synchronous code runs first
// "inside callback…"
▶ Preview: console

Notice the order. setTimeout schedules the callback, then the script keeps running. The callback fires later, even when the delay is zero.

Event Handlers

A click handler is a callback:

const btn = document.querySelector("#go");
btn.addEventListener("click", (event) => {
  console.log("clicked!", event.target);
});

You hand addEventListener a function. The browser calls it back whenever the user clicks.

Callback Hell

Before Promises, async code often nested callbacks. Each step waited for the previous one.

loadUser((user) => {
  loadOrders(user.id, (orders) => {
    sortOrders(orders, (sorted) => {
      render(sorted);
    });
  });
});

This is callback hell — deep nesting, fragile error handling, hard to read. Promises and async/await solved this.

Callback-Style Error Handling

A common convention in Node-style APIs: the first parameter of the callback is an error (or null if everything’s fine).

import { readFile } from "fs";

readFile("data.txt", "utf-8", (err, contents) => {
  if (err) {
    console.error("failed:", err);
    return;
  }
  console.log("read:", contents);
});

You have to remember the convention and check for the error every time. Promises and async/await make this cleaner — the rest of this chapter covers them.

Callbacks Aren’t Going Away

Even with Promises, you’ll still pass callbacks to:

  • setTimeout / setInterval
  • addEventListener (events)
  • Array methods (map, filter, etc.)
  • Many library APIs

What changed is that new async APIs almost always use Promises instead of error-first callbacks.

Up Next

A better abstraction for async work: Promises.

JavaScript Promises →