• English
  • 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.MultiReader advances only when the current Reader reaches io.EOF.

    Quick Example

    package main
    
    import (
    	"fmt"
    	"io"
    	"strings"
    )
    
    func main() {
    	r := io.MultiReader(
    		strings.NewReader("header:"),
    		strings.NewReader("body"),
    	)
    
    	data, err := io.ReadAll(r)
    	if err != nil {
    		fmt.Println("read failed:", err)
    		return
    	}
    
    	fmt.Println(string(data))
    }

    Output:

    header:body

    The caller sees one continuous stream:

    Reader 1 ──EOF──▶ Reader 2 ──EOF──▶ EOF

    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:

    Reader 1 ──error──▶ STOP
    
                          └── Reader 2 is not read

    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:

    r := io.MultiReader(
    	strings.NewReader("type=text\n\n"),
    	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:

    Reader A
    
       │ blocking
    
    Reader B  ← not reached

    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.MultiReader is 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:

    resp, err := http.Get(url)
    if err != nil {
    	return err
    }
    defer resp.Body.Close()
    
    r := io.MultiReader(
    	strings.NewReader("prefix\n"),
    	resp.Body,
    )

    The caller still owns resp.Body and is responsible for closing it.

    io.MultiReader composes 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

    NeedRecommended APICore Behavior
    Combine Readers sequentiallyio.MultiReaderReads each source in order
    Duplicate writes to multiple destinationsio.MultiWriterSends each write to multiple Writers
    Copy a stream from Reader to Writerio.CopyStreams one source into one destination
    Limit the number of bytes exposedio.LimitReaderStops after at most N bytes
    Read exactly N bytesio.ReadFullFills the supplied buffer or reports an error
    Observe data while it is being readio.TeeReaderWrites each read byte to another Writer

    Rule of Thumb

    Use io.MultiReader when multiple sources should behave like one sequential stream.

    It gives you composition, not concurrency, retry logic, or resource ownership.