⚡ Why This Topic Matters
Grasping the difference between synchronous, asynchronous, blocking, and non-blocking code is core to understanding Node.js — and it underpins everything else in this course.
🔒 Synchronous (Blocking) Code
📖 What It Means
Code is executed line by line, one statement after another.
Each line waits for the previous line to finish before running.
💻 Example
const fs = require('fs');
const textIn = fs.readFileSync('./txt/input.txt', 'utf8');
console.log(textIn);Here:
First, the
fsmodule is loaded.Then, the file is read.
Only after that finishes, the result is logged.
⚠️ The Problem
This is called blocking code because a slow operation (like reading a large file) blocks everything else from running until it completes.
This becomes a serious issue in Node.js due to how Node is designed (explained below).
🚀 Asynchronous (Non-Blocking) Code
📖 What It Means
Slow or heavy tasks get offloaded to run in the background.
Rather than waiting around, the rest of the code continues executing right away.
Once the background task wraps up, a pre-registered callback function fires to handle the result.
💻 Example
console.log('Will read file!');
fs.readFile('./txt/input.txt','utf8',(err, data)=>{
console.log(data);
});
console.log('Reading file...');readFilekicks off reading the file in the background.The program doesn't pause — it moves straight to the next line.
That's why
"Reading file..."logs before the file's contents — reading takes time.Once the file finishes loading, the callback runs and logs the actual data.
✅ This is what makes it non-blocking — nothing else in the program freezes while the read happens.
🧵 Why Does Node.js Need This? The Single-Thread Explanation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Why Node needs async</title>
</head>
<body>
<div class="busy-info" id="busyinfo">
<style>
.busy-info {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--err: #dc2626;
--err-soft: #fbe4e2;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
margin: 0 auto;
box-sizing: border-box;
padding: 2rem;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.busy-info[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-soft: #202942;
--line: #2c313d;
--err: #f87171;
--err-soft: #3a1f1f;
}
.busy-info * { box-sizing: border-box; }
.bz-eyebrow {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase;
color: var(--accent); text-align: center; margin: 1.5rem 0 0.3rem;
}
.bz-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 5vw, 1.85rem); text-align: center;
margin: 0 0 0.5rem; letter-spacing: -0.01em;
}
.bz-sub { color: var(--ink-soft); font-size: 0.88rem; text-align: center; margin: 0 0 1.4rem; }
.bz-toggle { display: flex; justify-content: center; gap: 0.5rem; margin-bottom: 1.4rem; }
.bz-toggle-btn {
font-family: var(--body-font); font-size: 0.82rem; font-weight: 600; padding: 0.5rem 1rem;
border-radius: 20px; border: 1px solid var(--line); background: var(--paper); color: var(--ink-soft); cursor: pointer;
}
.bz-toggle-btn.is-selected { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
/* ---- thread / background boxes ---- */
.bz-lanes { display: flex; gap: 0.6rem; margin-bottom: 1.2rem; }
.bz-lane { flex: 1; border: 1px solid var(--line); border-radius: 8px; background: var(--paper-alt); padding: 0.7rem 0.8rem; text-align: center; transition: border-color 0.2s, background 0.2s; }
.bz-lane.is-busy { border-color: var(--accent); background: var(--accent-soft); }
.bz-lane-label { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 0.3rem; }
.bz-lane-value { font-family: var(--mono-font); font-size: 0.78rem; font-weight: 700; color: var(--ink-soft); transition: color 0.2s; min-height: 1.2em; }
.bz-lane.is-busy .bz-lane-value { color: var(--accent); }
.bz-lane.is-disabled { opacity: 0.35; }
/* ---- user rows ---- */
.bz-users { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.2rem; }
.bz-user { display: flex; align-items: center; gap: 0.6rem; padding: 0.55rem 0.8rem; border: 1px solid var(--line); border-radius: 8px; background: var(--paper-alt); transition: border-color 0.2s, background 0.2s; }
.bz-user.is-serving { border-color: var(--accent); background: var(--accent-soft); }
.bz-user-icon { width: 30px; height: 30px; border-radius: 7px; display: flex; align-items: center; justify-content: center; background: var(--paper); border: 1px solid var(--line); flex-shrink: 0; }
.bz-user-icon svg { width: 15px; height: 15px; color: var(--ink-soft); }
.bz-user.is-serving .bz-user-icon svg { color: var(--accent); }
.bz-user-name { font-weight: 700; font-size: 0.82rem; flex-shrink: 0; width: 60px; }
.bz-user-task { font-size: 0.72rem; color: var(--ink-soft); flex: 1; }
.bz-user-status { font-family: var(--mono-font); font-size: 0.7rem; font-weight: 700; color: var(--ink-soft); white-space: nowrap; }
.bz-user.is-serving .bz-user-status { color: var(--accent); }
.bz-user.is-done .bz-user-status { color: var(--accent); }
.bz-controls { display: flex; justify-content: center; margin-bottom: 0.6rem; }
.bz-btn {
font-family: var(--mono-font); font-size: 0.82rem; font-weight: 600; padding: 0.6rem 1.2rem;
border-radius: 5px; border: 1px solid var(--accent); background: var(--accent); color: #fff; cursor: pointer;
}
.bz-btn:disabled { opacity: 0.55; cursor: default; }
.bz-summary { text-align: center; font-size: 0.85rem; color: var(--ink-soft); min-height: 1.4em; margin: 0.9rem 0 2.2rem; }
.bz-summary b { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.busy-info *, .busy-info *::before, .busy-info *::after { transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
}
</style>
<div class="bz-toggle">
<button type="button" class="bz-toggle-btn is-selected" data-mode="blocking">Blocking</button>
<button type="button" class="bz-toggle-btn" data-mode="async">Async</button>
</div>
<div class="bz-lanes">
<div class="bz-lane" id="bzThread">
<div class="bz-lane-label">Thread</div>
<div class="bz-lane-value" id="bzThreadValue">idle</div>
</div>
<div class="bz-lane" id="bzBackground">
<div class="bz-lane-label">Background</div>
<div class="bz-lane-value" id="bzBackgroundValue">—</div>
</div>
</div>
<div class="bz-users" id="bzUsers">
<div class="bz-user" data-user="A">
<div class="bz-user-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg></div>
<div class="bz-user-name">User A</div>
<div class="bz-user-task">Reads a file</div>
<div class="bz-user-status" data-status>waiting</div>
</div>
<div class="bz-user" data-user="B">
<div class="bz-user-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>
<div class="bz-user-name">User B</div>
<div class="bz-user-task">Quick request</div>
<div class="bz-user-status" data-status>waiting</div>
</div>
<div class="bz-user" data-user="C">
<div class="bz-user-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>
<div class="bz-user-name">User C</div>
<div class="bz-user-task">Quick request</div>
<div class="bz-user-status" data-status>waiting</div>
</div>
<div class="bz-user" data-user="D">
<div class="bz-user-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>
<div class="bz-user-name">User D</div>
<div class="bz-user-task">Quick request</div>
<div class="bz-user-status" data-status>waiting</div>
</div>
</div>
<div class="bz-controls">
<button type="button" class="bz-btn" id="bzRunBtn">▶ Send all 4 requests</button>
</div>
<p class="bz-summary" id="bzSummary"> </p>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('busyinfo');
if (!root) return;
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
var toggleBtns = root.querySelectorAll('.bz-toggle-btn');
var runBtn = root.querySelector('#bzRunBtn');
var thread = root.querySelector('#bzThread');
var threadValue = root.querySelector('#bzThreadValue');
var background = root.querySelector('#bzBackground');
var backgroundValue = root.querySelector('#bzBackgroundValue');
var users = root.querySelectorAll('.bz-user');
var summary = root.querySelector('#bzSummary');
var mode = 'blocking';
var running = false;
var timers = [];
function userEl(id) { return root.querySelector('.bz-user[data-user="' + id + '"]'); }
function setStatus(id, text, state) {
var el = userEl(id);
el.classList.remove('is-serving', 'is-done');
if (state) el.classList.add(state);
el.querySelector('[data-status]').textContent = text;
}
function schedule(fn, delay) {
var t = setTimeout(fn, delay);
timers.push(t);
}
function reset() {
timers.forEach(function (t) { clearTimeout(t); });
timers = [];
thread.classList.remove('is-busy');
threadValue.textContent = 'idle';
background.classList.remove('is-busy');
background.classList.toggle('is-disabled', mode === 'blocking');
backgroundValue.textContent = '\u2014';
users.forEach(function (u) {
u.classList.remove('is-serving', 'is-done');
u.querySelector('[data-status]').textContent = 'waiting';
});
summary.innerHTML = '\u00a0';
}
function runBlocking() {
// User A occupies the thread for the full 1800ms read
schedule(function () {
thread.classList.add('is-busy'); threadValue.textContent = 'A: reading file\u2026';
setStatus('A', 'serving\u2026', 'is-serving');
}, 0);
schedule(function () {
setStatus('A', 'done at 1800ms', 'is-done');
thread.classList.remove('is-busy'); threadValue.textContent = 'idle';
}, 1800);
['B', 'C', 'D'].forEach(function (id, i) {
var start = 1800 + i * 200;
schedule(function () {
thread.classList.add('is-busy'); threadValue.textContent = id + ': quick request';
setStatus(id, 'serving\u2026', 'is-serving');
}, start);
schedule(function () {
setStatus(id, 'done at ' + (start + 200) + 'ms', 'is-done');
thread.classList.remove('is-busy'); threadValue.textContent = 'idle';
}, start + 200);
});
schedule(function () {
summary.innerHTML = 'Total: <b>2400ms</b>. B, C and D sat idle for 1800ms before they even started \u2014 stuck behind A.';
running = false;
runBtn.disabled = false;
toggleBtns.forEach(function (b) { b.disabled = false; });
}, 2450);
}
function runAsync() {
// User A: thread briefly touches it, hands off to background, frees almost immediately
schedule(function () {
thread.classList.add('is-busy'); threadValue.textContent = 'A: handing off\u2026';
setStatus('A', 'serving\u2026', 'is-serving');
}, 0);
schedule(function () {
thread.classList.remove('is-busy'); threadValue.textContent = 'idle';
background.classList.add('is-busy'); backgroundValue.textContent = 'A: reading file\u2026';
}, 100);
schedule(function () {
background.classList.remove('is-busy'); backgroundValue.textContent = 'done';
thread.classList.add('is-busy'); threadValue.textContent = 'A: callback';
}, 1900);
schedule(function () {
setStatus('A', 'done at 2000ms', 'is-done');
thread.classList.remove('is-busy'); threadValue.textContent = 'idle';
}, 2000);
['B', 'C', 'D'].forEach(function (id, i) {
var start = 100 + i * 200;
schedule(function () {
thread.classList.add('is-busy'); threadValue.textContent = id + ': quick request';
setStatus(id, 'serving\u2026', 'is-serving');
}, start);
schedule(function () {
setStatus(id, 'done at ' + (start + 200) + 'ms', 'is-done');
thread.classList.remove('is-busy'); threadValue.textContent = 'idle';
}, start + 200);
});
schedule(function () {
summary.innerHTML = 'B, C and D were all done by <b>700ms</b> \u2014 A\u2019s slow read never blocked them, even though it took just as long.';
running = false;
runBtn.disabled = false;
toggleBtns.forEach(function (b) { b.disabled = false; });
}, 2050);
}
runBtn.addEventListener('click', function () {
if (running) return;
running = true;
runBtn.disabled = true;
toggleBtns.forEach(function (b) { b.disabled = true; });
reset();
if (mode === 'blocking') runBlocking(); else runAsync();
});
toggleBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
if (running) return;
mode = btn.getAttribute('data-mode');
toggleBtns.forEach(function (b) { b.classList.toggle('is-selected', b === btn); });
reset();
});
});
reset();
})();
</script>
</body>
</html>🔑 Key Concept: Node.js Runs on a Single Thread
A thread is the path of instructions the CPU runs — it's where your code executes.
A Node.js app runs on just one thread, no matter how many users it has — 5 or 5 million, everyone shares it.
⚠️ The Problem: Blocking Code on a Single Thread
If one user triggers blocking code — say, a file read that takes a second — everyone else has to wait that same second before the thread is free again.
Even simple actions like logging in get stuck in line.
With a few users, you won't notice. At scale, it becomes a real bottleneck.
Note: This is simplified — we'll cover Node's internals more deeply later.
✅ The Fix: Asynchronous Code
Async file reading moves the heavy work off the main thread.
The main thread stays free to handle other requests.
Once the background task finishes, its callback runs to process the result.
End result: every user gets served efficiently, with no single slow task holding up the rest.
🔄 What "I/O" Means
I/O = Input/Output — operations like:
📂 Reading/writing files
🌐 Handling network requests
Node.js's entire design (built around callbacks) exists specifically to support non-blocking I/O, making it fast and scalable.
⚖️ Comparison with Other Languages
Languages like PHP use a different model — a new thread is created for each user.
Node.js's creator chose the single-thread + non-blocking callback model instead, believing it's better suited for building highly performant, scalable web applications.
💡 Important Clarification About Callbacks
Passing a function into another function (a "callback") is common in JavaScript — but this does NOT automatically make code asynchronous.
Only certain built-in Node API functions (like
readFile) are specifically designed to behave asynchronously.Many more such functions will be explored throughout the course.