setTimeout works, how event listeners are just callbacks, a real button-click counter example using closures, the mistakes beginners make, and why removing event listeners is important for memory. Let's start.
What Is a Callback Function
A callback function is simply a function that you pass as an argument to another function, so it can be called later.
In JavaScript, functions are first-class citizens. That means you can treat a function like any other value — you can save it in a variable, pass it into another function, or return it from a function. This single idea is what makes callbacks possible.
The name "callback" makes sense if you think about it in plain English. When you give your phone number to someone and ask them to call you back later, that is exactly the idea. You hand over your function, and the other code promises to call it back at the right moment.
A Tiny First Example
function greet(name) {
console.log("Hello, " + name);
}
function welcome(userName, callback) {
console.log("Welcome to our app!");
callback(userName);
}
welcome("Anita", greet);
// Welcome to our app!
// Hello, Anita
Here, greet is the callback. We didn't call it ourselves — we passed it in. The welcome function decided when to call it. That is all a callback is.
Why Does JavaScript Need Callbacks
JavaScript is a single-threaded, synchronous language. It runs one line at a time, in order, using one call stack. It does not wait for slow things on its own.
But real apps need to wait — for a timer, a button click, an API response, a file to load. If JavaScript stopped the whole page for 3 seconds while waiting, users would see a frozen screen.
So JavaScript says: "Here is a deal — don't make me wait. Give me a function. I will keep working. When the slow task is done, I will call your function back." That function is the callback. It is how JavaScript does async work without pausing.
Example 1: Callback with setTimeout
The easiest place to see callbacks in action is setTimeout. You give it a function and a delay. It runs your function after that delay — while everything else keeps going.
console.log("Step 1: start");
setTimeout(function () {
console.log("Step 3: timer fired after 2 seconds");
}, 2000);
console.log("Step 2: end of script");
// Output order:
// Step 1: start
// Step 2: end of script
// Step 3: timer fired after 2 seconds
Notice that "Step 2" prints before "Step 3", even though the timer call is written in between. JavaScript does not wait. It hands the callback to the browser's timer, finishes the rest of the script, and runs the callback later.
What Happens Step by Step
Never Block the Main Thread
JavaScript has only one call stack. Every line of code must go through it. If you put a heavy loop or a long calculation on the main thread, the whole page freezes — no clicks, no scroll, nothing.
This is the real reason callbacks exist. They let JavaScript stay responsive while slow things happen somewhere else.
Example 2: Callbacks with Array Methods
You may have already used callbacks without realizing it. Every time you write map, filter, or forEach, you are passing a callback.
const numbers = [1, 2, 3, 4];
numbers.forEach(function (n) {
console.log(n * 10);
});
// 10, 20, 30, 40
The function inside forEach is a callback. You wrote it, you passed it in, but forEach is the one that calls it — once for each item.
Example 3: Event Listeners Are Callbacks Too
Every time you attach a click handler to a button, you are using a callback. The browser stores your function and calls it back when the user clicks.
const likeBtn = document.getElementById("like");
likeBtn.addEventListener("click", function () {
console.log("You liked the video!");
});
Read it out loud: "Hey button, when someone clicks you, please call back this function." That is exactly what the event listener does.
Real Example: A Button-Click Counter
A common interview question: "Make a button that counts how many times it was clicked." Let's solve it two ways — the bad way first, then the clean way.
Way 1: Using a Global Variable (Not Ideal)
let count = 0;
document.getElementById("like").addEventListener("click", function () {
count = count + 1;
console.log("Clicked " + count + " times");
});
This works, but count is a global variable. Any other code on the page can change it by mistake. That is dangerous in big apps.
Way 2: Using a Closure (Cleaner)
A better way is to keep count private inside a closure. The callback remembers the variable even after the outer function finishes.
function setupLikeButton() {
let count = 0;
document.getElementById("like").addEventListener("click", function () {
count = count + 1;
console.log("Clicked " + count + " times");
});
}
setupLikeButton();
Now count lives only inside setupLikeButton. Nobody else on the page can touch it. The inner callback keeps a private reference to count because of a closure. This is the professional way to write it.
Callbacks and the Call Stack
To understand callbacks deeply, remember how JavaScript runs them. Every async callback comes back later through this flow:
1. Call Stack
2. Web APIs
3. Callback Queue
4. Event Loop
That is why your callback runs "after some time" and not exactly when you expected. The event loop only runs it when the call stack is free.
Removing Event Listeners: A Memory Tip
Every event listener keeps your callback alive in memory. If you attach thousands of listeners and never remove them, your page slowly eats up RAM and becomes slow.
function onClick() {
console.log("Clicked!");
}
const btn = document.getElementById("like");
btn.addEventListener("click", onClick);
btn.removeEventListener("click", onClick);
When a component or a page is no longer needed, always remove the listeners you added. React and Vue handle this automatically in their cleanup phases, but in plain JavaScript it is your job.
Common Mistakes
Mistake 1: Calling the Function Instead of Passing It
This is the most common beginner mistake. The parentheses change everything.
// Wrong: runs greet right now, passes its return value
setTimeout(greet(), 1000);
// Right: passes greet itself, runs it later
setTimeout(greet, 1000);
Mistake 2: Blocking the Main Thread
Running a giant loop or a heavy calculation on the main thread freezes the page. Break it up, or use async tools like promises, workers, or setTimeout chunks.
Mistake 3: Callback Hell
Nesting callback inside callback inside callback creates a code pyramid that is hard to read. This is called callback hell or the pyramid of doom.
getUser(1, function (user) {
getOrders(user.id, function (orders) {
getItems(orders[0].id, function (items) {
getPrice(items[0].id, function (price) {
console.log(price);
});
});
});
});
The fix for callback hell is Promises and async/await — which are just better wrappers around callbacks.
Mistake 4: Inversion of Control
When you hand your callback to an outside API, you trust it to call your function correctly — once, at the right time, with the right data. If it misbehaves (calls twice, never calls, or fails silently), your app breaks. Promises were invented to fix this trust problem.
Mistake 5: Forgetting to Remove Event Listeners
Leftover listeners waste memory and sometimes fire on elements that no longer exist. Always clean up.
Comparison: Sync vs Async Callbacks
| Feature | Sync Callback | Async Callback |
|---|---|---|
| When does it run? | Right now, in the same tick | Later, after some event |
| Example | arr.map(fn), arr.forEach(fn) |
setTimeout, fetch.then, click handler |
| Uses event loop? | No | Yes |
| Blocks main thread? | Yes, during its run | No, runs in the background |
| Can cause callback hell? | Not really | Yes — that's why Promises exist |
Real-World Usage
Making an API Call
fetch("/api/products").then(function (res) {
return res.json();
}).then(function (data) {
console.log(data);
});
Delayed Toast Notification
function showToast(message) {
const box = document.getElementById("toast");
box.textContent = message;
box.style.display = "block";
setTimeout(function () {
box.style.display = "none";
}, 3000);
}
showToast("Saved successfully!");
Form Submit Handler
const form = document.getElementById("login");
form.addEventListener("submit", function (event) {
event.preventDefault();
console.log("Form submitted");
});
The Interview Answer
Say this clear one-liner:
"A callback function is a function passed as an argument to another function, which is then called at a later point in time. Callbacks are the way JavaScript handles async work since it is single-threaded."
Then give one quick example (
setTimeout or an event listener) and mention that callbacks also have two downsides — callback hell and inversion of control — which Promises solve.
Summary
A callback function is simply a function you pass into another function so it can be run later. JavaScript allows this because functions are first-class values — they can be stored, passed, and returned just like numbers or strings.
Callbacks exist because JavaScript is single-threaded. Without them, the page would freeze every time we waited for a timer, an API, or a click. Callbacks let slow work happen in the background and come back to us when ready.
You already use callbacks everywhere — in setTimeout, addEventListener, array.map, and fetch().then. They are also the perfect place to use closures, as we saw with the like-button counter.
Just remember the small rules: pass the function, don't call it (no parentheses); never block the main thread; remove event listeners when you are done; and when your callbacks start nesting into a pyramid, switch to Promises or async / await.
| Concept | Key Takeaway |
|---|---|
| First-class function | Functions can be treated like any other value |
| Callback | Function passed to another function, run later |
| Why needed | JavaScript is single-threaded — must not block |
| setTimeout | Classic example of an async callback |
| Event listeners | Callbacks the browser calls on user actions |
| Closures | Private data kept alive for callbacks |
| Main thread | Never block it with heavy work |
| Remove listeners | Avoid memory leaks in large apps |
| Callback hell | Nested callbacks — use Promises instead |
| Inversion of control | Losing trust when handing callbacks to others |