π Node.js Streams
Core idea: Streams let you process data piece by piece (in chunks) instead of waiting for the entire read/write to finish. You never have to hold the whole thing in memory at once.
π₯ Real-world analogy: Netflix and YouTube are "streaming" services because video is sent in chunks β you start watching before the full file has downloaded. Same idea, applied to any kind of data.
π Streams aren't a Node-specific concept β it's a general CS idea. Node just implements it in its core modules.
β Why use streams?
πΎ Memory efficient β read a chunk, process it, free it, repeat. No need to load everything at once.
β‘ Time efficient β start processing as soon as the first chunk arrives, instead of waiting for the whole thing.
Best for: large files (video, big datasets) and data arriving incrementally (network requests, uploads).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Streams</title>
</head>
<body>
<div class="st-info" id="stinfo">
<style>
.st-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);
}
.st-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;
}
.st-info * { box-sizing: border-box; }
.st-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;
}
.st-title {
font-family: var(--display-font); font-size: clamp(1.5rem, 5vw, 2rem); text-align: center;
margin: 0 0 0.5rem; letter-spacing: -0.01em;
}
.st-sub { color: var(--ink-soft); font-size: 0.88rem; text-align: center; margin: 0 0 1.8rem; max-width: 460px; margin-left: auto; margin-right: auto; }
.st-section-label { text-align: center; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 0.8rem; }
/* ---- part 1: memory ---- */
.st-toggle { display: flex; justify-content: center; gap: 0.5rem; margin-bottom: 1.2rem; }
.st-toggle-btn {
font-family: var(--body-font); font-size: 0.8rem; font-weight: 600; padding: 0.45rem 0.85rem;
border-radius: 18px; border: 1px solid var(--line); background: var(--paper); color: var(--ink-soft); cursor: pointer;
}
.st-toggle-btn.is-selected { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.st-file-row { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 1rem; }
.st-file-icon { width: 32px; height: 32px; border-radius: 7px; display: flex; align-items: center; justify-content: center; background: var(--paper-alt); border: 1px solid var(--line); flex-shrink: 0; }
.st-file-icon svg { width: 15px; height: 15px; color: var(--ink-soft); }
.st-file-name { font-family: var(--mono-font); font-size: 0.8rem; font-weight: 700; }
.st-mem-wrap { border: 1px solid var(--line); border-radius: 8px; background: var(--paper-alt); padding: 0.9rem 1rem; margin-bottom: 0.7rem; }
.st-mem-label { display: flex; justify-content: space-between; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-soft); margin-bottom: 0.5rem; }
.st-mem-track { height: 16px; border-radius: 4px; background: var(--paper); border: 1px solid var(--line); overflow: hidden; }
.st-mem-fill { height: 100%; width: 3%; background: var(--accent); transition: width 0.4s ease, background 0.3s ease; }
.st-mem-fill.is-danger { background: var(--err); }
.st-chunk-log { text-align: center; font-family: var(--mono-font); font-size: 0.74rem; color: var(--ink-soft); min-height: 1.3em; margin-bottom: 0.6rem; }
.st-mem-caption { text-align: center; font-size: 0.82rem; color: var(--ink-soft); min-height: 2.2em; margin-bottom: 1rem; }
.st-mem-caption b { color: var(--ink); }
.st-mem-caption.is-danger-text b { color: var(--err); }
.st-controls { display: flex; justify-content: center; margin-bottom: 2.2rem; }
.st-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;
}
.st-btn:disabled { opacity: 0.55; cursor: default; }
@media (prefers-reduced-motion: reduce) {
.st-info *, .st-info *::before, .st-info *::after { transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
}
</style>
<p class="st-section-label">Why streams exist</p>
<div class="st-toggle">
<button type="button" class="st-toggle-btn is-selected" data-mode="all">Read it all at once</button>
<button type="button" class="st-toggle-btn" data-mode="stream">Stream it in chunks</button>
</div>
<div class="st-file-row">
<div class="st-file-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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg></div>
<span class="st-file-name">video.mp4 · 2 GB</span>
</div>
<div class="st-mem-wrap">
<div class="st-mem-label"><span>Memory used</span><span id="stMemPct">3%</span></div>
<div class="st-mem-track"><div class="st-mem-fill" id="stMemFill"></div></div>
</div>
<p class="st-chunk-log" id="stChunkLog"> </p>
<p class="st-mem-caption" id="stMemCaption">Pick a mode, then run it.</p>
<div class="st-controls">
<button type="button" class="st-btn" id="stMemRunBtn">▶ Process the file</button>
</div>
</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('stinfo');
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');
/* ================= Part 1: memory ================= */
var toggleBtns = root.querySelectorAll('.st-toggle-btn');
var memRunBtn = root.querySelector('#stMemRunBtn');
var memFill = root.querySelector('#stMemFill');
var memPct = root.querySelector('#stMemPct');
var chunkLog = root.querySelector('#stChunkLog');
var memCaption = root.querySelector('#stMemCaption');
var mode = 'all';
var memRunning = false;
var memTimers = [];
function clearMemTimers() { memTimers.forEach(function (t) { clearTimeout(t); }); memTimers = []; }
function schedule(fn, delay) { memTimers.push(setTimeout(fn, delay)); }
function resetMem() {
clearMemTimers();
memFill.style.width = '3%';
memFill.classList.remove('is-danger');
memPct.textContent = '3%';
chunkLog.textContent = '\u00a0';
memCaption.classList.remove('is-danger-text');
memCaption.innerHTML = 'Pick a mode, then run it.';
}
toggleBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
if (memRunning) return;
mode = btn.getAttribute('data-mode');
toggleBtns.forEach(function (b) { b.classList.toggle('is-selected', b === btn); });
resetMem();
});
});
memRunBtn.addEventListener('click', function () {
if (memRunning) return;
memRunning = true;
memRunBtn.disabled = true;
resetMem();
if (mode === 'all') {
memCaption.innerHTML = '<b>fs.readFile()</b> starts pulling the whole 2 GB file into memory\u2026';
schedule(function () {
memFill.style.width = '97%';
memFill.classList.add('is-danger');
memPct.textContent = '97%';
}, 500);
schedule(function () {
memCaption.classList.add('is-danger-text');
memCaption.innerHTML = '<b>The entire file sits in RAM at once.</b> Do this with a few large files at the same time and you can run out of memory.';
memRunning = false;
memRunBtn.disabled = false;
}, 1300);
} else {
memCaption.innerHTML = '<b>fs.createReadStream()</b> reads a small chunk, hands it off, then reads the next one.';
var chunk = 0;
var totalChunks = 8;
function nextChunk() {
chunk++;
var pct = 12 + (chunk % 3);
memFill.style.width = pct + '%';
memPct.textContent = pct + '%';
chunkLog.textContent = 'chunk ' + chunk + ' / ' + totalChunks + ' processed';
if (chunk < totalChunks) {
schedule(nextChunk, 260);
} else {
schedule(function () {
memCaption.innerHTML = '<b>Memory stayed flat the whole time.</b> A 2 GB file or a 200 GB file would look the same here.';
memRunning = false;
memRunBtn.disabled = false;
}, 300);
}
}
schedule(nextChunk, 500);
}
});
})();
</script>
</body>
</html>