Callback
A callback is a function passed as an argument to another function, to be invoked later — either synchronously (like Array.map's callback) or asynchronously (like setTimeout's callback) — when a certain event or condition occurs.
Explanation
Callbacks are JavaScript's original mechanism for handling asynchronous operations and customizable behavior. Array.map(callback) calls your callback once per element — a synchronous callback. fs.readFile(path, callback) calls your callback when the file is read — an asynchronous callback. Both use the same pattern: pass a function, have it called later. The convention for Node.js-style callbacks (error-first callbacks, or "errbacks") is that the first argument is an error (null if none) and subsequent arguments are results: readFile(path, (err, data) => { if (err) throw err; /* use data */ }). This pattern predates Promises and is still common in older Node.js APIs. Callback hell (or "pyramid of doom") occurs when multiple async operations must be sequenced: readFile(path, (err, data) => { parseData(data, (err, parsed) => { saveResult(parsed, (err, result) => { /* 3+ levels deep */ }) }) }). Each level of indentation represents one async step, and error handling must be duplicated at each level. Promises and async/await were invented to solve this. Callbacks remain the right choice for synchronous higher-order functions (map, filter, reduce, sort, forEach), event listeners (addEventListener), and observability hooks (middleware next(), React's setState updater function). The key distinction: synchronous callbacks are executed inline during the current call; asynchronous callbacks are scheduled and run later.
Code Example
javascript// Synchronous callbacks: common array methods
const nums = [1, 2, 3, 4, 5];
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10]
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
nums.sort((a, b) => b - a); // [5, 4, 3, 2, 1]
// Asynchronous callbacks: old-style Node.js
const fs = require('fs');
fs.readFile('./data.json', 'utf8', (err, data) => { // error-first
if (err) return console.error(err);
const parsed = JSON.parse(data);
console.log(parsed);
});
// Callback hell (avoid this)
readUser(id, (err, user) => {
if (err) return handleError(err);
readPosts(user.id, (err, posts) => {
if (err) return handleError(err);
readComments(posts[0].id, (err, comments) => {
// 3+ levels deep, error handling repeated
});
});
});
// Promise equivalent (flat, single error handler)
readUser(id)
.then(user => readPosts(user.id))
.then(posts => readComments(posts[0].id))
.then(comments => console.log(comments))
.catch(handleError);
Why It Matters for Engineers
Callbacks are foundational JavaScript. Every event listener you write, every Array.map() you use, every middleware next() you call — these are callbacks. Understanding the synchronous vs asynchronous distinction prevents bugs where you expect a callback's result to be available immediately when it's actually executed later. Understanding callbacks also helps you understand why Promises and async/await were invented: they solve callback hell's readability problem without replacing the underlying callback mechanism (Promises still use callbacks internally).
Related Terms
Promise · Async/Await · Event Loop · Closure
Learn This In Practice
Go deeper with the full module on Beyond Vibe Code.
Programming Foundations → →