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.