Skip to main content

What is a callback?

A callback is a function you pass to another function, to be called later when something finishes. You’ve already used them — .map(), .filter(), and .forEach() all take callbacks.
For async operations, callbacks work the same way: “When this async task finishes, call this function with the result.”

Callbacks for async operations

Before fetch, JavaScript used XMLHttpRequest with callbacks. Here’s what async code looked like with callbacks:
The pattern: pass a function that receives the result. The async operation calls your function when it’s done.

The error-first callback pattern

Node.js standardized a pattern: the first argument to a callback is always the error (or null if no error).
The error-first pattern (callback(error, result)) is a convention, not a language feature. Node.js APIs follow this pattern, but browser APIs like fetch use Promises instead.

Callback hell

The real problem shows up when you need multiple async operations that depend on each other — get a user, then get their orders, then get the order details:
This is called callback hell or the “pyramid of doom.” Every dependent async operation adds another level of nesting. It’s:
  • Hard to read
  • Hard to debug
  • Hard to handle errors properly
  • Easy to make mistakes
Compare the same logic with the modern approach you’ll learn soon:
Same logic, no nesting, clean error handling. This is where we’re headed.

Where you’ll still see callbacks

Callbacks aren’t dead. You’ll use them for:
These are fine because they’re not being nested. The problem was using callbacks for sequential async operations.
You don’t need to master callbacks for async work. The point of this lesson is to understand why Promises and async/await were created. For actual async code, you’ll use async/await.

What’s next?

Promises were invented to solve callback hell. They give async operations a cleaner interface and let you chain operations without nesting.

Promises

Handle async operations with a cleaner pattern