skip to content

In Python's open(), what do buffering=0, buffering=1 and -1 mean?

level: middleimportance: should knowfreq 35%

answer

  1. picks a policy, not just a size
  2. one value is illegal for text
  3. one value is meaningless for binary
  4. negative one asks for the default
  5. the default constant grew in 3.14

basics

~20 s

0 means unbuffered and is legal only in binary mode; 1 means line buffering and is meaningful only in text mode; -1 is the default block buffering. Any other positive number is an explicit buffer size in bytes.

solid answer

~50 s

`open()`'s `buffering` argument selects the policy for the file object's byte buffer. `buffering=0` removes the buffer entirely — every write becomes a system call — and is accepted only in binary mode; asking for it in text mode raises `ValueError: can't have unbuffered text I/O`, because the text layer needs a buffered object underneath. `buffering=1` means line buffering: in text mode it sets `line_buffering` on the returned `io.TextIOWrapper` so each newline flushes; in binary mode it is not supported, and CPython emits a `RuntimeWarning` and falls back to the default size. A positive number greater than one is a fixed buffer size in bytes. `-1`, the default, uses `io.DEFAULT_BUFFER_SIZE` — 128 KiB on CPython 3.14 — or, for a regular file, the device block size clamped into that range. The choice trades syscalls against how promptly a reader sees your writes.

code

python · 14 lines
python
import io
import tempfile
import os

path = os.path.join(tempfile.mkdtemp(), "log.txt")

with open(path, "w", buffering=1) as f:
    print("line_buffering:", f.line_buffering)
    f.write("visible to a reader at once\n")

with open(path, "w") as f:
    print("line_buffering:", f.line_buffering)

print("default buffer bytes:", io.DEFAULT_BUFFER_SIZE)

go deeper

for a junior

Recall the three special values: 0 unbuffered, 1 line-buffered, -1 default. Knowing that buffering=1 is not a one-byte buffer is the trap this question is built around.

for a middle

Explain the mode restrictions and why they exist — no unbuffered text I/O because the text wrapper needs a buffered object, no line buffering in binary mode — and name what open() returns in each case.

for a senior

Choose the value from the access pattern: default for bulk writes nobody reads until close, line buffering for a file another process tails or that may outlive an abrupt kill, unbuffered binary when the syscall boundary itself matters.

for a principal

Frame it as a contract with the reader of the file. Decide where in the codebase this is set, keep it distinct from durability guarantees, and make sure the standard-stream policy and the file policy are not assumed to be the same knob.

## What the argument controls `open(path, mode, buffering=-1, ...)` builds a small stack of objects. At the bottom is an `io.FileIO` holding the file descriptor and issuing real system calls. Above it, unless you opt out, is a buffered object — `io.BufferedWriter`, `io.BufferedReader` or `io.BufferedRandom` depending on the mode — that batches bytes. In text mode there is a third layer, an `io.TextIOWrapper`, which encodes and decodes and handles newline translation. The `buffering` argument decides the middle layer. ## The four cases **`buffering=0` — unbuffered.** The middle layer is omitted and `open()` hands back the raw `io.FileIO` itself. Every `write` call is one system call, and every `read` goes straight to the kernel. This is legal in binary mode only: `open(path, "w", buffering=0)` raises `ValueError` with the message "can't have unbuffered text I/O", because the text wrapper is written against a buffered object. Reads from an unbuffered binary file may also return fewer bytes than requested, since a single raw read is not required to fill your request — code that assumes a full read is a common bug here. **`buffering=1` — line buffering.** In text mode this sets `line_buffering` on the returned wrapper: any write containing a newline flushes the stack down to the operating system. That is the setting you want for a log file or a progress file that another process tails, because a reader always sees whole lines and never a half-written one. In binary mode line buffering is not supported; CPython emits a `RuntimeWarning` saying so and quietly uses the default buffer size instead, which is a trap if you wrote `open(path, "wb", buffering=1)` expecting per-line flushing and never looked at warnings. **`buffering=n` for n greater than 1 — a fixed size.** The buffer is exactly `n` bytes. This is worth setting when you know the access pattern: a larger buffer for a big sequential write, a smaller one when memory matters more than syscall count. Note that in text mode a size does not enable line buffering — `line_buffering` stays false — so a file opened with `buffering=8192` writes nothing until 8 KiB has accumulated regardless of newlines. **`buffering=-1` — the default.** For a regular file, CPython consults the file's block size reported by the filesystem and clamps it: it uses the device block size where that is larger than the standard default, up to a ceiling of 8 MiB, and otherwise `io.DEFAULT_BUFFER_SIZE`. On CPython 3.14 that constant is 128 KiB, raised from 8 KiB in earlier releases — a change worth knowing, because it means a default-opened file holds sixteen times more unwritten data than the older number everyone quotes. Interactive text files (a tty) are line-buffered under the default. ## Choosing, in practice The question behind the argument is always "who else is looking at this file, and when?". If nothing reads the file until you close it — a data export, a report, an archive — take the default. Every extra syscall you avoid is free throughput, and the `with` block closes and flushes at the end. If another process tails the file, or a human is watching it, or the process may be killed before it finishes, line buffering earns its cost: one syscall per line instead of one per 128 KiB, in exchange for the reader seeing progress. Unbuffered binary writing is rarer, and is chosen for exact control over the syscall boundary — talking to a device, or writing records whose framing must not be split or merged by a buffer. It is also the wrong dial for two adjacent problems. The buffering argument does not make writes durable: flushing hands bytes to the kernel, and pushing them onto the storage device is a separate operation. And it says nothing about the standard streams — `PYTHONUNBUFFERED` and `-u` configure `sys.stdout` and `sys.stderr`, never the files you open yourself. A service that sets `PYTHONUNBUFFERED=1` in its container and still loses the tail of a file it opened has confused the two. ## Introspecting what you got The returned object tells you what happened: `type(f)` distinguishes an `io.FileIO` (unbuffered) from a `io.TextIOWrapper` (text), and in text mode `f.line_buffering` and `f.write_through` report the flushing policy. Checking those two attributes in a REPL settles most arguments about this argument faster than reading the documentation.

  • Why does open(path, 'w', buffering=0) raise while open(path, 'wb', buffering=0) works?
    In text mode open() returns an io.TextIOWrapper, and that wrapper is implemented against a buffered binary object; with no buffer there is nothing to wrap, so CPython raises ValueError with the message 'can't have unbuffered text I/O'. In binary mode there is no text layer, so open() can simply hand back the raw io.FileIO and let every write be a system call.
  • What actually happens if you pass buffering=1 to a binary-mode open()?
    Line buffering is not supported for binary files. CPython emits a RuntimeWarning saying so and uses the default buffer size instead, so you get an ordinary block-buffered io.BufferedWriter. Code that expected per-line flushing gets none, and the warning is easy to miss under a default warning filter. Write text mode with buffering=1, or flush explicitly.
  • Does setting PYTHONUNBUFFERED=1 change the buffering of files you open yourself?
    No. That variable, like the -u flag, configures only the standard streams at interpreter startup. A file object from open() takes its policy from its own buffering argument and keeps it. Conflating the two leads to services whose console output is prompt while a file they write still loses its tail on an abrupt exit.

saying these in an interview costs you the question

  • Reads buffering=1 as a one-byte buffer rather than line buffering
  • Expects buffering=1 to flush lines in binary mode
  • Thinks buffering=0 works in text mode
  • Believes a large buffering value enables newline flushing
  • Says buffering controls durability on disk
  • Assumes PYTHONUNBUFFERED also affects files opened by open()

context