Systems

Standard I/O Explained

Programs do not need to know whether input came from a keyboard, a file, or another program. Standard streams give them the same three doors every time.

Rickvian Aldi·Software engineer·June 19, 2026·5 min read

Co-authored with generative AI

A command line tool prints useful data and error messages into the same place. The next command in a pipeline receives both. A clean JSON file now has file not found text in the middle of it, and the parser fails for a reason that looks unrelated.

Standard I/O is the small convention that prevents that mess. It gives every process three default streams: one for input, one for normal output, and one for diagnostics.

The Short Version

Standard I/O means standard input, standard output, and standard error. A stream is a sequence of bytes that a program can read from or write to. The operating system connects these streams to the terminal by default, but it can also connect them to files, pipes, or other programs.

A process reads from stdin, writes normal data to stdout, and writes diagnostics to stderr.

The useful part is not the names. The useful part is the separation. Data and diagnostics can travel different paths.

The Three Streams

stdin is where the program reads input. In a terminal session, stdin usually comes from the keyboard. In a pipeline, stdin often comes from another program.

stdout is where the program writes normal output. If a command produces rows, JSON, logs meant for later processing, or a final answer, that belongs on stdout.

stderr is where the program writes error messages and diagnostics. If the program needs to say why it failed, what file was missing, or what warning matters to a human, that belongs on stderr.

The terminal often displays stdout and stderr together, which makes them feel like one thing. They are still separate streams underneath.

A Small Python Example

This Python script reads one line from stdin, writes the greeting to stdout, then writes a diagnostic message to stderr.

import sys
 
# stdin supplies input. It may come from the keyboard, a file, or a pipe.
name = sys.stdin.readline().strip()
 
# stdout carries normal output that another program may want to consume.
print(f"Hello, {name}")
 
# stderr carries diagnostics for humans or logs.
print("diagnostic: greeting was written", file=sys.stderr)

Run it interactively and both output lines appear in the terminal:

python greet.py
Ada
Hello, Ada
diagnostic: greeting was written

Redirect stdout to a file and stderr still stays visible:

printf 'Ada\n' | python greet.py > output.txt
diagnostic: greeting was written

The file contains only the normal result:

Hello, Ada

That is the point. Another program can safely read output.txt without accidentally receiving diagnostic text.

Why The Separation Matters

Command line tools become more useful when they can be composed. Composition means one program's output becomes another program's input.

stdout can feed another program while stderr remains a side channel for diagnostics.

If errors are mixed into stdout, every downstream program has to guess which bytes are data and which bytes are commentary. With stderr, the rule is simple: stdout is the data path, stderr is the human diagnostic path.

When To Use It

Use standard I/O for command line tools, scripts, build steps, data filters, and small automation programs. It is especially useful when a program should work both interactively and inside a pipeline.

A good default is:

  1. Read input from stdin when no file path is given.
  2. Write machine-readable results to stdout.
  3. Write progress, warnings, and errors to stderr.
  4. Exit with a nonzero status code when the command fails.

When Not To Use It

Standard I/O is not a complete user interface. A long-running graphical app, network server, or mobile app needs other communication paths.

It is also a poor fit for structured two-way protocols that need authentication, retries, multiplexing, or long-lived sessions. For those cases, use a socket, HTTP API, message queue, or another protocol designed for that job.

Gotchas

The first gotcha is buffering. A program may hold stdout in memory before writing it, especially when stdout goes to a file or pipe. That can make output appear later than expected.

The second gotcha is accidental logging to stdout. Progress bars, debug lines, and warnings can corrupt data files if they share the normal output stream.

The third gotcha is assuming stderr always means failure. A program can write warnings to stderr and still exit successfully. The exit code is the better signal for success or failure.

Alternatives

Files are better when input or output needs to be durable and revisited later. Environment variables are better for small configuration values. Command line arguments are better for options and file names. Network APIs are better when another machine or service needs to communicate with the program.

Standard I/O sits underneath many of those tools because it is small, predictable, and easy to connect.

Takeaway

Standard I/O works because it keeps input, normal output, and diagnostics separate enough for programs to compose without guessing what each byte means.

Get essays in your inbox

Practical deep-dives on software craft, career leverage, and building things that matter.