File I/O (os, bufio, io)

Go reads and writes files through the os package for whole-file operations, bufio for efficient line-by-line and buffered access, and io for streaming data of any size between readers and writers.

Learn File I/O (os, bufio, io) in our free Go course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Whole-file: WriteFile & ReadFile

The simplest operations. os.WriteFile(path, data, perm) creates or truncates a file and writes all the bytes; os.ReadFile(path) returns the whole file as a []byte . Both handle opening and closing for you — ideal for small files. We write under os.TempDir() so it runs and tidies up.

2️⃣ Line by line with bufio.Scanner

For larger files, os.Open the file (and defer f.Close() ), then wrap it in a bufio.NewScanner . Each Scan() advances to the next line; Text() gives it to you without the newline. Always check scanner.Err() after the loop to catch read errors.

3️⃣ Buffered writing & streaming with io.Copy

A bufio.Writer batches many small writes into fewer syscalls — just remember to Flush() before closing or the last bytes never land. And io.Copy(dst, src) streams from any reader to any writer in chunks, copying files of any size with constant memory.

🎯 Your Turn

Write a file and read it straight back. Fill in the two blanks marked ___ , then run it.

❌ Forgetting f.Close() after os.Open / Create — leaks file descriptors.

✅ defer f.Close() right after a successful open.

❌ Not calling w.Flush() on a bufio.Writer — the last buffered bytes vanish.

✅ w.Flush() before closing the underlying file.

❌ Reading a huge file with ReadFile — out-of-memory.

✅ Stream it with a bufio.Scanner or io.Copy .

❌ Ignoring scanner.Err() — a read error looks just like end-of-file.

Possibly empty or truncated. Buffered bytes sit in memory until Flush() ; without it (or if you close the file first) they never reach disk.

hello — Text() returns the line without the trailing newline. Use scanner.Bytes() for the raw bytes.

Write the sample text to a temp file, then scan it and count the lines that aren't empty. The text has 5 lines but 2 are blank, so print non-empty lines: 3 .

Practice quiz

os.WriteFile(path, data, perm) does what to an existing file?

  • appends to it
  • errors if it exists
  • creates or truncates it, writing all bytes
  • reads it

Answer: creates or truncates it, writing all bytes. WriteFile creates the file or truncates an existing one, then writes the bytes.

What does os.ReadFile(path) return?

ReadFile slurps the entire file into a single []byte in memory.

After os.Open, the idiomatic way to guarantee the file closes is...

  • call f.Close() at the end
  • rely on the GC
  • os.Remove(f)
  • defer f.Close() right after a successful open

Answer: defer f.Close() right after a successful open. defer f.Close() runs on every return path, including errors.

By default, each bufio.Scanner Scan() reads...

  • one byte
  • one line
  • the whole file
  • one word

Answer: one line. The default split function is by lines; Text() gives the line without the newline.

scanner.Text() on the line "hello " returns...

  • "hello"
  • "hello "
  • " "
  • the byte count

Answer: "hello". Text() strips the trailing newline; use Bytes() for the raw bytes.

Why must you call w.Flush() on a bufio.Writer?

  • to open the file
  • to lock the file
  • buffered bytes stay in memory until flushed
  • it resets the buffer size

Answer: buffered bytes stay in memory until flushed. A bufio.Writer batches writes; without Flush the last buffered bytes never reach disk.

What does io.Copy(dst, src) do?

  • loads the whole file into memory
  • streams from a Reader to a Writer in chunks
  • compares two files
  • deletes src

Answer: streams from a Reader to a Writer in chunks. io.Copy streams in fixed-size chunks with constant memory and returns bytes copied.

Why check scanner.Err() after the scan loop?

  • to count lines
  • to flush the buffer
  • to close the file
  • the loop ends on EOF and on error alike, hiding read failures

Answer: the loop ends on EOF and on error alike, hiding read failures. Scan() returns false on both EOF and error; Err() tells the difference.

What does the mode 0644 mean?

  • owner rwx, others none
  • owner read/write, group and others read
  • everyone read/write
  • it is ignored everywhere

Answer: owner read/write, group and others read. Octal 0644 is owner read+write (6), group read (4), others read (4).

To append rather than overwrite, you should...

  • use os.WriteFile again
  • use io.Copy
  • open with os.O_APPEND|os.O_CREATE|os.O_WRONLY
  • call ReadFile first

Answer: open with os.O_APPEND|os.O_CREATE|os.O_WRONLY. os.WriteFile always truncates; OpenFile with O_APPEND makes writes go to the end.