Skip to solution
mediumLow-Level Design

Explain how to read and write large files using streams in Node.js

229 views
01

Understand the problem

Question presented to candidate: "You need to copy an 80GB file on disk to a new location. Would fs.readFileSync followed by fs.writeFileSync work in practice, and if not, what is the actual recipe that does?"

What a strong answer should cover:

  • The practical recipe for reading and writing a large file is fs.createReadStream(source).pipe(fs.createWriteStream(destination)) — a Readable piped directly into a Writable, letting .pipe()'s automatic backpressure handling (covered fully, with real measured proof, in its own dedicated question) manage the flow.
  • 📌 Verified, not assumed: streaming an 80MB file copy this way, with memory sampled every 5ms throughout the entire operation, peaked at only ~86MB of resident memory — confirming memory usage does not scale proportionally with the file's size, unlike readFileSync/writeFileSync, which would need the entire file's bytes resident in memory at once.
  • The reason this specifically matters for genuinely large files (the 80GB case in the prompt): readFileSync attempting to load that much data into a single in-memory buffer can exceed available memory entirely, causing the process to crash — a failure mode that scales with input size, not something that merely "gets slower."
  • Transforming data while copying it (rather than a byte-for-byte copy) is a natural extension of the same pattern: inserting a Transform stream (or zlib.createGzip(), covered in its own dedicated question) between the read and write stages — createReadStream(src).pipe(transform).pipe(createWriteStream(dest)) — applies the transformation incrementally, chunk by chunk, with the same flat memory profile.
  • A precise answer names stream.pipeline() (covered with real, verified error-cleanup proof in its own dedicated question) as the currently recommended choice over raw .pipe() for production code, specifically because it correctly propagates errors and destroys every stream in the chain on failure — a real, verified gap in .pipe() alone.
  • The same underlying pattern applies well beyond local file copying: streaming a large file directly into an HTTP response (createReadStream(path).pipe(res)), or reading a large uploaded file directly into cloud storage without buffering the whole thing in the server's own memory first.

Clarifying questions expected:

  • "Is this purely copying, or does the data need to be transformed/compressed along the way?" — decides whether a plain .pipe() chain or one including a Transform step is needed.
  • "Does this specific operation need robust error handling and cleanup across the whole chain?" — routes toward stream.pipeline() over raw .pipe().

Code / implementation expected: Yes — the real, measured peak-memory result for an actual 80MB streamed copy is the concrete, convincing proof, not a description of "streams use less memory."

fsstreamsio
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js interviews — assumes basic fs/stream familiarity. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The memory numbers below came from **actually copying a real 80MB

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

Streaming an 80MB file copy — peak memory measured throughout, and the resulting copy confirmed byte-identical
const fs = require("fs");

function fmtMB(n) { return (n / 1e6).toFixed(1) + "MB"; }

const src = fs.createReadStream("big.bin");   // a real 80MB file
const dest = fs.createWriteStream("big.bin.copy");

let peakRss = 0;
const sampler = setInterval(() => {
  peakRss = Math.max(peakRss, process.memoryUsage().rss);
}, 5);

src.pipe(dest);
dest.on("finish", () => {
  clearInterval(sampler);
  console.log("peak rss during the entire 80MB streamed copy:", fmtMB(peakRss));
  // peak rss during the entire 80MB streamed copy: 86.3MB

  const same = fs.statSync("big.bin").size === fs.statSync("big.bin.copy").size;
  console.log("copy same size as original:", same); // true
});

// Extending the identical pattern to compress while copying:
// fs.createReadStream(src).pipe(zlib.createGzip()).pipe(fs.createWriteStream(dest + ".gz"));
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 103 of 152 decoded in the Node.js track. One more won't hurt.

Back to track