Example: io.MultiReader
io.MultiReader combines multiple io.Reader values into one sequential input stream.
It lets downstream code consume several sources in order without manually switching between Readers.
The core idea is:
Compose the order of data, not the data itself.
The important boundary is:
io.MultiReaderadvances only when the current Reader reachesio.EOF.
Quick Example
Output:
The caller sees one continuous stream:
No combined []byte is created by io.MultiReader.
How It Behaves
io.MultiReader consumes Readers strictly in the order provided.
If the current Reader returns io.EOF, MultiReader moves to the next Reader. That intermediate io.EOF is not returned to the caller.
If a Reader returns data together with io.EOF, the data is still returned and MultiReader can continue with the next Reader.
A non-EOF error is different:
MultiReader does not retry, skip, or recover from that error.
The useful mental model is:
EOF means "move to the next source"; any other error means "stop".
An empty io.MultiReader() behaves like an empty Reader and returns io.EOF.
Common Use
Use io.MultiReader when several sources logically form one sequential stream.
For example, prepend a small in-memory header to a large file:
The downstream consumer can then read from r without knowing that the data comes from two different sources.
This is useful for:
- generated prefix + file
- metadata + payload
- multiple file segments
- several in-memory buffers
- prefix + network stream
The important property is that the sources remain separate. io.MultiReader does not first materialize them into one buffer.
Common Mistakes
1. Expecting concurrent reads
io.MultiReader is sequential.
If the first Reader blocks, the second Reader is never accessed:
Do not use io.MultiReader when multiple independent sources need to make progress concurrently.
For concurrency, use an application-level design involving goroutines, channels, io.Pipe, or another scheduling mechanism.
io.MultiReaderis a stream-composition primitive, not a concurrency primitive.
2. Expecting recovery after an error
Only io.EOF advances to the next Reader.
If a Reader returns another error, later Readers are not consumed.
If the application needs retry, fallback, or skip-on-error behavior, implement that policy explicitly.
3. Expecting it to close underlying resources
io.MultiReader only implements io.Reader.
It does not close underlying io.ReadCloser values.
For example:
The caller still owns resp.Body and is responsible for closing it.
io.MultiReadercomposes reading behavior, not resource ownership.
4. Assuming successful reads mean the whole stream is done
A successful Read only means that the current operation produced data.
It does not mean that:
- the current Reader is exhausted
- all Readers have been consumed
- the combined stream has reached EOF
Only EOF from the final Reader ends the combined stream.
API Selection Guide
Rule of Thumb
Use
io.MultiReaderwhen multiple sources should behave like one sequential stream.
It gives you composition, not concurrency, retry logic, or resource ownership.