Streams & Buffers

Streams let Node process data piece by piece as it arrives instead of loading it all into memory at once, and Buffers are the fixed-length chunks of raw binary data that streams move around.

Learn Streams & Buffers in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

By the end of this lesson you'll know what a Buffer holds and how to convert it to and from text, you'll read a large file chunk by chunk with a readable stream, and you'll copy a file efficiently by piping one stream into another — all while keeping memory use flat.

What You'll Learn in This Lesson

1️⃣ Buffers: Chunks of Raw Bytes

Before streams, you need to understand what they carry. A Buffer is Node's container for raw binary data — a fixed-length sequence of bytes. Text, images, network packets: at the lowest level they're all just bytes, and a Buffer is how Node holds them.

Read the worked example below — every line is explained — then run it. Notice that printing the Buffer directly shows its bytes as hexadecimal numbers.

2️⃣ Readable Streams: Data Piece by Piece

A readable stream hands you a file (or a network response, or any source) in small chunks instead of all at once. You open one with fs.createReadStream(path) and then listen for two key events:

Why bother? Because fs.readFile loads the entire file into memory before you see a single byte. A 2 GB log file would need 2 GB of RAM. A stream processes that same file in ~64 KB chunks, so memory use stays low and constant no matter how big the file gets.

Your turn. The program below works once you fill in the three blanks marked ___ . Follow the 👉 hints, then run it and compare with the expected output.

3️⃣ Writable Streams & .pipe()

A writable stream is the opposite of a readable one: it's a destination you push chunks into . You create one with fs.createWriteStream(path) . The magic happens when you connect a readable stream to a writable one with .pipe() :

This is how you copy a multi-gigabyte file using only a tiny, constant amount of memory — the whole copy is just source.pipe(dest) .

No blanks this time — just a brief and an outline to keep you on track. Stream a file, total up the byte length of each chunk, and print the result. Write it yourself, run it, and check your output against the example in the comments.

📋 Quick Reference — Streams & Buffers

Practice quiz

What does Buffer.from('hello').length return?

  • The number 5 (bytes)
  • The string 'hello'
  • The number of words
  • A hex dump

Answer: The number 5 (bytes). length is the number of bytes; 'hello' is 5 ASCII bytes.

Which method decodes a Buffer's bytes back into readable text?

  • buf.decode()
  • buf.toString()
  • buf.read()
  • buf.parse()

Answer: buf.toString(). buf.toString() decodes bytes (UTF-8 by default) into a string.

Why prefer fs.createReadStream over fs.readFile for a huge file?

  • It is always faster on tiny files
  • It validates the file format
  • It keeps memory low by reading in chunks
  • It encrypts the data

Answer: It keeps memory low by reading in chunks. A stream processes ~64KB chunks, so memory stays flat instead of loading the whole file.

Which event fires once after the last chunk of a readable stream?

  • 'finish'
  • 'close'
  • 'data'
  • 'end'

Answer: 'end'. 'end' fires once on the readable side after the final chunk.

What does source.pipe(dest) automatically handle for you?

  • Backpressure
  • File encryption
  • JSON parsing
  • DNS lookups

Answer: Backpressure. pipe() pauses the source when the destination can't keep up — that's backpressure.

Why must you attach a 'error' listener to every stream?

  • To rename the file
  • Stream errors are emitted, and unhandled ones crash the process
  • To speed up reads
  • To enable utf8 mode

Answer: Stream errors are emitted, and unhandled ones crash the process. Stream errors aren't thrown; an unhandled 'error' event crashes Node.

How many bytes does Buffer.from('hello')[0] (the byte for 'h') equal?

  • 72
  • 5
  • 104
  • 0

Answer: 104. 'h' is 0x68, which is 104 in decimal.

What event fires on the WRITABLE side when all data is flushed?

  • 'end'
  • 'finish'
  • 'data'
  • 'drain'

Answer: 'finish'. The writable side emits 'finish' once every byte has been written.

Passing 'utf8' to createReadStream makes each chunk arrive as what?

  • A string
  • A raw Buffer
  • A number
  • An array

Answer: A string. With an encoding set, chunks are strings instead of Buffers.

Which is true about a Buffer's .length for the text '😀'?

  • It equals the character count
  • It is always 1
  • It counts bytes, so an emoji is more than 1
  • It throws an error

Answer: It counts bytes, so an emoji is more than 1. length counts bytes; a single emoji takes several UTF-8 bytes.