Skip to main content

Command Palette

Search for a command to run...

Callbacks in JavaScript: Why Do They Even Exist

Updated
5 min read
Callbacks in JavaScript: Why Do They Even Exist

Hello readers,

In my previous blog, we explored how JavaScript executes code—Sync vs. Async—and why things don't always run in the order we expect.

If you haven’t read it yet, I highly recommend checking that out first. Here is the link:

https://js-with-abhishek.hashnode.dev/sync-async

Because today’s topic is built on top of that understanding…

Here are some questions for you...

  • How does JavaScript handle tasks like API calls or timers?

  • How can one function “wait” for another without blocking everything?

  • And why do we pass functions inside functions? 🤯

That’s where callbacks enter the scene, and this blog is all about callbacks.

Before jumping into callbacks... let's understand this first

Functions are just values in JavaScript

In JavaScript, functions are first-class citizens.
That means:

  • You can store them in variables

  • Pass them as arguments

  • Return them from other functions

Example:

function greet() {
  console.log("Hello!");
}

function execute(fn) {
  fn(); // calling the function passed as argument
}

execute(greet);

Here, greet is passed as a value.

What is a Callback Function?

In a simple way, the callback function is:

A function that is passed as an argument to another function and is executed later

Example:

function processUser(name, callback) {
  console.log("Processing user:", name);
  callback();
}

function sayWelcome() {
  console.log("Welcome!");
}

processUser("Abhishek", sayWelcome);

Here:

  • sayWelcome is the callback

  • It runs after processUser does its job

Why do callbacks exist?

Now comes the real question — why do we even need them?

Problem without callbacks

JavaScript is single-threaded. It can do only ONE thing at a time

So what happens with slow tasks?

  • API calls

  • File reading

  • Timers

If JS waits for them → everything freezes

Solution: Asynchronous programming

Instead of blocking, JavaScript says:

“Hey, I’ll continue my work… you call me back when you're done.”

And that “call me back” is exactly what a callback is.

Example

console.log("Start");

setTimeout(() => {
  console.log("Inside timeout");
}, 2000);

console.log("End");

Output:

Start
End
Inside timeout

The function inside the setTimeout is a callback.

Passing Functions as Arguments

Callbacks work because functions can be passed around like data.

Example:

function calculate(a, b, operation) {
  return operation(a, b);
}

function add(x, y) {
  return x + y;
}

console.log(calculate(2, 3, add)); // 5
  • add is passed as a callback

  • calculate decides when/how to execute it

Use case of callbacks

1. Timers

setTimeout(() => {
  console.log("Executed after delay");
}, 1000);

2. API Calls

fetch("https://api.example.com/data")
  .then((response) => response.json())
  .then((data) => console.log(data));

.then() takes a callback. Don't worry, this code snippet will be covered in the next blog.

3. Event Handling

button.addEventListener("click", () => {
  console.log("Button clicked!");
});

The function runs only when an event happens.

Problems with callbacks

Up until now, callbacks look clean and powerful…

But in real-world apps, things don’t stay this simple

Let’s understand this with a relatable example

To understand the problems with callbacks, imagine this scenario

Imagine you're building a system like Zomato/Swiggy

Steps involved:

  1. Place Order

  2. Process Payment

  3. Assign Delivery Partner

  4. Deliver Order

Important: Each step depends on the previous one

  • No payment → no delivery

  • No order → nothing happens

Let’s implement this using callbacksLet’s implement this using callbacks

function placeOrder(callback) {
  setTimeout(() => {
    console.log("Order placed");
    callback();
  }, 1000);
}

function processPayment(callback) {
  setTimeout(() => {
    console.log("Payment successful");
    callback();
  }, 1000);
}

function assignDelivery(callback) {
  setTimeout(() => {
    console.log("Delivery partner assigned");
    callback();
  }, 1000);
}

function deliverOrder() {
  setTimeout(() => {
    console.log("Order delivered");
  }, 1000);
}

// Flow
placeOrder(() => {
  processPayment(() => {
    assignDelivery(() => {
      deliverOrder();
    });
  });
});

Problem number 1: Pyramid of Doom

Look at that structure carefully…

placeOrder(() => {
  processPayment(() => {
    assignDelivery(() => {
      deliverOrder();
    });
  });
});

This shape is called Pyramid of Doom (aka Callback Hell)

As steps increase → nesting increases

Imagine adding:

  • Order tracking

  • Notifications

  • Refund logic

Your code becomes a right-slanting triangle

  • Hard to read

  • Hard to maintain

  • Easy to break

Problem 2: Function Dependency

Each function depends on the previous one:

  • processPayment depends on placeOrder

  • assignDelivery depends on processPayment

  • deliverOrder depends on assignDelivery

This creates tight coupling

Why is this bad?

  • You can’t reuse functions independently

  • You must follow the strict order

  • Changing one step may break the entire chain

Problem 3: Inversion of Control

This is a subtle but very important issue.

When you pass a callback:

placeOrder(() => {
  processPayment(() => {
    ...
  });
});

You are giving control of your function to another function

Now you’re trusting:

  • Will it call your callback?

  • Will it call it once or multiple times?

  • Will it call it at the right time?

You lose control over execution

Problem 4: Error Handling Nightmare

Now imagine something fails…

placeOrder(() => {
  processPayment((err) => {
    if (err) {
      console.log("Payment failed");
    } else {
      assignDelivery(() => {
        deliverOrder();
      });
    }
  });
});

Error handling becomes:

  • Scattered

  • Repetitive

  • Ugly

And worse… You must handle errors at every level

Callbacks were powerful… but:

  • They don’t scale well

  • They make code messy

  • They reduce readability

And That’s Why Promises Exist

To solve:

  • Callback hell

  • Dependency issues

  • Error handling mess

  • Inversion of control

JavaScript introduced Promises

That's it for this blog. I hope you understood callbacks and the problems associated with them. In the next blog, I will discuss what promises are and how they solve these callback issues.

Until then...

Stay Consistent and keep grinding

Peace. ✌️