Frontend Labs
⋯

22. I/O: io.Reader/Writer, bufio, and os

22.1 The two interfaces underneath everything

type Reader interface {
	Read(p []byte) (n int, err error)
}
type Writer interface {
	Write(p []byte) (n int, err error)
}

Files, network connections, in-memory buffers, gzip streams, TLS connections, and os.Stdin/os.Stdout all implement these two interfaces — which is why io.Copy(dst, src) works identically no matter what dst and src actually are.

22.2 bufio for line-oriented and buffered work

f, err := os.Open("access.log")
if err != nil {
	log.Fatal(err)
}
defer f.Close()

scanner := bufio.NewScanner(f)
for scanner.Scan() {
	line := scanner.Text()
	process(line)
}
if err := scanner.Err(); err != nil {
	log.Fatal(err)
}

bufio.Scanner's default buffer caps at 64KB per line — for genuinely huge lines, use bufio.Reader.ReadString('\n') or raise scanner.Buffer(...) explicitly.

22.3 Streaming instead of loading whole files

// BAD for a multi-GB file: os.ReadFile loads it all into memory
data, err := os.ReadFile("huge.log")
if err != nil {
	log.Fatal(err)
}
_ = data

// GOOD: stream line by line, constant memory regardless of file size
f, err := os.Open("huge.log")
if err != nil {
	log.Fatal(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
	handleLine(scanner.Text())
}
if err := scanner.Err(); err != nil {
	log.Fatal(err)
}

22.4 Real-world example

Chapter 42's log analyzer processes multi-gigabyte files in constant memory using exactly this streaming pattern — the difference between os.ReadFile and bufio.Scanner is the difference between a tool that OOMs on a large input and one that doesn't care how large the input is.

Applied in the series: the TCP/IP guide's pcapread decodes binary capture files with bufio, io.ReadFull, and encoding/binary; the Linux guide's atomicwrite makes file writes durable and atomic.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36