Asynchronous programming is the single most important concept in Node.js. This lesson explains what "asynchronous" actually means in practice and sets up the callbacks, promises, async/await, and event loop lessons that follow.
Why Node.js Is Asynchronous by Design
Most I/O operations, reading a file, querying a database, calling another API, take time to complete relative to CPU speed. A synchronous approach would block the entire program while waiting. Node.js instead starts the operation, immediately continues running other code, and gets notified later when the result is ready.
This non-blocking model is why a single Node.js process can serve thousands of concurrent requests: while one request waits on a database query, the same thread is free to start handling other requests instead of sitting idle.
console.log('1: Request received');
setTimeout(() => {
console.log('3: Database result ready (simulated)');
}, 1000);
console.log('2: Continuing to handle other work');
The output prints 1, 2, 3, not 1, 3, 2. Node.js doesn't wait for the 1-second timer, it moves on immediately and comes back once the delay elapses.
Callbacks are the original async pattern in Node.js, a function passed in to be called later.
Promises represent a value that will be available eventually, with .then()/.catch().
async/await is syntax sugar over promises that reads like synchronous code.
All three approaches ultimately rely on the same event loop underneath.
Async Node.js Cheatsheet
A quick comparison of the async patterns covered in the next few lessons.
Pattern
Example
Introduced
Callback
fn(arg, (err, result) => {})
Node.js's original style
Promise
fn(arg).then(result => {})
ES2015
async/await
const result = await fn(arg);
ES2017
Event emitter
emitter.on('event', handler)
Node.js core pattern
Timers
setTimeout(fn, ms)
Classic async scheduling
Blocking vs Non-Blocking Code
"Blocking" code halts further execution until an operation finishes; "non-blocking" code starts the operation and immediately continues, handling the result later through a callback, promise, or await. Node.js's core APIs default to non-blocking versions wherever possible, with blocking (*Sync) alternatives available for scripts and startup code.
Blocking: fs.readFileSync(), halts execution until the file is fully read.
Non-blocking: fs.readFile() / fs.promises.readFile(), returns immediately and resolves later.
CPU-bound synchronous code (like a heavy loop) blocks regardless of which I/O style you use elsewhere.
Why This Matters at Scale
Imagine 1,000 simultaneous requests, each waiting 200ms on a database query. A blocking model would need 1,000 threads (or serve requests one at a time, taking 200 seconds total). Node.js's non-blocking model lets one thread issue all 1,000 queries almost immediately and respond to each as its result comes back, dramatically improving throughput for I/O-heavy workloads.
Common Mistakes
Writing synchronous, blocking code (*Sync methods, heavy loops) inside request handlers of a running server.
Assuming asynchronous code runs in a different thread, most of it runs on the same main thread, just not in blocking order.
Mixing callbacks, promises, and async/await inconsistently across a codebase, hurting readability.
Forgetting that asynchronous operations can resolve in a different order than they were started.
Key Takeaways
Node.js defaults to non-blocking I/O so a single thread can serve many concurrent operations.
Callbacks, promises, and async/await are three different syntaxes for the same underlying async model.
Blocking, synchronous code should be reserved for startup scripts, not request handlers.
Non-blocking I/O is what makes Node.js efficient for I/O-heavy workloads like APIs and real-time apps.
Pro Tip
When in doubt about whether an operation is blocking, check whether it has a Sync suffix or explicitly returns a promise/accepts a callback. If it's neither, it's very likely synchronous and can block the event loop.
You now understand why Node.js is asynchronous. Next, learn callbacks, the original pattern for handling async results in Node.js.