Callback function
A callback function is a function passed as an argument to another function so that it runs later, after some event happens or an operation finishes. Put simply: "I am handing you a function, call it when you are ready."
Theory
TL;DR
- A callback is a function passed as an argument that the receiving function calls at the right moment.
- This works because functions in JavaScript are first-class objects and can be passed as values.
- A callback can be named or anonymous, an arrow function in particular.
- A callback is not necessarily asynchronous:
array.map(fn)calls it synchronously,setTimeout(fn)does not. setTimeout,addEventListenerandfetch().then()are all built on callbacks.- Deep nesting of callbacks is called callback hell, and
Promiseplusasync/awaitcure it.
Quick example
function sayHello(name) {
console.log(`Hello, ${name}!`);
}
function greet(callback) {
console.log('Getting ready to say hello...');
callback('Oleh');
}
greet(sayHello);
// Output:
// Getting ready to say hello...
// Hello, Oleh!What happens:
sayHellois the callback passed intogreet;greetcalls it from inside itself;- two lines are printed to the console, in the order the code runs.
Note that sayHello is passed to greet without parentheses. greet(sayHello()) would call the function immediately and pass its result, that is, undefined.
How it works
- JavaScript accepts a callback as a value, since functions here are first-class objects.
- The receiving function does not call it immediately, it stores it for a later call.
- When the right moment arrives (the timer fired, the request finished, the event happened), the engine calls that function.
A callback does not have to be named, an anonymous function is often passed instead:
setTimeout(() => {
console.log('Two seconds have passed');
}, 2000);Here () => console.log(...) is the callback function, and it will be called in two seconds, not right away.
Callbacks in real situations
1. An event handler. Here the browser calls the callback when the user clicks:
button.addEventListener('click', () => {
console.log('Button clicked!');
});2. Loading data. The .then() callbacks are called after the asynchronous request completes:
fetch('/user')
.then(response => response.json())
.then(data => console.log(data));3. A step in an algorithm. A callback is not necessarily tied to asynchrony, it can simply be a way to plug your own logic in:
function calculate(a, b, callback) {
const result = a + b;
callback(result);
}
calculate(2, 3, (sum) => {
console.log('Result:', sum);
});The callback hell problem
When there are too many callbacks, the code becomes nested and unreadable:
getUser(id, user => {
getPosts(user, posts => {
getComments(posts, comments => {
console.log('Done!');
});
});
});This is called callback hell: every next step shifts the code further to the right, error handling is duplicated at each level, and the sequence of actions becomes hard to follow.
How to get rid of the nesting
Promises appeared to avoid these infernal nestings:
getUser(id)
.then(getPosts)
.then(getComments)
.then(console.log);And then async/await, which gives asynchronous code the look of ordinary sequential code:
const user = await getUser(id);
const posts = await getPosts(user);
const comments = await getComments(posts);
console.log('Done!');A simple analogy for an interview:
Imagine you order a pizza and say: "Call me when it is ready." That call is the callback.
Summary:
| Term | Description |
|---|---|
| Callback function | A function passed into another function to be run later |
| When it is called | After an event, after an operation completes, or on a condition |
| Usage examples | setTimeout, addEventListener, fetch().then() |
| The problem | Deep nesting (callback hell) |
| Modern alternatives | Promise, async/await |
Common mistakes
- Passing a call instead of a function.
setTimeout(fn(), 1000)runsfnimmediately and hands the timer its result. The correct form issetTimeout(fn, 1000). - Assuming every callback is asynchronous.
array.forEach(fn)andarray.map(fn)call the callback synchronously, right now, and defer nothing. - Losing
this. A regular function used as a callback gets its ownthis. Use an arrow function orbind. - Ignoring errors. In the error-first style the first argument is the error:
(err, data) => { if (err) ... }. Skip it and failures are lost silently. - Calling the callback twice. Without a
returnafter an error the function keeps going and calls the callback again, which breaks all the logic above it. - Building deep pyramids. Three or more levels of nesting is a signal to rewrite the code with
Promiseorasync/await.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.