JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
58: Callbacks and Callback Hell
I was messing around with a mock API the other day, trying to build a simple user profile viewer. I had a function that simulated fetching a user from a database. It looked something like this:
function getUser(id) {
setTimeout(() => {
console.log("Found user: Dave");
return { id: id, name: "Dave" };
}, 1000);
}
const user = getUser(1);
console.log("The user is: " + user.name);
Wait, where did the data go?
When I ran that, I expected to see "Found user: Dave" and then "The user is: Dave". Instead, I got a TypeError telling me I can't read property 'name' of undefined. I looked at the console, and "The user is: undefined" printed before "Found user: Dave" even showed up.
The problem is that getUser doesn't actually return anything immediately. The return statement is tucked inside the setTimeout callback, which doesn't execute until the timer finishes. By the time that timer goes off, the rest of the script has already finished running. JavaScript didn't wait for the database; it just kept moving.
Handing off the baton
Since I can't force the function to "wait" without blocking the whole browser, I have to change my approach. Instead of asking the function to give me the data, I'll give the function a set of instructions on what to do once it has the data. This is the core idea of a callback.
Let's rewrite it. I'll add a second parameter to getUser—a function that I'll call once the data is ready:
function getUser(id, callback) {
setTimeout(() => {
console.log("Found user: Dave");
const user = { id: id, name: "Dave" };
callback(user); // Here is where we "call back" the provided function
}, 1000);
}
getUser(1, (user) => {
console.log("The user is: " + user.name);
});
Now it works. I'm essentially saying, "Hey, go get the user, and when you're finally done, run this piece of code I'm handing you." It feels a bit backwards at first, but it's the only way to handle asynchronous events without freezing the UI.
The sliding scale of indentation
This works great for one request. But real apps aren't that simple. What if I need the user's name, then I need to use that user's ID to fetch their latest posts, and then I need to fetch the comments for the first post? Each of these is an async request.
I tried chaining them together, and this is what happened to my code:
getUser(1, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
console.log("The first comment is: " + comments[0].text);
// Imagine if we needed to fetch the author of that comment...
// And then the author's profile picture...
});
});
});
Looking at this mess
Take a look at that structure. The code is literally sliding off the right side of my screen. We've hit what the community calls "Callback Hell" (or the "Pyramid of Doom").
Beyond just looking ugly, this is a nightmare to maintain for a few reasons:
- Error Handling: If
getPostsfails, where do I catch that error? I'd have to add an error check inside every single nested level. - Readability: The logic flows horizontally instead of vertically. It's hard to track where one operation ends and the next begins.
- Fragility: If I decide I want to swap the order of two requests, I have to carefully move multiple sets of closing braces and parentheses without breaking the whole chain.
We'll look at ways to flatten this out in future lessons, but for now, recognize this pattern. When you see your code starting to look like a sideways triangle, you've officially entered Callback Hell.
📋 Practical Task
Refactoring the Nested Order-Tracking System
You have been handed a piece of legacy code that tracks a customer's order. The current code is written in a deeply nested callback style, making it hard to read. Your task is to implement the missing logic in the final callback to complete the chain.
The Goal: 1. Fetch the order. 2. Use the order ID to fetch the shipping status. 3. Use the shipping status to fetch the current GPS coordinates of the delivery truck. 4. Finally, log a message: "Your package is currently at [coordinates]".
// Mock API functions (Do not modify these)
function getOrder(orderId, callback) {
setTimeout(() => callback({ id: orderId, item: "Mechanical Keyboard" }), 500);
}
function getShippingStatus(orderId, callback) {
setTimeout(() => callback({ status: "In Transit", truckId: "TRK-99" }), 500);
}
function getTruckLocation(truckId, callback) {
setTimeout(() => callback({ coords: "40.7128° N, 74.0060° W" }), 500);
}
// YOUR TASK:
// Complete the callback chain below to log the coordinates.
getOrder(123, (order) => {
getShippingStatus(order.id, (shipping) => {
// Write your code here to get the truck location
// and log the final message.
});
});
There are no comments for now.