JavaScript Callbacks

Understand synchronous vs asynchronous callbacks, the error-first pattern, and why Callback Hell led to Promises.

1. What is a Callback Function?

A callback is simply a function that you pass into another function as an argument, to be called when some work is finished.

💡 Think of a Fast Food Buzzer

When you order a burger, you don't stand at the register freezing the whole line. The cashier hands you a buzzer (the callback). You go sit down, and when your food is ready, the buzzer buzzes (executes your callback) to notify you!

function prepareFood(foodName, onReady) {
    console.log(`Cooking ${foodName}...`);
    // When done, run the callback function:
    onReady(`${foodName} is hot and ready! 🍔`);
}

function notifyCustomer(message) {
    console.log("Customer Alert:", message);
}

// Pass 'notifyCustomer' as a callback (do NOT add parentheses () when passing!)
prepareFood("Burger", notifyCustomer);
Output
Cooking Burger...
Customer Alert: Burger is hot and ready! 🍔

2. Synchronous vs Asynchronous Callbacks

Callbacks can run immediately (sync) or wait for a timer/API (async):

1. Synchronous (Immediate ⚡)

Runs right away before moving to the next line of code (e.g. .map(), .filter()).

[1, 2].forEach(n => console.log(n));
console.log("Done"); // Prints after loop

2. Asynchronous (Delayed ⏳)

Pauses in the background (timers, network requests) and runs after the main script completes.

setTimeout(() => console.log("Timer"), 0);
console.log("Done"); // Prints BEFORE timer!

3. The Error-First Callback Pattern

In Node.js and classic JavaScript APIs, the first input of a callback is always reserved for an error. If there's no error, it passes null:

function fetchScore(studentId, callback) {
    setTimeout(() => {
        if (studentId <= 0) {
            // 1. First argument is the Error, second is null:
            return callback(new Error("Invalid Student ID!"), null);
        }
        // 2. On success: error is null, second argument is the data:
        callback(null, { id: studentId, score: 95 });
    }, 50);
}

fetchScore(101, (err, data) => {
    if (err) {
        return console.log("Error:", err.message);
    }
    console.log("Success! Score is:", data.score);
});
Output
Success! Score is: 95

4. Callback Hell: Why Promises Were Created

When you have multiple async actions in a row (e.g. login -> fetch profile -> load messages -> play notification), nesting callbacks inside callbacks creates a giant messy triangle called Callback Hell:

⚠️ The "Pyramid of Doom"

loginUser(credentials, (err, user) => {
    getProfile(user.id, (err, profile) => {
        getMessages(profile.inboxId, (err, messages) => {
            renderScreen(messages, (err) => {
                // Hard to read, hard to debug, error handling is duplicated!
            });
        });
    });
});

Modern JavaScript solved this problem with Promises and async/await, which let you write flat, readable asynchronous code.

5. Quick Callback Summary

Concept What it Means Rule to Remember
Callback A function passed into another function Pass the reference fn, not the invocation fn()
Sync Callback Executes immediately in sequence Blocks following lines until finished (e.g. .map())
Async Callback Executes after background task finishes Never blocks main thread (e.g. setTimeout)
Error-First callback(err, data) format Always check if (err) before using data

6. Callbacks Interview Deck

Test your understanding on async timing, passing references vs invocations, error-first conventions, and callback hell:

🎯 Callbacks Interview Deck
Question 1 of 5
⭐ Easy • Async Execution Order Async Timing
console.log(1);

setTimeout(() => {
    console.log(2);
}, 0);

console.log(3);
What will be the order of logged numbers?
A) 1, 3, 2
B) 1, 2, 3
C) 2, 1, 3
D) 3, 2, 1
⚡ Medium • Reference vs Invocation Callback References
function greet() {
    return "Hello";
}

function processUser(callback) {
    callback();
}

// Passing greet() instead of greet:
// processUser(greet());
What happens when passing greet() with parentheses?
A) Runs greet normally
B) Throws TypeError: callback is not a function (passes undefined)
C) ReferenceError
D) Returns "Hello"
⭐ Easy • Error-First Convention Node Conventions
function doTask(callback) {
    // Success scenario:
    callback(null, "Data received");
}
What should the first argument be on a successful callback?
A) null
B) true
C) "Success"
D) 0
⭐ Easy • Synchronous Callbacks Array Callbacks
const nums = [10, 20];
const doubled = nums.map(x => x * 2);

console.log(doubled);
What will be logged to the console?
A) [20, 40]
B) [10, 20]
C) 60
D) undefined
⭐ Easy • Callback Hell Solution Async Patterns
// Deeply nested callbacks:
step1(() => step2(() => step3(() => step4())));
What modern JavaScript feature was introduced to solve this?
A) Promises and async/await
B) for...in loops
C) BigInt
D) Symbols

7. Practice Challenge

🎯 Coding Challenge: Async Error-First Calculator

1. Create an asynchronous function calculateAsync(a, b, callback) that uses setTimeout.
2. If either input is not a number, pass new Error(...) as the first argument.
3. If valid, pass null as the first argument and the sum a + b as the second argument.

Your output will appear here.