Go by Example

Writing Files

os.WriteFile for small writes, os.Create with bufio.Writer for large or streaming writes, os.OpenFile with flags, and fsync for durability.

The os package provides file writing at different granularities. os.WriteFile handles small writes in one call; bufio.Writer wraps a file for efficient large or streaming output.

os.WriteFile writes a []byte to a file in one call, creating or truncating the file as needed. Suitable for config files, small generated outputs, or test fixtures where the entire content is known upfront.

package main
 
import (
    "fmt"
    "os"
)
 
func main() {
    data := []byte("hello, file\nline two\n")
    err := os.WriteFile("output.txt", data, 0644)
    if err != nil {
        panic(err)
    }
    fmt.Println("wrote", len(data), "bytes")
}

For large or streaming writes, create the file with os.Create and wrap it in a bufio.Writer. The writer batches small writes into larger OS calls, reducing system call overhead. Always call Flush() before closing.

package main
 
import (
    "bufio"
    "fmt"
    "os"
)
 
func main() {
    f, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    defer f.Close()
 
    w := bufio.NewWriter(f)
    for i := range 5 {
        fmt.Fprintf(w, "line %d\n", i+1)
    }
    if err := w.Flush(); err != nil {
        panic(err)
    }
}

os.OpenFile gives full control over open flags. O_APPEND adds to an existing file instead of truncating it, O_CREATE creates the file if it does not exist, and O_WRONLY opens for writing only.

package main
 
import (
    "fmt"
    "os"
)
 
func main() {
    f, err := os.OpenFile("log.txt",
        os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer f.Close()
 
    _, err = fmt.Fprintln(f, "appended line")
    if err != nil {
        panic(err)
    }
}

In production

bufio.Writer buffers writes in memory - call Flush() before closing or the last buffer is silently dropped. For durable writes (config files, ledger entries), call f.Sync() before f.Close() to flush the OS page cache to disk; defer f.Close() alone does not guarantee durability. The safest pattern for critical writes is to write to a temp file, call f.Sync(), close it, then rename atomically with os.Rename - readers either see the old file or the complete new file, never a partial write. os.OpenFile with O_APPEND is not atomic for writes larger than the OS write size on some filesystems; for append-only logs under concurrent writers, use a dedicated logging library or serialize writes through a goroutine.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free