Example: io.Pipe
io.Pipe connects an io.Writer and an io.Reader with a synchronous, in-memory stream.
It has no internal buffering. A write blocks until its data is consumed by one or more reads, so the writer and reader normally run concurrently.
Use it when a producer should stream data directly to a consumer without first building the complete output in memory.
Quick Example
The writer and reader run concurrently. Because the pipe has no internal buffer, the writer blocks until the reader consumes the data.
Common Use
io.Pipe is useful when one component produces a stream while another consumes it immediately.
For example, a producer can generate data directly into an HTTP request body:
Create the request before starting the producer goroutine. If request construction fails, no consumer will read from the pipe, and a producer blocked in Write could otherwise remain stuck indefinitely.
The same pattern can connect:
- a data generator to an encoder;
- an encoder to a compressor;
- a producer to an HTTP request body;
- one streaming stage to another.
Common Mistakes
1. Writing and reading sequentially in the same goroutine
This can deadlock:
Write waits for a reader, but the reader cannot run until Write returns.
Run the producer and consumer concurrently.
2. Forgetting to close the writer
After the producer finishes, close the writer so the reader can observe EOF.
3. Using io.Pipe when buffering is required
io.Pipe is a synchronous handoff, not a buffered queue.
If the producer needs to continue independently while the consumer falls behind, use an explicit buffering mechanism instead.
4. Losing producer errors
An error returned inside the producer goroutine does not automatically reach the reader.
Use CloseWithError:
The reader can then receive the producer's error instead of seeing a normal EOF.
Rule of Thumb
Use
io.Pipewhen a producer and consumer should exchange a stream synchronously, without an intermediate buffer.