TL;DR: A callback is a function you pass to another function as an argument. It says: "When you finish your job, run this." JavaScript uses callbacks everywhere because it can only do one thing at a time — callbacks let it hand off work and keep going. If nested callbacks get ugly, ask your AI to refactor to async/await.
Why AI Coders Need to Know This
Ask any AI to build you a web app and look at the code it generates. Within the first fifty lines, you will find a callback. Probably three or four of them.
That button click handler? Callback. The code that fetches data from an API? Callback. The timer that shows a notification after five seconds? Callback. The function that reads a file from the server? Callback.
Callbacks are not some advanced computer science concept. They are the basic plumbing of JavaScript — and since JavaScript powers almost every web app your AI builds, callbacks show up in virtually every project you will ever work on.
Here is the problem: when you do not understand callbacks, you cannot read your own code. You see a function nested inside a function nested inside another function, and it looks like spaghetti. When something breaks, you do not know where to look. When your AI generates a fix, you cannot tell if the fix actually makes sense.
The good news: callbacks are genuinely simple once you see the pattern. Let's break it down.
The Construction Metaphor
Imagine you are a general contractor managing a house build. The framing crew is on-site and you need to schedule the electrician — but only after the framing is done.
You have two options:
Option 1: Stand there and watch. You physically stand on the job site, staring at the framing crew until they hammer the last nail. Then you pick up the phone and call the electrician. You got nothing else done all day.
Option 2: Leave your number. You tell the framing crew: "When you finish, call me at this number so I can schedule the electrician." Then you drive to another job site and get other work done. When the framing crew finishes, they call you. You make the next call.
Option 2 is a callback. You handed your phone number (a function) to someone else (another function), saying "use this when you are done." You did not stand around waiting. You went and did other work.
That is exactly what JavaScript does. It cannot stand around waiting — it has only one thread (one worker). So it hands off a callback and moves on to the next task.
What a Callback Actually Looks Like in Code
Let's start with the simplest possible callback — no AI, no frameworks, just raw JavaScript:
// This is a function that accepts a callback
function doSomethingThenReport(callback) {
console.log("Doing the work...");
// Work is done. Now call the callback.
callback();
}
// This is the callback function
function reportDone() {
console.log("Work is finished!");
}
// Pass the callback to the function
doSomethingThenReport(reportDone);
// Output:
// "Doing the work..."
// "Work is finished!"
That is the entire pattern. doSomethingThenReport accepts a function as an argument. When it finishes its work, it calls that function. The function you passed in — reportDone — is the callback.
Notice: reportDone is passed without parentheses. You write reportDone, not reportDone(). Parentheses would call the function immediately. Without them, you are handing the function itself to be called later. This is the most common beginner mistake with callbacks.
Real Scenarios Where AI Generates Callbacks
Now let's look at the callbacks your AI actually writes. These are the four patterns you will see over and over.
1. Event Listeners (Button Clicks, Form Submits)
Every time your AI creates interactive UI, it writes event listeners — and every event listener uses a callback:
// The function inside addEventListener is a callback
const button = document.querySelector('#submit-btn');
button.addEventListener('click', function() {
console.log("Button was clicked!");
// Do something when the user clicks
});
// JavaScript does NOT wait here for a click.
// It registers the callback and moves on.
// When the user eventually clicks, the callback fires.
The second argument to addEventListener — that entire function() { ... } block — is the callback. You are telling the browser: "When someone clicks this button, run this function." The browser goes about its business. When the click happens, it calls your function back.
2. API Calls (Fetching Data)
When your AI fetches data from a server, it uses callbacks through Promises:
// .then() takes a callback that runs when data arrives
fetch('https://api.example.com/users')
.then(function(response) {
return response.json();
})
.then(function(users) {
console.log("Got the users:", users);
})
.catch(function(error) {
console.log("Something went wrong:", error);
});
Each .then() and .catch() takes a callback. The first one says "when the data arrives, run this." The second says "when you have parsed the JSON, run this." The .catch() says "if anything goes wrong, run this." JavaScript does not freeze while waiting for the server — it fires these callbacks when results come back.
3. Timers (setTimeout, setInterval)
Timers are one of the clearest callback examples:
// The first argument to setTimeout is a callback
setTimeout(function() {
console.log("This runs after 3 seconds");
}, 3000);
console.log("This runs immediately");
// Output:
// "This runs immediately"
// (3 seconds later)
// "This runs after 3 seconds"
Notice the order: the "runs immediately" message prints first, even though it comes after setTimeout in the code. JavaScript does not wait three seconds. It sets the timer, registers the callback, and keeps going. When three seconds pass, the callback fires.
4. File and Database Operations (Node.js)
If your AI builds a backend with Node.js, you will see callbacks in file and database operations:
const fs = require('fs');
// The second argument is a callback
fs.readFile('data.json', 'utf8', function(error, data) {
if (error) {
console.log("Failed to read file:", error);
return;
}
console.log("File contents:", data);
});
console.log("This runs while the file is still being read");
Same pattern: JavaScript starts reading the file, registers the callback, and moves on. When the file is fully read, the callback fires with either an error or the data.
How to Read AI-Generated Callbacks
When you see a wall of AI-generated code and want to find the callbacks, ask two questions:
- Which function is being called? That is the "outer" function — the one that does the work.
- Which function is being passed in? That is the callback — the one that runs when the work is done.
Here is a cheat sheet for spotting callbacks in the wild:
// Pattern: someFunction(callback)
setTimeout(function() { ... }, 1000);
// ^^^^^^^^^^^^^^^^^^^^^^^^ ← this is the callback
// Pattern: element.method(event, callback)
button.addEventListener('click', function() { ... });
// ^^^^^^^^^^^^^^^^^^^^^^^^ ← callback
// Pattern: promise.then(callback).catch(callback)
fetch(url).then(function(res) { ... }).catch(function(err) { ... });
// ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
// success callback error callback
// Arrow function syntax (same thing, shorter)
setTimeout(() => { ... }, 1000);
button.addEventListener('click', () => { ... });
fetch(url).then(res => { ... }).catch(err => { ... });
Arrow functions (() => { ... }) are just shorter syntax for function() { ... }. When your AI writes them, it is writing callbacks — just in a more compact form.
Prompt to Try
Explain each callback in this code. For each one,
tell me: what is the outer function, what is the
callback, and when does the callback fire?
[paste your code here]
Why Callbacks Exist: The Single-Thread Problem
JavaScript has one thread. One worker. One pair of hands. It can only do one thing at a time.
Now imagine your app needs to fetch user data from a server. That takes 500 milliseconds. Without callbacks, JavaScript would freeze for half a second — no scrolling, no clicking, no animations. The entire page would lock up every time it needed data.
Callbacks solve this by letting JavaScript say: "Start this task, but do not wait for it. Here is a function to call when it finishes. I am going to keep processing other stuff."
This is called asynchronous programming. The "async" in async/await refers to this exact concept. Callbacks were the original way JavaScript handled asynchronous work. Promises and async/await came later as cleaner ways to write the same idea.
Callback Hell: When Callbacks Go Wrong
Callbacks have one major flaw: when you need to do several things in sequence, each one depending on the previous result, your code starts nesting deeper and deeper:
// This is callback hell — and yes, AI sometimes generates this
getUser(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
getShippingStatus(details.trackingId, function(status) {
updateUI(status, function() {
console.log("Finally done!");
});
});
});
});
});
See that pyramid shape? Each callback is indented further right. By the time you get to the innermost function, you are five levels deep. This is called callback hell (also called the "pyramid of doom"), and it is:
- Hard to read — which closing bracket belongs to which function?
- Hard to debug — if step 3 fails, the error is buried under layers of nesting.
- Hard to modify — adding a step means restructuring the entire pyramid.
AI tools sometimes generate callback hell, especially when given prompts that require multiple sequential operations. If you see this pattern in your AI-generated code, do not try to untangle it manually. Ask your AI to fix it.
Prompt That Fixes Callback Hell
Refactor this code from nested callbacks to
async/await. Keep the same logic and error handling.
[paste the callback hell code]
The refactored version looks like this:
// Same logic, but flat and readable
async function processOrder(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
const status = await getShippingStatus(details.trackingId);
await updateUI(status);
console.log("Finally done!");
} catch (error) {
console.log("Something failed:", error);
}
}
Same operations. Same order. But flat, readable, and each step is on its own line. That is the power of async/await — it is callbacks underneath, but written in a way humans can actually follow.
The Evolution: Callbacks → Promises → Async/Await
JavaScript did not abandon callbacks. It built better patterns on top of them. Understanding the evolution helps you read code from any era:
Callbacks (Original)
Pass a function to be called later. Simple but nests badly. Still used for event listeners and simple timers.
getData(function(result) {
// use result
});
Promises (ES6, 2015)
An object representing a future result. Chains with .then() instead of nesting. Better error handling with .catch().
getData()
.then(result => { ... })
.catch(err => { ... });
The Modern Standard
Async/await (ES2017) lets you write asynchronous code that looks synchronous. Under the hood it uses Promises, which use callbacks. Same concept, cleaner syntax: const result = await getData();
All three solve the same problem: "I need to do something that takes time, and I need to run code when it finishes." Callbacks are the foundation. Promises improved the pattern. Async/await made it readable.
Your AI will generate all three styles depending on what it is building. Event listeners will always use callbacks. API calls might use .then() or async/await. Legacy code uses raw callbacks. Knowing the evolution helps you read any version.
Common Callback Errors and What They Mean
When callbacks go wrong, the errors can be confusing. Here are the ones you will hit most often:
"TypeError: callback is not a function"
This means a function expected to receive a callback but got something else — or got nothing at all. Usually happens when you forget to pass the callback argument:
// Broken: forgot to pass the callback
function processData(data, callback) {
// do work...
callback(result); // ERROR: callback is not a function
}
processData(myData); // Oops — no second argument!
// Fixed: pass the callback
processData(myData, function(result) {
console.log(result);
});
Calling the callback with parentheses when passing it
This is the most common beginner mistake:
// WRONG: This calls handleClick immediately, not on click
button.addEventListener('click', handleClick());
// RIGHT: This passes handleClick to be called later
button.addEventListener('click', handleClick);
Without parentheses, you pass the function. With parentheses, you call the function and pass whatever it returns. This distinction trips up even experienced developers.
Callback fires but nothing happens
Usually means the callback ran before the data was ready, or an error was silently swallowed. Check if your callback has proper error handling:
// Bad: ignores errors
fs.readFile('data.json', 'utf8', function(error, data) {
const parsed = JSON.parse(data); // crashes if error occurred
});
// Good: check for errors first
fs.readFile('data.json', 'utf8', function(error, data) {
if (error) {
console.log("Error reading file:", error);
return; // Stop here — don't try to use data
}
const parsed = JSON.parse(data);
});
Debugging Prompt
I'm getting "callback is not a function" on line [X].
Here is the relevant code. Explain what's wrong and
show me the fix.
[paste your code]
What to Tell Your AI
Now that you understand callbacks, here are specific prompts that use that knowledge:
When Code Is Hard to Read
Refactor this from callbacks to async/await.
Keep the same logic and add try/catch for error handling.
When You See Nested Callbacks
This code has callback hell. Flatten it using
async/await. Explain each step of the refactored version.
When You Need to Understand Generated Code
Walk me through this code. Identify every callback,
explain when each one fires, and what data it receives.
When to keep callbacks: Event listeners (addEventListener, onClick) should stay as callbacks. They fire multiple times — every click, every keystroke, every scroll. Async/await is designed for one-time operations. Do not ask your AI to convert event listeners to async/await — that is the wrong tool for the job.
When to refactor: If you see nested callbacks (more than two levels deep) for operations like API calls, file reads, or database queries — those are perfect candidates for async/await. Tell your AI to refactor them.
What to Learn Next
Callbacks are the foundation. Here is where to go from here:
- What Is a Function? — if anything about passing functions around felt fuzzy, start here.
- What Is a Promise? — the next step in the evolution. Promises build on callbacks and eliminate the nesting problem.
- What Is Async/Await? — the modern standard. Makes asynchronous code read like regular top-to-bottom code.
- What Are Event Listeners? — the most common place you will see callbacks in action.
- What Is JavaScript? — the language that makes all of this work.
Next Step
Open a project your AI built and search for addEventListener, .then(, or setTimeout. You will find callbacks. Now you know how to read them — and when to ask your AI to refactor them into something cleaner.
FAQ
A callback is a function you pass to another function as an argument, with the instruction: "run this when you're done." Instead of waiting around for a task to finish, JavaScript hands off a callback so it can keep working on other things, then the callback fires when the result is ready.
Callbacks are the original way JavaScript handles anything that takes time — button clicks, API calls, file reads, timers. AI generates callbacks because they are built into almost every JavaScript API. Event listeners, fetch requests, setTimeout, and database queries all use callbacks or patterns that evolved from them.
Callback hell is when callbacks are nested inside other callbacks, creating a pyramid of indented code that becomes extremely hard to read and debug. It happens when multiple asynchronous operations depend on each other. Modern JavaScript solves this with Promises and async/await, which flatten the nesting into readable sequential code.
Callbacks and Promises solve the same problem — handling code that takes time to complete — but Promises are cleaner. A callback is a function you pass in. A Promise is an object that represents a future result, and you attach handlers with .then() and .catch(). Promises chain instead of nest, which avoids callback hell.
Yes, in most cases. Async/await is the modern standard and produces code that reads like plain English. Tell your AI: "Refactor this to use async/await instead of callbacks." The one exception is event listeners — those still use callbacks because they fire multiple times, and async/await is designed for one-time operations.