In short
Before Promises existed, JavaScript handled "do this, then that" by passing functions into other functions. These callbacks are still everywhere, from button clicks to array methods.
Functions passed as arguments to be executed later — the original pattern for async code in JS, still used everywhere (event handlers, array methods). Callbacks remain fundamental because browser events and many APIs are callback-driven, even when Promises and async/await provide cleaner composition for asynchronous workflows.

Think of it like this
Leave your number for a call back
You call a coaching centre and every counsellor is busy. Instead of waiting on hold, you leave your number: "call me back when someone's free". A callback is that phone number: a function you hand over so it can be called later.
Watch it happen
Call stack with a delayed callback
Top of the stack is coloured
1/12The script starts running at the top level.
The big ideas
What you need to know
A function passed as an argument
You hand a function to another function, and that function decides when to call it.
Sync callbacks
map, filter and forEach call your callback right away, once per item, before moving on.
Async callbacks
setTimeout and event listeners call your function later, after something happens.
Callback hell
Nesting callback inside callback makes a sideways pyramid that's hard to read. Promises and async/await fix this.
See it in code
Walk through the code
1function fetchMarks(roll, callback) {2 setTimeout(() => {3 if (!roll) return callback(new Error("No roll number"));4 callback(null, { roll, marks: 87 });5 }, 500);6}7 8fetchMarks(42, (err, result) => {9 if (err) return console.log(err.message);10 console.log(result.marks); // 87, after half a second11});12 13[1, 2, 3].forEach((n) => console.log(n * 10)); // sync: 10, 20, 301/4fetchMarks takes a callback. With no roll, it passes an Error and stops.
The (err, result) shape is called error-first. Node.js used it everywhere: check err first, and only use result if err is null.
Save yourself the pain
Common mistakes
Oops
Calling the callback twice by forgetting to return after the error case.
Do this instead
Write return callback(err) so the function stops there and the success line never runs.
Oops
Nesting five callbacks deep to do five steps in order.
Do this instead
Wrap the steps in promises and use async/await. The code reads top to bottom again.
Your mission
Write your own forEach
Quick check
What's the order? setTimeout(() => console.log("A"), 0); console.log("B");
Go deeper
- 1Write a function that accepts a callback and calls it after a `setTimeout`. Validate your understanding by applying it in a small practical example.
- 2Recognize 'callback hell' from deeply nested callbacks in older code. Validate your understanding by applying it in a small practical example.
- 3Understand why Promises/async-await were introduced to solve callback hell.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.