Hands-on stdout buffering lab: full buffering maximizes throughput, while line and unbuffered modes restore live visibility on pipes.
6 min read
Line buffering writes stdout after every newline, so a Python child piping 40 lines showed its first line after about 12 ms, while full (8 KiB block) buffering, the default when stdout is a pipe or file rather than a terminal, held everything until about 226 ms. Full buffering was about 4.9x faster on throughput (about 4.0M vs 0.82M lines/s on a pipe), so keep it for bulk output and switch on python -u or PYTHONUNBUFFERED=1 when something reads the pipe live.
This is a hands-on lab with measured numbers:
buffering=1), and unbuffered (buffering=0) modes.print() on a pipe vs python -u vs PYTHONUNBUFFERED=1.Related links:
Lab honesty (30 Sep 2026 IST): Shared Linux lab box (8 cores, kernel 6.12). Python 3.13.5. Child stdout is a pipe or file (not a TTY — so Python’s default is block-buffered). No Docker. No GPU. No API keys. Affiliates: 0.
Verdict up front: full buffering hit ~4.0M lines/s on a pipe but first line stayed invisible until ~226 ms. Line / unbuffered showed first line at ~12 ms. Default pipe print() matched full; python -u and PYTHONUNBUFFERED=1 restored early visibility.
| Mode | How we opened stdout | Typical use |
|---|---|---|
| full | text, buffering=8192 | high-volume logs to pipe/file |
| line | text, buffering=1 | progress lines you want live |
| none | binary, buffering=0 | every write hits the OS |
Related links:
| Mode | Wall s | Lines/s |
|---|---|---|
| full | 0.050 | ~4.03M |
| line | 0.243 | |
| none | 0.158 | ~1.27M |
Block buffering wins raw throughput. Paying a write(2) per line (or nearly) is expensive when you do not need live visibility.
| Mode | First line s | Mid s | DONE s |
|---|---|---|---|
| full | 0.226 | 0.227 | 0.227 |
| line | 0.012 | 0.119 | 0.226 |
| none | 0.012 | 0.119 | 0.229 |
Full buffering held everything until the final flush — first, mid, and DONE arrived together near ~226 ms. Line and unbuffered streamed: first line ~12 ms after start.
| Mode | Wall s | Lines/s |
|---|---|---|
| full | 0.047 | ~4.30M |
| line | 0.354 | |
| none | 0.352 | ~0.57M |
Same story on a file sink: full buffering dominates; line and none collapse together when every line is a syscall.
print() vs -u / env| Setup | First line s | DONE s |
|---|---|---|
| default pipe | 0.229 | 0.229 |
python -u | 0.014 | 0.228 |
PYTHONUNBUFFERED=1 | 0.013 | 0.228 |
Default print() to a pipe behaved like full buffering. Both -u and PYTHONUNBUFFERED=1 restored early first-line visibility (~13–14 ms).
Related links:
Use line buffering (or -u / explicit flush) when a human, supervisor, or sidecar reads the pipe live.
flush() / exit.bufsize with child buffering — we set parent pipe reads unbuffered so visibility reflects the child.python -u, PYTHONUNBUFFERED=1, or explicit flush=True / line buffering.On non-TTY stdout, full (8 KiB) buffering delivered ~4.0M lines/s (pipe) / ~4.3M (file) but hid the first line until ~226 ms. Line / unbuffered showed first line at ~12 ms and paid ~5–8× throughput. Default pipe print() matched full; python -u and PYTHONUNBUFFERED=1 fixed visibility without rewriting the child.
Evidence path on the lab box: lab-evidence/25-stdout-buffering/results/. Affiliates: 0.
Output is flushed to the OS at each newline. In Python that's buffering=1 on a text stream. It is the default for interactive terminals, not for pipes or files.
When stdout is a pipe or file, Python block-buffers it (8 KiB here), so lines wait until the buffer fills or the process exits. In this lab, default print() to a pipe behaved like full buffering.
Run python -u, set PYTHONUNBUFFERED=1, or pass flush=True to print. Both -u and the environment variable brought the first line back to about 13-14 ms.
Lab evidence
Lab 30 Sep 2026 IST. Python 3.13.5. Non-TTY child stdout. Pipe 200k lines p50: full 0.050s (~4.03M lines/s); line 0.243s (~0.82M); none 0.158s (1.27M). Visibility 40×5ms: full first=0.226s (batch); line/none first0.012s. File 200k: full 0.047s (~4.30M); line/none ~0.35s (~0.57M). Default pipe print first=0.229s; python -u 0.014s; PYTHONUNBUFFERED=1 0.013s. No Docker. Affiliates: 0. Evidence: lab-evidence/25-stdout-buffering/.
Explore the product
Untested by us. Check what's included, the license and refund terms before you buy.
Hands-on itertools.islice vs list slice window lab: real ops/s taking ranges from sequences, measured on Linux localhost in this hands-on lab for SREs.
Lab1 Oct 20264 min read
Hands-on heapq.merge vs sorted(chain) multi-way merge: real records/s on pre-sorted lists, measured on Linux localhost today in this hands-on lab for SREs.
Lab1 Oct 20264 min read
Hands-on mmap MAP_SHARED write+msync vs pwrite region update: real MB/s with durability labels, measured on Linux localhost in this hands-on lab for SREs.
Lab1 Oct 20264 min read