100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Callbacks and Asynchronous JavaScript

Learn how JavaScript handles operations that take time using callback functions, and why deeply nested callbacks lead to callback hell.

Asynchronous JavaScriptIntermediate10 min readJul 8, 2026
Analogies

1. Introduction

JavaScript is single-threaded, meaning it can only execute one piece of code at a time on its call stack. Yet browsers and Node.js let JavaScript wait for things like timers, network requests, and file reads without freezing the whole program. This is possible because slow operations are handed off to the runtime (the browser or Node), and JavaScript keeps running other code in the meantime. A callback is simply a function passed as an argument to another function, to be invoked later when that operation finishes. Callbacks were the original way JavaScript handled asynchronous work, long before Promises or async/await existed.

🏏

Cricket analogy: A single umpire can only watch one thing at a time, but by delegating boundary-line decisions to assistant umpires, the runtime, the main umpire keeps the match moving instead of freezing play; the assistants call back with a decision once ready, the way scoring was done before third-umpire tech existed.

Under the hood, the JavaScript engine relies on an event loop. The event loop continuously checks whether the call stack is empty; when it is, it pulls the next task from a queue (the callback/task queue) and pushes it onto the stack to run. This is what allows setTimeout, DOM events, and I/O callbacks to run 'later' without blocking the main thread.

🏏

Cricket analogy: The match referee only calls in the next scheduled fixture once the current match on the ground has completely finished, stack empty, pulling the next game from the tournament's queued schedule, the task queue, exactly when the ground clears.

2. Syntax

javascript
// A function that accepts a callback
function doSomethingAsync(input, callback) {
  setTimeout(() => {
    // simulate work, then call the callback with the result
    callback(null, input * 2);
  }, 100);
}

doSomethingAsync(21, (error, result) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log(result); // 42
});

3. Explanation

When you call an asynchronous function like setTimeout, JavaScript does not wait. It registers the callback with the runtime and immediately continues executing the rest of the synchronous code. Only once the call stack is completely empty does the event loop take the next callback from the task (macrotask) queue and run it. This is why 'later' callbacks always run after all currently queued synchronous code has finished, even if the delay is 0ms.

🏏

Cricket analogy: Scheduling a post-match press conference doesn't pause the celebration on the field; the team keeps celebrating, sync code, and only once every player has left the ground, stack empty, does the press conference, the callback, actually begin, even if it was scheduled immediately.

A common Node.js convention is the 'error-first callback': the first argument is reserved for an error (or null if none occurred), and the second is the result. This pattern works, but it does not compose well. When you need to perform several async steps in sequence -- fetch a user, then their orders, then order details -- each step must be nested inside the previous callback. The result is deeply indented code known as 'callback hell' or the 'pyramid of doom', where error handling has to be duplicated at every level and the logical flow becomes hard to follow.

🏏

Cricket analogy: Checking for rain, the error, before confirming the toss result, then checking ground fitness before confirming innings start, then checking bad light before confirming close of play, nests three separate check-then-proceed steps into a deeply indented sequence of if-no-problem-then reports for the scorer to follow.

Callback hell is not just an aesthetic problem. Each nested callback needs its own error check, it is easy to forget one, and a thrown synchronous error inside an async callback will NOT be caught by a surrounding try/catch -- try/catch only guards code that runs synchronously within it. This is one of the main reasons Promises (and later async/await) were introduced: they let asynchronous errors propagate through a single, predictable channel.

4. Example

javascript
function getUser(id, callback) {
  setTimeout(() => {
    console.log("Fetched user");
    callback({ id, name: "Alice" });
  }, 100);
}

function getOrders(user, callback) {
  setTimeout(() => {
    console.log("Fetched orders for " + user.name);
    callback(["order1", "order2"]);
  }, 100);
}

function getOrderDetails(order, callback) {
  setTimeout(() => {
    console.log("Fetched details for " + order);
    callback({ order, total: 42 });
  }, 100);
}

console.log("Start");

getUser(1, (user) => {
  getOrders(user, (orders) => {
    getOrderDetails(orders[0], (details) => {
      console.log("Details:", details);
      console.log("Done");
    });
  });
});

console.log("End");

5. Output

text
Start
End
Fetched user
Fetched orders for Alice
Fetched details for order1
Details: { order: 'order1', total: 42 }
Done

6. Key Takeaways

  • JavaScript is single-threaded; async operations are handed off to the runtime and their callbacks run later via the event loop.
  • A callback is a function passed to another function to be invoked once an operation completes.
  • Synchronous code (including console.log calls before/after the async call) always runs before any queued callback, even with a 0ms delay.
  • Nesting callbacks to sequence multiple async steps leads to 'callback hell' / the 'pyramid of doom'.
  • try/catch cannot catch errors thrown inside an asynchronous callback, since it has already exited the try block by the time the callback runs.
  • Error-first callbacks (error, result) are the classic Node.js convention, but they don't compose as cleanly as Promises.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#JavaScriptProgrammingStudyNotes#Programming#CallbacksAndAsynchronousJavaScript#Callbacks#Asynchronous#Syntax#Explanation#Concurrency#StudyNotes#SkillVeris