Skip to main content
Career Paths
Concepts
Bep Async Programming
The Simplified Tech

Role-based learning paths to help you master cloud engineering with clarity and confidence.

Product

  • Career Paths
  • Interview Prep
  • Scenarios
  • AI Features
  • Cloud Comparison
  • Resume Builder
  • Pricing

Community

  • Join Discord

Account

  • Dashboard
  • Credits
  • Updates
  • Sign in
  • Sign up
  • Contact Support

Stay updated

Get the latest learning tips and updates. No spam, ever.

Terms of ServicePrivacy Policy

© 2026 TheSimplifiedTech. All rights reserved.

BackBack
Interactive Explainer

Async Programming: Callbacks, Promises, Async/Await

I/O waits for no one — understand the event loop or your server will crawl

🎯Key Takeaways
I/O-bound operations (database, network) are why async matters — blocking code wastes millions of CPU cycles waiting.
Node.js event loop: register async operations with the OS, continue executing, process callbacks when I/O completes.
Never block the event loop with synchronous CPU-intensive work — use Worker Threads.
Sequential awaits are often a bug — use Promise.all for independent async operations.
p-limit for concurrency control. Exponential backoff for retries. Promise.race for timeouts.

Async Programming: Callbacks, Promises, Async/Await

I/O waits for no one — understand the event loop or your server will crawl

~6 min read
Be the first to complete!
What you'll learn
  • I/O-bound operations (database, network) are why async matters — blocking code wastes millions of CPU cycles waiting.
  • Node.js event loop: register async operations with the OS, continue executing, process callbacks when I/O completes.
  • Never block the event loop with synchronous CPU-intensive work — use Worker Threads.
  • Sequential awaits are often a bug — use Promise.all for independent async operations.
  • p-limit for concurrency control. Exponential backoff for retries. Promise.race for timeouts.

Lesson outline

Why Your Server Serves Thousands of Users on One Thread

Node.js runs JavaScript on a single thread. Somehow, it handles 50,000 concurrent connections. Python's asyncio and Go's goroutines do similar things through different mechanisms. The secret: I/O is the bottleneck, not CPU.

The I/O Wait Insight

When your server reads from a database, it waits ~5ms for the network round-trip. During those 5ms, a modern CPU can execute 5,000,000 instructions. Synchronous (blocking) code wastes all that CPU time waiting. Async code uses that time to serve other requests.

OperationTimeCPU cycles wasted if blocking
L1 cache access0.5ns1 (reference)
RAM access100ns200
SSD read150μs300,000
Database query (LAN)5ms10,000,000
HTTP API call100ms200,000,000
Database query (cross-region)150ms300,000,000

The Event Loop: How One Thread Does the Work of Many

The Node.js Event Loop (simplified)

→

01

Your code runs synchronously until it hits an async operation (setTimeout, DB query, HTTP call)

→

02

Node.js registers a callback with the OS (via libuv) and immediately continues executing

→

03

OS handles the I/O operation (disk read, network request) in the background

→

04

When I/O completes, OS notifies Node.js via an event

→

05

Node.js puts the callback in the event queue

06

When the call stack is empty, Node.js picks the next callback from the queue and executes it

1

Your code runs synchronously until it hits an async operation (setTimeout, DB query, HTTP call)

2

Node.js registers a callback with the OS (via libuv) and immediately continues executing

3

OS handles the I/O operation (disk read, network request) in the background

4

When I/O completes, OS notifies Node.js via an event

5

Node.js puts the callback in the event queue

6

When the call stack is empty, Node.js picks the next callback from the queue and executes it

Don't Block the Event Loop

If you run synchronous CPU-intensive code (image processing, crypto, JSON.parse of a 50MB file) on the Node.js main thread, ALL requests are blocked until it finishes. Move CPU work to worker threads or a separate process.

Event Loop Phases (Node.js)

  • timers — setTimeout, setInterval callbacks
  • I/O callbacks — Most OS I/O completions (network, disk)
  • poll — Retrieves new I/O events; blocks here if queue is empty
  • check — setImmediate callbacks — always after I/O
  • close callbacks — socket.on("close", ...) cleanup
  • ⚡microtasks (between phases) — Promise .then() and process.nextTick — these drain completely before the next phase

Callbacks → Promises → Async/Await: The Evolution

JavaScript's async story evolved over 15 years. Understanding all three helps you read older codebases and understand what async/await actually does.

Async/Await Is Syntactic Sugar

async/await doesn't add new functionality. It's syntax that makes Promise chains readable. The async keyword makes a function return a Promise. await pauses execution of that function (not the whole thread) until the Promise resolves.

async-evolution.ts
1// The evolution of async patterns in JavaScript
2
3// ❌ Callback Hell (Node.js 2009-2015)
Callback hell: 5+ levels of nesting, error handling repeated at every level
4function getUser(id: string, callback: (err: Error | null, user?: User) => void) {
5 db.query('SELECT * FROM users WHERE id = ?', [id], (err, rows) => {
6 if (err) return callback(err);
7 const user = rows[0];
8 db.query('SELECT * FROM posts WHERE user_id = ?', [user.id], (err, posts) => {
9 if (err) return callback(err);
10 // Imagine 3 more levels of nesting...
11 callback(null, { ...user, posts });
12 });
13 });
14}
15
16// ✅ Promises (ES2015/ES6)
17function getUserWithPosts(id: string): Promise<UserWithPosts> {
18 return db.query('SELECT * FROM users WHERE id = ?', [id])
19 .then(rows => rows[0])
20 .then(user =>
21 db.query('SELECT * FROM posts WHERE user_id = ?', [user.id])
22 .then(posts => ({ ...user, posts }))
Promises flatten the nesting but .then() chains are still awkward for complex logic
23 );
24}
25
26// ✅✅ Async/Await (ES2017) — same as Promises, much more readable
async/await: same Promise machinery, reads like synchronous code
27async function getUserWithPostsAsync(id: string): Promise<UserWithPosts> {
28 const rows = await db.query('SELECT * FROM users WHERE id = ?', [id]);
29 const user = rows[0];
30 const posts = await db.query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
31 return { ...user, posts };
32}
Sequential awaits: 50ms + 50ms = 100ms total. Unnecessary when requests are independent
33
34// ⚠️ Common mistake: sequential awaits when parallel is possible
35async function slowVersion(userId: string) {
36 const user = await getUser(userId); // waits 50ms
37 const orders = await getOrders(userId); // waits another 50ms = 100ms total
38 return { user, orders };
39}
Promise.all starts both requests simultaneously. Total time = max(50ms, 50ms) = 50ms
40
41// ✅ Parallel with Promise.all
42async function fastVersion(userId: string) {
43 const [user, orders] = await Promise.all([
44 getUser(userId), // both start simultaneously
45 getOrders(userId), // completes in ~50ms total
46 ]);
47 return { user, orders };
Always wrap top-level async code in try/catch. Unhandled rejections kill the process
48}
49
50// ✅ Error handling with async/await
51async function safeGetUser(id: string) {
52 try {
53 const user = await getUserWithPostsAsync(id);
54 return { success: true, data: user };
55 } catch (error) {
56 // Always catch — unhandled Promise rejections crash Node.js
57 console.error('Failed to get user:', error);
58 return { success: false, error: 'User not found' };
59 }
60}

Concurrency Patterns for Real Systems

Advanced Async Patterns You'll Need

  • 🚀Promise.all — Wait for ALL promises. Fails fast if any reject. Use when all results are required.
  • 🏆Promise.allSettled — Wait for ALL promises, get all results (resolved or rejected). Use when you need all results even if some fail.
  • ⚡Promise.race — Resolves/rejects as soon as the first promise settles. Use for timeout patterns.
  • 🎯Promise.any — Resolves when any ONE promise resolves (ignores rejections). Use for fastest-wins fallback.
  • 🔄Throttling with p-limit — Limit concurrent async operations. Don't fire 1000 DB queries simultaneously — use a concurrency limit.
  • 🔁Retry with backoff — Transient failures (network blips) should be retried with exponential backoff, not ignored or immediately failed.

Promise.all vs Promise.allSettled

Promise.all fails fast — if one promise rejects, the entire call rejects immediately (other promises still run but their results are ignored). Promise.allSettled waits for all, then gives you each result with a status. Use allSettled when partial failure is acceptable.

concurrency-patterns.ts
1import pLimit from 'p-limit';
2
3// Pattern 1: Throttle concurrent operations
p-limit queues promises beyond the limit. Prevents overwhelming databases with thousands of simultaneous queries
4// Process 1000 user IDs but only 10 at a time
5const limit = pLimit(10); // max 10 concurrent
6
7async function processUsers(userIds: string[]) {
8 const results = await Promise.all(
9 userIds.map(id => limit(() => processUser(id))) // p-limit queues extras
10 );
11 return results;
12}
13
14// Pattern 2: Retry with exponential backoff
15async function withRetry<T>(
16 operation: () => Promise<T>,
17 maxRetries = 3,
18 baseDelayMs = 100
Exponential backoff: 100ms, 200ms, 400ms. Avoids thundering herd on transient failures
19): Promise<T> {
20 for (let attempt = 0; attempt <= maxRetries; attempt++) {
21 try {
22 return await operation();
23 } catch (error) {
24 if (attempt === maxRetries) throw error; // exhausted retries
25 const delay = baseDelayMs * 2 ** attempt; // 100ms, 200ms, 400ms
26 await new Promise(resolve => setTimeout(resolve, delay));
27 }
28 }
29 throw new Error('Should not reach here');
Promise.race: first to settle wins. Timeout promise races against the real operation
30}
31
32// Pattern 3: Timeout with Promise.race
33async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
34 const timeout = new Promise<never>((_, reject) =>
35 setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms)
36 );
37 return Promise.race([promise, timeout]);
38}
39
40// Usage: DB call with 5-second timeout
41const user = await withTimeout(db.users.findById(id), 5000);
How this might come up in interviews

Async questions test fundamental JavaScript/Node.js understanding. Senior engineers know why async exists, not just the syntax.

Common questions:

  • Explain how the Node.js event loop works
  • What is the difference between Promise.all and Promise.allSettled?
  • How would you handle a CPU-intensive task in a Node.js server?
  • What causes "callback hell" and how do Promises solve it?

Strong answers include:

  • Mentions event loop and I/O offloading
  • Knows Promise.all vs allSettled vs race
  • Mentions Worker Threads for CPU-bound work
  • Uses exponential backoff for retry logic

Red flags:

  • Thinks async/await is multi-threaded
  • Sequential awaits when Promise.all would work
  • Doesn't know how to handle Promise rejections

Quick check · Async Programming: Callbacks, Promises, Async/Await

1 / 3

You need to fetch user data and order data to build a response. They are independent. Which approach is fastest?

Key takeaways

  • I/O-bound operations (database, network) are why async matters — blocking code wastes millions of CPU cycles waiting.
  • Node.js event loop: register async operations with the OS, continue executing, process callbacks when I/O completes.
  • Never block the event loop with synchronous CPU-intensive work — use Worker Threads.
  • Sequential awaits are often a bug — use Promise.all for independent async operations.
  • p-limit for concurrency control. Exponential backoff for retries. Promise.race for timeouts.

From the books

You Don't Know JS: Async & Performance — Kyle Simpson (2015)

Chapter 1: Asynchrony: Now & Later

JavaScript's concurrency model is event-loop based, not thread-based. Understanding this model is fundamental to writing correct async code.

Ready to see how this works in the cloud?

Switch to Career Paths for structured paths (e.g. Developer, DevOps) and provider-specific lessons.

View role-based paths

Sign in to track your progress and mark lessons complete.

Discussion

Questions? Discuss in the community or start a thread below.

Join Discord

In-app Q&A

Sign in to start or join a thread.