Callback in JavaScript

Hello friends, welcome to shrash studio learning, in this article we will learn about one of the most important topics in JavaScript — Callback Functions. This is a topic asked in almost every frontend interview. If you really want to become a good JavaScript developer, you must understand callbacks deeply. In this article we will use very simple English and small examples. You will learn what a callback is, why JavaScript needs them, how 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

Life of a setTimeout Callback
step 1: "Step 1: start" prints immediately
step 2: setTimeout hands the callback to the browser timer
step 3: JavaScript does NOT wait — it keeps running
step 4: "Step 2: end of script" prints
step 5: 2 seconds later, browser sends the callback back
step 6: Callback runs — "Step 3" prints

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.

Rule of thumb: Any task that takes more than a few milliseconds — a network call, a file read, a big calculation — should be async. That means it runs in the background and comes back through a callback (or a promise).

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:

HOW AN ASYNC CALLBACK GETS BACK

1. Call Stack

Where JS runs code
Hands off setTimeout
Moves on to next line

2. Web APIs

Browser handles the wait
Timer ticks in background
Callback is parked here

3. Callback Queue

Waiting line
Timer finished
Callback joins queue

4. Event Loop

The traffic cop
Waits till stack is empty
Pushes callback to stack

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.

Note: You can only remove a listener if you pass the same function reference you used to add it. An anonymous arrow function cannot be removed later, because you don't have a name to refer to it by.

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

If an interviewer asks "What is a callback function?":

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

Chakrapani U

Hi, I’m Chakrapani Upadhyaya, an IT professional with 15+ years of industry experience. Over the years, I have worked on web development, enterprise applications, database systems, and cloud-based solutions. Through this blog, I aim to simplify complex technical concepts and help learners grow from beginners to confident, industry-ready developers.

Previous Post Next Post

نموذج الاتصال