JavaScript Notes
Master JavaScript callbacks from basics to advanced patterns. Learn callback functions, higher-order functions, callback hell, error-first callbacks, and how callbacks interact with the event loop and task queues.
Introduction
A callback is a function passed as an argument to another function, to be executed ("called back") at a later time. Callbacks are the fundamental building block of asynchronous JavaScript — they were the original way to handle operations that complete in the future, like network requests, file reads, and timers.
Understanding callbacks is essential because:
- They're used everywhere: event handlers, array methods, timers, Node.js APIs
- They reveal how JavaScript's event loop and callback queue work
- Understanding their limitations explains why Promises and async/await were created
- Many libraries and legacy codebases still rely heavily on callbacks
Synchronous vs Asynchronous Callbacks
Synchronous Callbacks (Execute Immediately)
2 4 6 8 10 Done! [2, 4, 6, 8, 10] [2, 4] [1, 1, 3, 4, 5]
Asynchronous Callbacks (Execute Later)
// Timer callbacks - execute AFTER a delay
console.log("1. Before setTimeout");
setTimeout(function() {
console.log("3. Inside setTimeout callback (after 2 seconds)");
}, 2000);
console.log("2. After setTimeout (runs immediately!)");1. Before setTimeout 2. After setTimeout (runs immediately!) 3. Inside setTimeout callback (after 2 seconds) ← 2 seconds later
Event Loop Timeline for Async Callbacks
Higher-Order Functions
A higher-order function is a function that either takes a function as an argument (callback) or returns a function. They enable powerful composition patterns.
Iteration 0
Iteration 1
Iteration 2
10
15
{ valid: true, value: 5 }
{ valid: false, error: "Validation failed for: -3" }Real World Analogy 🍔
Think of callbacks like ordering food at a drive-through:
- You place your order (call an async function, e.g.,
loadData()) - You're handed a receipt number (function registers your callback)
- You pull forward — you don't block the lane (non-blocking!)
- When your food is ready, the speaker calls your number (callback is invoked)
- You receive your food and continue (callback processes the result)
The key insight: you didn't stand frozen at the window. JavaScript kept moving while the "kitchen" (Web API) prepared your food.
Real-World Async Callback Patterns
Pattern 1: Event Listeners
// DOM event callbacks
const button = document.querySelector("#myButton");
button.addEventListener("click", function(event) {
console.log("Button clicked!", event.target);
console.log("Click coordinates:", event.clientX, event.clientY);
});
// Multiple callbacks on same event
button.addEventListener("click", function() {
console.log("Second handler - analytics tracking");
});
// Remove callback with named function
function handleClick(event) {
console.log("Handled!");
button.removeEventListener("click", handleClick); // Remove after first click
}
button.addEventListener("click", handleClick);Pattern 2: Timer Callbacks
Tick 1 Tick 2 Tick 3 Tick 4 Tick 5 Timer stopped! This runs once after 3 seconds
Pattern 3: XMLHttpRequest (Classic Async Callback)
// The old way of making HTTP requests (before fetch)
function makeRequest(url, onSuccess, onError) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
const data = JSON.parse(xhr.responseText);
onSuccess(data);
} else {
onError(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
};
xhr.onerror = function() {
onError(new Error("Network error"));
};
xhr.send();
}
// Usage
makeRequest(
"https://jsonplaceholder.typicode.com/users/1",
function(data) {
console.log("Success:", data.name); // "Leanne Graham"
},
function(error) {
console.error("Error:", error.message);
}
);Success: Leanne Graham
Error-First Callbacks (Node.js Pattern)
The error-first callback (also called "errback") is a convention in Node.js where the first argument of the callback is reserved for an error object. If there's no error, it's null.
Data: Contents of data.txt: Hello World! Error: File not found: missing.txt
Callback Hell (Pyramid of Doom)
When you need to perform multiple async operations in sequence, callbacks get deeply nested, creating callback hell — code that's hard to read, debug, and maintain.
Visual: The Pyramid Shape
Problems with Callbacks
1. Inversion of Control
// You hand control of your code to a third-party library
// What if it calls your callback multiple times? Or never?
thirdPartyLibrary.doSomething(function(result) {
// ⚠️ You trust the library to:
// - Call this exactly once
// - Call it with correct arguments
// - Call it at the right time
// - Not swallow errors
processPayment(result); // Dangerous if called multiple times!
});
// ✅ Promises solve this - they can only resolve/reject ONCE2. Error Handling is Fragile
// ❌ Errors in callbacks don't propagate to outer try-catch
try {
setTimeout(function() {
throw new Error("This error is NOT caught by the outer try-catch!");
}, 1000);
} catch (e) {
// This NEVER executes for the async error!
console.log("Caught:", e);
}3. Difficult to Compose (Parallel Operations)
Refactoring Callback Hell
Strategy 1: Named Functions (Extract and Flatten)
Strategy 2: Convert to Promises (The Modern Solution)
Advanced Callback Patterns
Debounce (Rate-Limiting Callbacks)
Searching for: javascript ← Only fired once after all rapid calls
Once (Execute Callback Only Once)
function once(callback) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = callback.apply(this, args);
}
return result;
};
}
const initialize = once(function() {
console.log("Initializing app...");
return { initialized: true };
});
initialize(); // "Initializing app..."
initialize(); // Nothing
initialize(); // NothingInitializing app...
Callback Queue vs Microtask Queue
// This demonstrates the priority difference
console.log("1. Synchronous");
// Goes to CALLBACK QUEUE (macrotask)
setTimeout(function() {
console.log("4. setTimeout (callback/macrotask queue)");
}, 0);
// Goes to MICROTASK QUEUE (higher priority)
Promise.resolve().then(function() {
console.log("3. Promise.then (microtask queue)");
});
queueMicrotask(function() {
console.log("2.5. queueMicrotask (microtask queue)");
});
console.log("2. Synchronous");1. Synchronous 2. Synchronous 2.5. queueMicrotask (microtask queue) 3. Promise.then (microtask queue) 4. setTimeout (callback/macrotask queue)
Common Mistakes ⚠️
Mistake 1: Forgetting to Return After Error
Mistake 2: Calling Callback Multiple Times
// ❌ Bug: Callback called twice!
function riskyFunction(callback) {
try {
const result = JSON.parse('{"ok": true}');
callback(null, result);
} catch (error) {
callback(error);
}
callback(null, "extra call"); // ← BUG! Called again!
}
// ✅ Fix: Use a guard flag
function safeFunction(callback) {
let called = false;
function once(err, result) {
if (called) return;
called = true;
callback(err, result);
}
try {
const result = JSON.parse('{"ok": true}');
once(null, result);
} catch (error) {
once(error);
}
}Mistake 3: Losing this Context
Alice is friends with Bob Alice is friends with Charlie
Interview Questions 🎯
Q1. What is a callback function?
A callback is a function passed as an argument to another function, intended to be executed at a later time. It's the primary mechanism for handling asynchronous operations in JavaScript. Callbacks can be synchronous (like array methods) or asynchronous (like setTimeout or event handlers).
Q2. What is callback hell and how do you solve it?
Callback hell (pyramid of doom) occurs when multiple async operations depend on each other, creating deeply nested callbacks. Solutions: (1) Extract to named functions to flatten structure, (2) Promises for chaining with .then(), (3) async/await for synchronous-looking async code.Q3. Explain the error-first callback pattern.
Error-first callbacks are a Node.js convention where the first parameter is an error object (nullif successful), and subsequent parameters contain result data:function(err, data) { if (err) { handle error } else { use data } }. Always return after calling the error callback to stop further execution.
Q4. What's the difference between synchronous and asynchronous callbacks?
Synchronous callbacks execute immediately during the calling function's execution (e.g.,Array.forEach). Asynchronous callbacks are queued and execute later, after the current call stack clears (e.g.,setTimeout, event handlers).
Q5. Why can't you use try-catch with asynchronous callbacks?
By the time an async callback executes, the try-catch block has already completed and its stack frame is gone. The callback runs in a different event loop tick. That's why error-first callbacks pass errors as arguments, and why Promises/async/await were created.
Q6. What is "inversion of control" in the context of callbacks?
When you pass a callback to a third-party function, you surrender control over when and how your code runs. The receiver could call it multiple times, never call it, or call it with wrong arguments. Promises solve this because they can only resolve/reject once.
Q7. What is the difference between the callback queue and the microtask queue?
The microtask queue (Promise callbacks, queueMicrotask) has higher priority — all microtasks are drained after each sync task. The callback queue (setTimeout, setInterval, DOM events) has lower priority — only ONE item is processed per event loop cycle, after all microtasks complete.Q8. How does debounce work and when do you use it?
Debounce delays a callback until after a period of inactivity. It resets the timer on every call, so the callback only fires once after the calls stop. Use it for search-as-you-type, window resize handlers, or any rapidly-firing event where you only want to react to the final state.
Key Takeaways 📌
- Callbacks are functions passed to other functions — they're the building block of async JS
- Synchronous callbacks execute immediately (forEach, map, filter)
- Asynchronous callbacks are queued and execute later (setTimeout, fetch, events)
- Error-first pattern (
err, data) is the Node.js convention for error handling - Callback hell results from deeply nested sequential async operations
- Higher-order functions take callbacks or return functions — enabling composition
- The event loop moves callbacks from the queue to the call stack when it's empty
- Promises and async/await were created to solve callback hell and inversion of control
Next Steps
Now that you understand callbacks and their limitations, the next chapter explores Promises — JavaScript's built-in solution for managing asynchronous operations with better composition, error handling, and readability.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for JavaScript Callbacks - Complete Guide with Callback Hell & Patterns.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this JavaScript Master Course topic.
Search Terms
javascript-complete-guide, javascript master course, javascript, complete, guide, asynchronous, callbacks, javascript callbacks - complete guide with callback hell & patterns
Related JavaScript Master Course Topics