Example: io.Discard
Sometimes an API requires an io.Writer, but you genuinely do not want the output.
For example, a test may need to execute code that writes a large amount of output without printing it. A benchmark may need to consume a stream without storing the result.
That is what io.Discard is for:
It accepts the bytes and keeps none of them.
Think of it as /dev/null for Go's io.Writer interface.
io.Discardcontrols the destination, not the source.Writing to
io.Discarddoes not stop the source from producing, reading, decompressing, or otherwise processing its data.
Quick Example
Consume a stream without retaining its contents:
Output:
The bytes are still read from src. They are simply discarded after being written to the destination.
This distinction is important:
io.Discard does not make the source cheaper or faster by itself.
Suppressing Output
When a function accepts an io.Writer, passing io.Discard is often cleaner than adding a special "disable output" mode.
The caller can explicitly suppress the output:
The function does not need to know whether its output is going to a terminal, a file, a buffer, or nowhere.
This is particularly useful in tests and benchmarks.
Muting Loggers
Both log.Logger and log/slog can use io.Discard as their output destination.
This keeps the logging code unchanged while suppressing its output.
For tests, this is generally preferable to redirecting output to a temporary file or conditionally disabling logging inside the code under test.
io.Copy Still Reads the Source
A common misunderstanding is to treat:
as a way to skip src.
It is not.
io.Copy still calls src.Read until it reaches EOF or encounters an error.
If the source performs expensive work, that work still happens:
For example, discarding an HTTP response body still requires reading the response body from the network.
io.Discard only eliminates the destination-side storage or processing.
Errors Are Still Propagated
Discarding the output does not discard source errors.
If reader.Read fails, io.Copy reports that error.
This makes io.Discard suitable when the application does not need the data but still needs to know whether consuming the stream succeeded.
The byte count is also meaningful:
n is the number of bytes successfully consumed before the operation stopped.
io.Discard Does Not Retain the Buffer
io.Discard does not store the bytes passed to Write.
The write succeeds with:
The discarded writer does not retain the supplied byte slice or its contents.
This makes it useful as a sink when the output has no value after the write completes.
io.Discard and io.ReaderFrom
io.Discard also implements io.ReaderFrom.
That matters when it is used as the destination of io.Copy:
io.Copy can use destination-side ReadFrom support when the source does not provide a suitable WriterTo implementation.
The exact execution path is an implementation detail of io.Copy and the participating types. Do not treat a particular buffer size, allocation count, or fast path as part of the io.Discard API contract.
If allocation or throughput matters, benchmark the complete operation you actually care about.
Benchmarking Without Destination Storage
io.Discard is useful when a benchmark needs to consume data but does not need to retain the result.
The benchmark measures the complete bytes.Reader → io.Copy → io.Discard operation.
It does not measure "pure reader performance."
That distinction matters when comparing implementations. If the real application consumes a network stream, compressed data, or a file, benchmark that actual path instead of assuming io.Discard removes all unrelated work.
Bounded Consumption
Sometimes the application does not need the data, but it also should not consume an unlimited amount of it.
For example:
This places a maximum on how many bytes the application will consume.
io.CopyN is another option:
The choice depends on the desired semantics.
The important point is:
Discarding output and limiting input are separate concerns.
io.Discard answers:
Where should the output go?
io.LimitReader answers:
How much input should this reader expose?
Neither one provides a timeout or cancellation policy for a blocking network operation.
HTTP Response Bodies
io.Discard is sometimes useful when an HTTP response body is not needed:
But this makes an important commitment:
Consume the body until EOF or error.
For a large or untrusted response, that may be the wrong choice.
If the application only wants to consume a bounded amount:
A byte limit does not guarantee a time limit. Network operations still need appropriate timeout or cancellation policies.
Also, io.Discard itself has no HTTP connection-management semantics. Whether a connection can be reused depends on the HTTP transport and how the response body is handled.
Common Mistakes
Mistake 1: Treating discard as skip
This:
still reads the data.
If the source supports seeking and the application genuinely wants to move past bytes without reading them, use the source's seeking or offset-based API instead.
Mistake 2: Assuming discard removes source-side cost
If the source performs:
- network I/O
- decompression
- decryption
- parsing
- database reads
that work still happens.
io.Discard only removes the need to retain or process the resulting bytes at the destination.
Mistake 3: Ignoring source errors
This is still wrong:
when successful consumption matters.
Check the returned error:
Mistake 4: Using an unbounded drain when a limit is required
This:
means "consume until EOF or error."
If the input is untrusted or potentially very large, establish an explicit size policy when appropriate.
Mistake 5: Using io.Discard when the output will be needed later
Once written to io.Discard, the bytes are gone.
If the caller needs to inspect, retry, or process the output later, use an appropriate storage or processing writer instead.
io.Discard vs Other Choices
The key distinction is that io.Discard is a destination.
It does not control how the input is produced or how much input should be consumed.
Rule of Thumb
Use
io.Discardwhen an API needs anio.Writerand the output is intentionally unwanted.
And remember:
Discarding output is not the same as skipping input.