What are callback functions in JavaScript?

Beginner8 min interview
Skills tested:
Higher-order function mechanicsSynchronous vs asynchronous callback executionError-first callback conventionCallback hell and its consequencesWhen to use callbacks vs Promises

Advertisement

🧩 Scenario

In a real codebase, you will encounter callbacks whenever you work with array methods like map, filter, and reduce, event listeners, timers, and older Node.js APIs. Understanding callbacks is a prerequisite for understanding Promises and async/await, because those abstractions were built specifically to solve the problems that callbacks introduce at scale. A developer who cannot read or write callback-based code will struggle with any JavaScript runtime environment, including Node.js, browser APIs, and third-party SDKs.

Architecture Walkthrough

What Makes a Function a Callback

A callback is any function passed as an argument to another function, with the expectation that the receiving function will invoke it at some point. The function that accepts another function as an argument is called a higher-order function. This pattern is native to JavaScript because functions are first-class values: they can be stored in variables, passed around, and returned from other functions.

Callbacks are not inherently asynchronous. Synchronous callbacks execute immediately inside the calling function, such as the function you pass to Array.prototype.map. Asynchronous callbacks execute later, after the current call stack is empty, such as the function you pass to setTimeout or a network request handler.

The Error-First Convention

Node.js popularized the error-first callback pattern, also called the "errback" convention. The callback always receives error as its first argument. If the operation succeeded, error is null. If it failed, error contains the error object and all other arguments should be ignored.

This convention exists because asynchronous errors cannot be caught with try/catch. The error-first pattern gives the caller a consistent place to check for failures, making error handling predictable across different APIs and libraries.

Callback Hell and Its Cost

When multiple asynchronous operations depend on each other, callbacks nest inside callbacks, producing code that is difficult to read, difficult to test, and difficult to reason about. Each level of nesting adds indentation and error handling overhead. This structure is called callback hell or the pyramid of doom.

The practical cost of callback hell is not just aesthetic. Deep nesting makes it easy to miss error cases, hard to add logging or retries, and nearly impossible to share intermediate values across branches. Promises and async/await were introduced specifically to flatten this structure while preserving the non-blocking nature of asynchronous code.


Key Code Explained

// Synchronous callback: executes immediately
const doubled = [1, 2, 3].map((n) => n * 2);
// doubled is [2, 4, 6] before the next line runs

// Asynchronous callback: executes after delay
setTimeout(() => {
  console.log('This runs after the current stack clears');
}, 0);

console.log('This runs first, even though the delay is 0');

The setTimeout example illustrates that even a zero-millisecond delay does not mean "run now." The callback is queued in the event loop and only executes after the current synchronous code finishes.

// Error-first callback pattern
function readFile(path, callback) {
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) {
      callback(err, null);
      return; // critical: return here to prevent calling callback twice
    }
    callback(null, data);
  });
}

readFile('./data.txt', (err, content) => {
  if (err) {
    console.error('Failed to read file:', err.message);
    return;
  }
  console.log(content);
});

The return after calling the callback with an error is critical. Without it, execution continues and the callback could be called a second time with the success path, which leads to subtle and hard-to-trace bugs.


Tradeoffs

ApproachProCon
CallbacksWorks everywhere, no extra syntax or polyfills neededNesting gets unreadable, error handling is manual
PromisesChainable, cleaner error handling with .catchStill requires understanding the callback that wraps them
async/awaitMost readable, looks like synchronous codeRequires understanding Promises underneath, can hide error paths if not careful

What Interviewers Actually Check

  • Whether you understand that a callback is just a function reference, not something syntactically special
  • Whether you can distinguish synchronous from asynchronous callback execution and explain why it matters
  • Whether you know the error-first pattern and can read Node.js-style callback APIs
  • Whether you can name the downsides of deeply nested callbacks and describe how they are solved
  • Whether you understand that setTimeout(fn, 0) does not mean immediate execution
  • Whether you have used higher-order functions like map, filter, and reduce that rely on synchronous callbacks

Follow-Up Questions

  1. How does the JavaScript event loop relate to when an asynchronous callback actually runs?
  2. What happens if you pass a non-function value as a callback to a function that expects one?
  3. How would you write a unit test for a function that accepts a callback?
  4. If a callback-based API is deeply embedded in a codebase, how would you promisify it without rewriting all call sites?
  5. A teammate says callbacks are outdated and everything should use async/await. Where do you agree, and where would you push back?

Common Candidate Mistakes

  • Calling the callback immediately when passing it: doWork(callback()) invokes the callback immediately and passes its return value, not the function itself
  • Not returning after calling the error callback, causing the success branch to also execute
  • Assuming all callbacks are asynchronous, when synchronous callbacks are equally common and important
  • Creating deeply nested callbacks instead of extracting named functions or switching to Promises
  • Forgetting that in asynchronous callbacks, the outer function has already returned by the time the callback runs, so you cannot use a return value from the outer function

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you explain what makes a function a higher-order function and give two examples?
  • Can you demonstrate the difference between synchronous and asynchronous callback execution?
  • Can you write and read the error-first callback pattern used in Node.js?
  • Can you explain what callback hell is and describe one way to avoid it?
  • Can you name the modern alternatives to callbacks for async operations and explain when callbacks are still preferred?

Summary

A callback is a function passed to another function to be executed at a later point, either synchronously within the same call or asynchronously after the current stack clears. This pattern is fundamental to JavaScript because functions are first-class values, and it underlies everything from array methods to event listeners to network requests.

The main limitation of callbacks is that nesting them to handle sequential asynchronous operations produces code that is difficult to maintain. The error-first convention in Node.js helps standardize error handling, but it cannot solve the structural problem of deep nesting. That problem led to Promises and ultimately async/await.

Understanding callbacks deeply matters even if you use async/await every day. Every asynchronous API in JavaScript is built on callbacks at the platform level, and knowing how they execute in the event loop gives you the mental model to debug timing issues, understand error propagation, and make informed choices about when to use callbacks versus higher-level abstractions.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

Are callbacks still used today?

Yes, but mostly replaced by Promises and async/await for better readability.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement