skip to content

Python's print() takes a flush argument — what does it do, and when do you pass it?

level: juniorimportance: should knowfreq 45%

answer

  1. Where does print() put the text first?
  2. Something has to drain that buffer
  3. Per-call switch, not a stream setting
  4. Equivalent to calling the stream's flush()

basics

~20 s

Passing flush=True to print writes sys.stdout's buffered text out to the operating system immediately, instead of leaving it to sit until the buffer fills. Use it for progress output and anywhere the process might die before the buffer drains.

solid answer

~40 s

`print()` writes into the buffer of `sys.stdout`; the text does not necessarily reach the operating system on the same call. When `sys.stdout` is a terminal the stream is line-buffered, so every newline pushes it out anyway and `flush=True` changes nothing visible. When stdout is a pipe or a redirect it is block-buffered, and a line can sit in memory until the block fills, until something flushes it, or until the interpreter shuts down cleanly. `flush=True` calls the stream's `flush()` right after that write, so the line leaves the process at once. It is a per-call decision and does not change the stream's buffering mode — if every line needs it, set the mode once with `python -u`, `PYTHONUNBUFFERED=1`, or `sys.stdout.reconfigure(line_buffering=True)` rather than sprinkling the keyword through the code.

code

python · 9 lines
python
import sys
import time

for step in range(3):
    print(f"loading shard {step}", flush=True)
    time.sleep(0.2)

print("startup finished", file=sys.stderr)
print(sys.stdout.line_buffering, sys.stdout.write_through)

go deeper

for a junior

Be ready to say that print() writes into a buffer and flush=True empties it right away, and that you notice the difference when output is piped rather than shown in a terminal.

for a middle

Explain the mechanics: the buffering mode is chosen at startup from whether the stream is a terminal, flush=True is a one-call action, and the stream-wide equivalents are -u, PYTHONUNBUFFERED and reconfigure(line_buffering=True).

for a senior

Show the judgement of where to flush. Name the failure it protects against — a hard kill discarding buffered output — and the cost it adds, one syscall per line, and pick the stream-level setting over scattering the keyword.

for a principal

Own the convention for the codebase: whether services run unbuffered by default, whether diagnostics belong on stderr instead, and how you keep that decision in one place rather than in thousands of call sites.

## What `print()` actually touches `print()` is a thin convenience over `sys.stdout.write()`. It formats its arguments, joins them with `sep`, appends `end` (a newline by default) and writes the resulting `str` to the file object given by `file=` — `sys.stdout` unless you say otherwise. That file object is an `io.TextIOWrapper`: a text layer that encodes `str` to bytes, sitting on top of a buffered binary layer, sitting on top of file descriptor 1. Text goes in at the top; bytes reach the kernel only when some layer decides to hand them down. ## Why there is a buffer at all Every trip to the kernel costs a system call. A program that emitted one `write` syscall per line would spend a large share of its time in that overhead when it is chatty. So the binary layer accumulates bytes and hands them over in blocks. The tradeoff is latency: text that is already "printed" from the program's point of view may still be sitting in the process's memory, invisible to anything reading the other end of the pipe. CPython picks the policy at startup from what the stream is attached to: * **Terminal (interactive):** `sys.stdout` is line-buffered. Each `\n` triggers a flush, which is why the buffer is invisible during development. * **Pipe, file or any non-terminal:** `sys.stdout` is block-buffered. Nothing goes out until the block fills (`io.DEFAULT_BUFFER_SIZE` is 128 KiB on CPython 3.14), something flushes it, or the interpreter finalizes. * **`sys.stderr`:** line-buffered even when redirected, since Python 3.9. Diagnostics and tracebacks are deliberately given the low-latency stream. ## What `flush=True` does `print("x", flush=True)` performs the write and then calls `flush()` on the same file object. `flush()` pushes the text layer's pending characters down into the binary layer and asks that layer to write its bytes to the file descriptor. After it returns, the data is in the operating system's hands — the kernel's pipe or page cache — not in your process. That is the guarantee you get: it survives your process being killed. It is *not* a guarantee that bytes are on a physical disk; that would need an explicit sync at the OS level, which is a different concern. The keyword affects exactly one call. It does not mutate the stream: `sys.stdout.line_buffering` and `sys.stdout.write_through` read the same before and after. The next `print()` without it buffers again. ## When you actually need it * **Progress or heartbeat output** that a human or a supervisor is watching in real time, when stdout is not a terminal. * **Breadcrumbs in a process that may be killed abruptly** — a hard kill, an out-of-memory kill, `os._exit()`, or a native crash. Normal interpreter shutdown flushes the standard streams; none of those paths do. * **Interleaving two streams.** If a program writes progress to stdout and warnings to stderr, and stdout is block-buffered while stderr is line-buffered, a reader that merges the two sees them badly out of order. Flushing stdout at the points that matter restores the ordering. * **Before handing the terminal to a subprocess** that will write to the same descriptor, so your text does not appear after the child's. ## When you do not Interactively it is noise — the stream is already line-buffered. And if you find yourself typing it on every `print()` in a module, that is the wrong tool: you want a stream-level setting, not a per-call one. `python -u` or `PYTHONUNBUFFERED=1` makes both standard streams unbuffered for the whole run (they have no effect on stdin), which is the usual choice for a service whose stdout is collected as logs. From inside the program, `sys.stdout.reconfigure(line_buffering=True)` gives you flush-per-line without a syscall for every partial write, which is a gentler middle ground. ## The cost Flushing is not free. Each flush is at least one `write` syscall, so a tight loop that prints thousands of short lines with `flush=True` can spend a noticeable fraction of its wall time in the kernel. That is the whole reason block buffering is the default off a terminal. Choose deliberately: flush what someone is waiting to read, and let the rest ride in the buffer.

  • Does flush=True change how later print() calls behave?
    No. It flushes once, immediately after that write. The stream keeps whatever buffering it was created with — `sys.stdout.line_buffering` and `sys.stdout.write_through` are unchanged. To change every subsequent write you have to reconfigure the stream with `sys.stdout.reconfigure(line_buffering=True)`, or start the interpreter with `-u` or `PYTHONUNBUFFERED=1`.
  • If stdout output can be lost, why do tracebacks from a crashing script usually still appear?
    Tracebacks go to `sys.stderr`, which since Python 3.9 is line-buffered even when redirected, so each line leaves the process as it is written. Its error handler is `backslashreplace`, so it also survives characters the encoding cannot represent. That asymmetry is why a failed job often shows the traceback but not the print() breadcrumbs that led up to it.

The buffer is an outbox: print() drops the letter in it, and the courier normally waits until the tray is full. flush=True sends the courier now, for that one letter.

saying these in an interview costs you the question

  • Says print() always writes straight to the terminal
  • Thinks flush=True permanently unbuffers sys.stdout
  • Assumes a newline always flushes, whatever stdout is attached to
  • Adds flush=True to every print instead of setting PYTHONUNBUFFERED
  • Believes sys.stdout and sys.stderr buffer identically
  • Claims flush() guarantees the bytes are on disk

context