Blocking vs Non-Blocking Code in Node.js

1. What blocking code means
Blocking code refers to operations that stop the execution of the next line of code until the current operation finishes. During this time, the thread (the worker doing the task) is stuck, doing nothing but waiting for the external resource (disk, network, database) to respond.
Analogy: Waiting vs. Continuing Execution
Blocking (Synchronous): You are at a coffee shop. You order a latte. Instead of stepping aside, you stand glued to the counter staring at the barista until the latte is in your hand. You cannot answer a text, you cannot tie your shoe, you cannot take the next order. The entire line behind you stops moving. This is blocking.
Non-Blocking (Asynchronous): You order a latte. The cashier gives you a pager (a Promise or Callback). You step aside. You are free to continue executionโyou reply to emails, read the newspaper, or take the next order. When the pager buzzes, you pick up the latte exactly where you left off.
Impact on Server Performance
In a web server context (e.g., Node.js, Python Flask, Java Spring), blocking code is the #1 killer of concurrency.
| Aspect | Blocking Behavior | Non-Blocking Behavior |
|---|---|---|
| Thread State | Idle / Sleeping | Active / Free to handle other requests |
| Resource Usage | High memory (if spawning many waiting threads) | Low memory (single thread manages many tasks) |
| Concurrency | 1 Request = 1 Thread | 1 Thread = 1,000s of Requests |
| Outcome | Server crashes under moderate load (C10k problem). | Server remains responsive under heavy load. |
Topic 1: What Blocking Code Means
Definition: Blocking code (Synchronous) is JavaScript that must finish executing completely before the next line of code can run. It "blocks" the main thread.
In Node.js, the main thread is the Event Loop. If you block the Event Loop, Node stops listening for new connections, timers, or I/O completions.
The "Deadly Sins" of Blocking in Node:
fs.readFileSync(),fs.writeFileSync()crypto.randomBytes()without async flagwhileloops that run for more than a few millisecondsJSON.parse()on very large objects (>50MB)
Topic 2: The Analogy - Waiting vs. Continuing Execution
Let's move beyond the restaurant waiter analogy to something more specific to computer logic: The Toll Booth vs. The FastPass Lane.
| Concept | Blocking (Toll Booth) | Non-Blocking (FastPass Lane) |
|---|---|---|
| Action | Car arrives. Attendant takes money, makes change, prints receipt. The car sits there until everything is done. | Car arrives. Scans a barcode. Car drives through immediately. Receipt is emailed later. |
| Lane Status | Frozen. The 50 cars behind are stuck. | Flowing. Cars 2, 3, and 4 are already through. |
| Node.js Thread | Stuck counting change. | Free to scan the next car's pass. |
The Analogy for Code:
Blocking: "I asked for the file. I will stand here and do absolutely nothing else until you hand me the entire file."
Non-Blocking: "I asked for the file. Let me know when it's ready. I'm going to go check on the database and the user login while you work on that."
Topic 3: Impact on Server Performance (The Math)
Scenario: A single API endpoint that reads a 100MB log file and compresses it. This operation takes 4 seconds of disk I/O.
Using Blocking Code (readFileSync):
Concurrency: 1 request every 4 seconds.
Throughput: 15 requests per minute.
User Experience: If 10 users click the button at the same time:
User 1: Wait 4 sec.
User 2: Wait 8 sec.
User 10: Wait 40 seconds (Browser times out).
Using Non-Blocking Code (readFile):
Concurrency: Thousands of simultaneous requests.
Throughput: Limited only by disk hardware speed, not Node.js code.
User Experience: All 10 users wait roughly 4 seconds concurrently.
Topic 4: Code Comparison - File Handling Scenario
Let's illustrate this with a concrete Node.js server example.
Scenario A: The Blocking Nightmare (sync.js)
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.end('<h1>Home Page</h1>');
} else if (req.url === '/report') {
console.log('๐ [BLOCKING] Starting to read huge report...');
// ๐ THIS IS THE DANGER ZONE ๐
// Node cannot handle ANY other request while this reads.
const data = fs.readFileSync('huge_10gb_file.txt');
console.log('โ
[BLOCKING] Report ready. Sending.');
res.end(data);
}
});
server.listen(3000, () => console.log('Server on 3000'));
User Experience Test:
Open Tab 1: Click
/report. (Page starts loading...)Open Tab 2: Click
/(Home Page).Result: Tab 2 spins forever until Tab 1 finishes loading the 10GB file.
Scenario B: The Non-Blocking Standard (async.js)
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.end('<h1>Home Page</h1>');
} else if (req.url === '/report') {
console.log('โก [NON-BLOCKING] Initiating read...');
// โ
This returns immediately. The callback runs later.
const readStream = fs.createReadStream('huge_10gb_file.txt');
// Pipe data to response in chunks (memory efficient)
readStream.pipe(res);
console.log('โก [NON-BLOCKING] Event loop freed up.');
}
});
server.listen(3000, () => console.log('Server on 3000'));
User Experience Test:
Open Tab 1: Click
/report. (Starts downloading).Open Tab 2: Click
/(Home Page).Result: Tab 2 loads instantly. The file download proceeds in the background.
Topic 5: Diagram Ideas - Execution Timelines
Here are text-based diagrams you can easily translate into a slide or whiteboard drawing.
Diagram 1: Blocking Execution Timeline (The Frozen Wasteland)
Time (ms) Event Loop Thread (Only 1 Worker) Status
---------------------------------------------------------------
0 Receive Request 1 (Read File)
1 [fs.readFileSync START]
2 WAITING FOR DISK... ๐ซ BLOCKED
3 WAITING FOR DISK... ๐ซ BLOCKED
4 WAITING FOR DISK... ๐ซ BLOCKED
5 Receive Request 2 (Home Page) ๐ซ IGNORED (Queue full)
6 WAITING FOR DISK... ๐ซ BLOCKED
7 [fs.readFileSync FINISH]
8 Send Response 1
9 Process Request 2 (Finally!)
Visual Note: The timeline is a single, flat, idle line for the majority of the operation.
Diagram 2: Non-Blocking Execution Timeline (The Concurrency Machine)
Time (ms) Event Loop Thread (Single) Background (Libuv Thread Pool)
---------------------------------------------------------------
0 Receive Request 1
1 [fs.readFile START] Delegates work to Worker A
2 EVENT LOOP FREE -> Handles other tasks Worker A: Reading Disk...
3 Receive Request 2 -> Send Response 2 โ
Worker A: Reading Disk...
4 Check for completed timers/logic Worker A: Reading Disk...
5 Receive Request 3 -> Send Response 3 โ
Worker A: Reading Disk...
6 [Callback Triggered] Worker A: FINISHED -> Puts callback in Queue
7 Process Chunk of Data from File
8 EVENT LOOP FREE -> Handle next user Worker A: Reads next chunk
Diagram 3: Visual Architecture Comparison (Suggested Slide Graphic)
[ BLOCKING ARCHITECTURE ]
[User 1] ----\
[User 2] ----|----> [ ๐งต NODE THREAD (STUCK) ] ----> [ ๐พ DISK ]
[User 3] ----/ (Frozen)
[ NON-BLOCKING ARCHITECTURE ]
[User 1] ----\
[User 2] ----|----> [ โก EVENT LOOP (Active) ] <-----> [ ๐พ DISK (Background) ]
[User 3] ----/ (Always Responsive)
2. What non-blocking code means
Definition: Non-Blocking code is JavaScript that initiates an operation and immediately returns control to the Node.js Event Loop without waiting for that operation to finish. The result of the operation is handled later via a Callback, Promise, or await syntax.
The Core Mechanism:
Node.js offloads the actual heavy lifting (File System access, Network requests, Cryptography) to Libuv (a background thread pool) or the Operating System Kernel. Your JavaScript code simply says "Do this when you have a minute," and then moves on to the next line.
Suggestion 1: Compare Waiting vs. Continuing Execution Analogy
Let's use a more precise analogy than a restaurant: The Chef with a Smart Oven vs. a Dumb Pan.
| Concept | Blocking (The Dumb Pan) | Non-Blocking (The Smart Oven) |
|---|---|---|
| Action | Chef puts egg in pan. Chef stands there staring at the egg for 3 minutes. Cannot chop onions. | Chef puts bread in toaster. Presses "Toast". Chef turns around and chops onions immediately. |
| Thread State | Idle & Waiting. The CPU is doing nothing useful while the egg cooks. | Active & Productive. The CPU is preparing the rest of the meal. |
| The "Done" Signal | Chef notices the egg is cooked because they never looked away. | A loud "DING!" The toaster interrupts the chef's onion chopping to say "I'm done!" |
Translation to Code:
Blocking:
const egg = cookSync();-> The thread pauses here.Non-Blocking:
cookAsync((egg) => { ... });-> The thread continues immediately. The callback is the "DING!".
Suggestion 2: Explain Impact on Server Performance
This is the "Why It Matters" section. Blocking doesn't just make one request slow; it makes the entire server slow for everyone else.
The Waterfall of Doom (Blocking Scenario)
Imagine a server that takes exactly 2 seconds to read a file for a specific request.
| Time (Seconds) | Request A (Heavy File) | Request B (Simple Text) | Request C (Simple Text) |
|---|---|---|---|
| 0s | Starts Reading | Queued | Queued |
| 1s | Waiting on Disk | Queued | Queued |
| 2s | Finishes | Starts Processing | Queued |
| 2.001s | Done | Finishes | Starts Processing |
| 2.002s | - | Done | Finishes |
- Observation: Requests B and C were fast operations that should have taken 1 millisecond. Because of Request A's blocking code, B and C waited 2 whole seconds.
The Overlap of Efficiency (Non-Blocking Scenario)
Same server, same file read time (2 seconds).
| Time (Seconds) | Request A (Heavy File) | Request B (Simple Text) | Request C (Simple Text) |
|---|---|---|---|
| 0.000s | Initiates Read | - | - |
| 0.001s | (Disk Working) | Starts & Finishes โ | - |
| 0.002s | (Disk Working) | Done | Starts & Finishes โ |
| 2.000s | Callback Runs | - | - |
| 2.001s | Finishes | - | - |
- Observation: B and C were unaffected by the heavy workload. The server remained responsive.
Suggestion 3: Show Difference Using File Handling Scenario
Let's write the same exact task twice to highlight the difference in code flow, not just performance.
Goal: Read config.txt and log the content.
Scenario A: Blocking (Synchronous)
const fs = require('fs');
console.log('1. Script Start');
// ๐ THREAD FREEZES HERE until the OS gives us the file.
const data = fs.readFileSync('config.txt', 'utf8');
console.log('2. File Content:', data);
console.log('3. Script End');
Output Order:
1. Script Start
2. File Content: { ...the data... }
3. Script End
Analysis: This is predictable but inefficient. If this was a web server, it just froze for however long the disk took.
Scenario B: Non-Blocking (Asynchronous)
const fs = require('fs');
console.log('1. Script Start');
// โก THREAD CONTINUES IMMEDIATELY. No waiting.
fs.readFile('config.txt', 'utf8', (err, data) => {
// This block runs LATER, when the OS finishes reading.
console.log('3. File Content:', data);
});
console.log('2. Script End');
Output Order:
1. Script Start
2. Script End
3. File Content: { ...the data ... }
3. Why blocking slows servers
The single most important fact about Node.js performance: There is only one JavaScript thread. If you make that thread wait for the hard drive, every single user connected to your server waits with it.
Blocking code doesn't just slow down the current request. It creates a traffic jam where the first car breaks down and every car behind it is stuck forever.
Suggestion 1: Compare Waiting vs. Continuing Execution Analogy
Let's use the analogy of a Single Clerk at a Post Office Counter.
| Scenario | Blocking Clerk (Synchronous) | Non-Blocking Clerk (Asynchronous) | |
|---|---|---|---|
| The Setup | There is ONE clerk serving a line of 20 people. | There is ONE clerk serving a line of 20 people. | |
| Task #1 | Customer wants to mail a heavy package. | Customer wants to mail a heavy package. | |
| Action | Clerk says: "Wait here." Clerk lifts the box, walks to the back room to weigh it, and walks back. | Clerk says: "Put it on the conveyor belt." Clerk pushes a button. The belt takes the box away to be weighed automatically. | |
| The Line (Queue) | Frozen. All 19 other people stare at the empty counter getting angry. | Flowing. Clerk immediately calls out: "Next, please!" | |
| Task #1 Completion | 5 minutes later, the clerk returns with a receipt. | 5 minutes later, a bell rings and a receipt pops out of a printer next to the clerk. |
The Critical Distinction:
Blocking: The clerk (Thread) was physically absent from the counter.
Non-Blocking: The clerk (Thread) never left the counter. They delegated the slow work to a machine (Libuv/OS Kernel).
Suggestion 2: Explain Impact on Server Performance
This is the mathematical explanation of why a single synchronous file read can crash a production server under load.
The Metric: Requests Per Second (RPS)
Scenario: A Node.js API that reads a 50MB log file from a slow network-attached storage drive. This operation takes 200 milliseconds (0.2 seconds) of I/O time.
1. Blocking Implementation (readFileSync)
Time per request: 200ms (I/O) + 1ms (JS Execution) = 201ms.
Max Theoretical Throughput: 1,000ms / 201ms = ~5 Requests Per Second.
Concurrency Model: Sequential. If 100 requests arrive at once, the 100th request will wait in line for 20 seconds.
The "Slowness" Cascade:
Request 1 occupies thread for 200ms.
Requests 2-100 sit in a memory queue.
Result: The Average Response Time skyrockets to 10+ seconds because of the backlog.
2. Non-Blocking Implementation (createReadStream)
Time per request (Thread Occupied): 1ms (Initiate Read) + 1ms (Process Callback) = 2ms.
Max Theoretical Throughput: 1,000ms / 2ms = ~500 Requests Per Second (in terms of JS handling).
Concurrency Model: Parallel. If 100 requests arrive at once, the thread handles all 100 in the first 200ms (initiating the reads). They all finish around the 201ms mark simultaneously.
The Bottleneck Shift: The bottleneck is no longer the Node.js code. It is now the Disk Hardware. Node is no longer the problem.
Visual Comparison: Server Load Test
| Metric | Blocking Server | Non-Blocking Server |
|---|---|---|
| Active Connections | 1 active, 99 idle/stuck | 100 active |
| CPU Usage | Low (waiting on disk) | Low (waiting on disk) |
| Memory Usage | High (buffering 100 requests) | Low (streaming chunks) |
| User Perception | "Site is Down" | "Loading slowly, but working" |
Suggestion 3: Show Difference Using File Handling Scenario
Let's create a realistic Express.js server to demonstrate the exact moment the server dies.
The Setup: A route /download that serves a large file (e.g., big_image.jpg).
Scenario A: The Blocking Server Killer
const express = require('express');
const fs = require('fs');
const app = express();
app.get('/ping', (req, res) => {
// This is a health check endpoint. Should be instant.
res.send('pong');
});
app.get('/download', (req, res) => {
console.log('โ Blocking Request Started');
// ๐ THE SERVER KILLER ๐
// The entire Node process freezes here for EVERYONE.
const file = fs.readFileSync('big_image.jpg');
console.log('โ Blocking Request Finished');
res.send(file);
});
app.listen(3000);
The User Experience Experiment:
Open Browser Tab 1: Go to
http://localhost:3000/download. (Starts loading).Immediately Open Browser Tab 2: Go to
http://localhost:3000/ping.Observe: Tab 2 shows "Waiting for localhost..." (Spinner) until Tab 1 finishes downloading. The simple
pingroute is unreachable.
Scenario B: The Non-Blocking Solution
const express = require('express');
const fs = require('fs');
const app = express();
app.get('/ping', (req, res) => {
// This works instantly, no matter how many downloads are happening.
res.send('pong');
});
app.get('/download', (req, res) => {
console.log('โ
Non-Blocking Request Started');
// โ
The thread does not wait.
const readStream = fs.createReadStream('big_image.jpg');
readStream.pipe(res);
console.log('โ
Thread freed. Streaming data in background.');
});
app.listen(3000);
The User Experience Experiment:
Open Browser Tab 1: Go to
http://localhost:3000/download.Immediately Open Browser Tab 2: Go to
http://localhost:3000/ping.Observe: Tab 2 instantly displays
pong, even while Tab 1 is still downloading the 100MB file.
4. Async operations in Node.js
Core Definition
Async operations are tasks that Node.js delegates to the underlying system (Libuv thread pool or OS kernel) while the main JavaScript thread continues executing other code. This is the foundation of Node's famous "non-blocking I/O" model.
The Three Layers of Async in Node:
Callbacks โ The original pattern (e.g.,
fs.readFile(path, callback))Promises โ The modern standard (e.g.,
fs.promises.readFile(path))Async/Await โ Syntactic sugar over Promises (e.g.,
await fs.promises.readFile(path))
Suggestion 1: Compare Waiting vs. Continuing Execution Analogy
Let's use a Restaurant Kitchen with One Chef and Multiple Appliances.
| Action | Synchronous (Blocking) | Asynchronous (Non-Blocking) |
|---|---|---|
| Chef Role | The ONLY person who can do ANY work. | The ONLY person who can do ANY work. |
| Task: Boil Pasta (10 min) | Chef puts pot on stove. Stands motionless staring at the water for 10 minutes. Does not check phone, does not prep sauce. | Chef puts pot on stove. Sets TIMER. Immediately turns around and starts chopping vegetables. |
| Task Completion Signal | Chef notices water boiling because they never blinked. | Timer beeps LOUDLY. Chef interrupts vegetable chopping, drains pasta, returns to chopping. |
| Kitchen Efficiency | 1 dish every 10 minutes. | 5 dishes in 10 minutes. |
The "Async" Secret Sauce: The Timer
The timer represents the Event Loop's Callback Queue. The chef (Main Thread) doesn't wait for the water. The chef trusts the timer (Libuv) to interrupt them when the work is done.
Suggestion 2: Explain Impact on Server Performance
This section explains why async is not just a "nice feature" but a survival requirement for Node.js servers.
The Traffic Cop Problem
Imagine a busy intersection. The Traffic Cop is the Node.js Event Loop.
Scenario A: Synchronous Cop
Cop stops Car A for a broken taillight. Cop writes a ticket while standing in the middle of the intersection.
Result: All 4 lanes of traffic are completely stopped for 5 minutes. Honking ensues.
Scenario B: Asynchronous Cop
Cop stops Car A. Waves Car A to the shoulder (Background Thread). Cop returns to center of intersection and waves Car B, C, and D through.
Result: Traffic flows normally. Car A deals with the ticket on the side.
The Performance Math: Throughput Comparison
Setup: A server receives a request that requires a Database Query taking 100ms.
| Metric | Synchronous DB Driver (Hypothetical) | Asynchronous DB Driver (Real Node.js) |
|---|---|---|
| Thread Time per Request | 100ms (Waiting) | 0.05ms (Sending Query) + 0.05ms (Receiving) = 0.1ms |
| Max Requests / Second | 10 RPS | 10,000 RPS |
| Concurrency | 1 active connection | Thousands active connections |
Real-World Implication:
If you use a synchronous database driver in Node.js (if one even existed), a single slow query would freeze the entire website for every user currently logged in. Async operations ensure one slow user doesn't ruin the experience for fast users.
Suggestion 3: Show Difference Using File Handling Scenario
Let's build a Log Processor API that reads a massive access log file and counts the number of "404" errors.
Scenario A: The Synchronous Crash (sync-logger.js)
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
if (req.url === '/analyze') {
console.log('๐ [SYNC] Starting log analysis...');
// ๐ THE THREAD KILLER ๐
// Node reads the ENTIRE 2GB file into RAM before doing anything else.
const data = fs.readFileSync('access.log', 'utf8');
// Even this simple split() blocks the thread while processing 2GB of strings.
const lines = data.split('\n');
const count404 = lines.filter(line => line.includes(' 404 ')).length;
console.log('โ
[SYNC] Analysis complete.');
res.end(`404 Errors: ${count404}`);
} else {
res.end('OK');
}
});
server.listen(3000);
The Killer Problem:
Memory: Loads 2GB into RAM instantly. Server might run out of memory.
Thread: Frozen for 5-10 seconds while reading and splitting.
User Impact: During those 10 seconds, Health Checks fail, Load Balancers mark server as DOWN.
Scenario B: The Async Stream Solution (async-logger.js)
const http = require('http');
const fs = require('fs');
const readline = require('readline');
const server = http.createServer(async (req, res) => {
if (req.url === '/analyze') {
console.log('โก [ASYNC] Starting stream analysis...');
let count404 = 0;
// โ
Create a streaming interface. Reads file chunk by chunk (64kb at a time).
const fileStream = fs.createReadStream('access.log');
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
// โ
This event fires per line. Node handles OTHER requests between lines.
rl.on('line', (line) => {
if (line.includes(' 404 ')) {
count404++;
}
// IMPORTANT: Node is FREE to handle other incoming HTTP requests
// in between these 'line' events.
});
rl.on('close', () => {
console.log('โ
[ASYNC] Stream analysis complete.');
res.end(`404 Errors: ${count404}`);
});
console.log('โก [ASYNC] Event loop freed. Processing in background.');
} else {
res.end('OK');
}
});
server.listen(3000);
The Async Advantage:
Memory: Uses ~64kb of RAM regardless of file size.
Thread: Never blocked. Processes one line, checks for new HTTP requests, processes next line.
User Impact: Server remains responsive to health checks and other users.
6. Real-world examples (file read, DB calls)
The Two Most Common I/O Bottlenecks in Node.js
In production applications, two operations cause 99% of performance problems:
File System Access (Reading uploads, logs, config files)
Database Queries (MongoDB, PostgreSQL, MySQL)
Suggestion 1: Compare Waiting vs. Continuing Execution Analogy
Let's use a Busy Office Worker analogy that maps directly to file and database operations.
| Task | Blocking (Synchronous) | Non-Blocking (Asynchronous) |
|---|---|---|
| Read File | Worker walks to filing cabinet. Opens drawer. Stands there reading the entire 500-page document before returning to desk. Colleagues queue up waiting. | Worker walks to filing cabinet. Takes a photo of the document with phone. Returns to desk immediately. Reads the photo while helping colleagues. |
| Database Query | Worker calls IT Department. Stays on hold listening to elevator music for 3 minutes until they get the answer. Ignores all other calls. | Worker sends an Email to IT Department with subject "URGENT: Need User List". Sets email notification. Immediately answers next phone call. |
The Core Truth:
In both analogies, the Worker (Node.js Thread) is extremely fast at thinking (running JavaScript) but very slow at fetching (I/O). Async ensures the Worker is always thinking, never waiting.
Suggestion 2: Explain Impact on Server Performance
This section quantifies exactly how much slower a server becomes with blocking database calls.
The E-Commerce Checkout Nightmare
Imagine a Black Friday sale. Your /checkout endpoint does two things:
DB Call: Verify inventory (50ms average)
File Write: Log the transaction to
sales.log(10ms average)
Scenario A: Blocking Implementation
// ๐จ DO NOT DO THIS ๐จ
app.post('/checkout', (req, res) => {
// Step 1: DB Query (Synchronous hypothetical)
const inventory = db.querySync('SELECT * FROM inventory'); // 50ms BLOCK
// Step 2: File Write (Synchronous)
fs.appendFileSync('sales.log', 'Sale recorded\n'); // 10ms BLOCK
res.json({ success: true });
// Total Thread Blocked Time: 60ms per request
});
Performance Calculation:
Time per Request: 60ms
Max Requests per Second: 1000ms / 60ms = ~16 RPS
During Peak Hour (1000 users):
Request 1: Served in 60ms.
Request 100: Served in 6,000ms (6 seconds).
Request 500: Served in 30,000ms (30 seconds) - Browser times out. Sale lost.
Scenario B: Async Implementation
// โ
PRODUCTION STANDARD โ
app.post('/checkout', async (req, res) => {
// Step 1: Fire off DB Query (NON-BLOCKING)
const inventoryPromise = db.query('SELECT * FROM inventory'); // Returns immediately (0.2ms)
// Step 2: Fire off File Write (NON-BLOCKING)
const logPromise = fs.promises.appendFile('sales.log', 'Sale recorded\n'); // Returns immediately (0.1ms)
// Step 3: Wait for both to finish CONCURRENTLY
const [inventory] = await Promise.all([inventoryPromise, logPromise]);
res.json({ success: true });
// Total Thread Blocked Time: ~0.3ms (just the await resolution)
});
Performance Calculation:
Time per Request (Thread Occupied): 0.3ms
Max Requests per Second: 1000ms / 0.3ms = ~3,333 RPS
During Peak Hour (1000 users): All 1000 requests initiated in the first second. All complete by second 2.
The Difference: 3,333 RPS vs 16 RPS - That's 200x more capacity from the same hardware.





