#Reading And Writing To Files in Go

Elliot Forbes Elliot Forbes · May 28, 2026 · 4 min read

Note: If you are working with structured data, you may also like Go JSON Tutorial.

The techniques we’ll cover here are file format-agnostic — you can use them to read and write .txt, .csv, .json, or any other file type. The only thing that differs between formats is how you parse or structure the data inside.

Reading Files

Use os.ReadFile to read the entire contents of a file into memory in one call.

Create a main.go and a localfile.data file with some text in it, then add the following:

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    // read in the contents of the localfile.data
    data, err := os.ReadFile("localfile.data")
    if err != nil {
        log.Fatal(err)
    }

    // print out the contents as a string
    fmt.Print(string(data))
}

os.ReadFile returns the file contents as a []byte. We cast it to string for printing. If the file doesn’t exist or can’t be read, err will be non-nil.

Run it with:

> go run main.go
this has all my content%

Writing Files to New Files

Use os.WriteFile to create a file and write content to it in one call.

WriteFile takes three arguments: the file path, the data as a []byte, and the file permission bits.

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    mydata := []byte("All the data I wish to write to a file")

    // WriteFile creates the file and returns an error if unsuccessful
    err := os.WriteFile("myfile.data", mydata, 0644)
    if err != nil {
        log.Fatal(err)
    }

    data, err := os.ReadFile("myfile.data")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Print(string(data))
}

If the file already exists, os.WriteFile truncates it before writing — it does not append. Run this and you’ll see:

➜ go run main.go
All the data I wish to write to a file

Writing to Existing Files

To append to an existing file without overwriting it, use os.OpenFile with the os.O_APPEND flag.

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    mydata := []byte("All the data I wish to write to a file\n")

    // WriteFile creates the file and returns an error if unsuccessful
    err := os.WriteFile("myfile.data", mydata, 0644)
    if err != nil {
        log.Fatal(err)
    }

    data, err := os.ReadFile("myfile.data")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Print(string(data))

    f, err := os.OpenFile("myfile.data", os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    if _, err = f.WriteString("new data that wasn't there originally\n"); err != nil {
        log.Fatal(err)
    }

    data, err = os.ReadFile("myfile.data")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Print(string(data))
}

os.O_APPEND|os.O_WRONLY opens the file for writing and positions the cursor at the end. defer f.Close() ensures the file handle is released when the function returns.

$ go run main.go
All the data I wish to write to a file
new data that wasn't there originally

File Permissions

The third argument to os.WriteFile and os.OpenFile is a FileMode — a Unix-style permission bitmask.

ValueMeaning
0644Owner can read/write; group and others can read
0600Owner can read/write; no access for others
0755Owner can read/write/execute; group and others can read/execute

0644 is the standard choice for data files. For full documentation see os.FileMode.

Frequently Asked Questions

How do I read a file line by line in Go?

Use bufio.Scanner with os.Open. Call scanner.Scan() in a loop and read each line with scanner.Text(). This is more memory-efficient than os.ReadFile for large files.

What is the difference between os.ReadFile and os.Open in Go?

os.ReadFile reads the entire file into memory in one call — simple and convenient for small files. os.Open returns a file handle you control, letting you read incrementally with a bufio.Scanner or io.Reader. Use os.Open when files are large or you need streaming access.

How do I check if a file exists in Go?

Use os.Stat and check the error: if errors.Is(err, os.ErrNotExist) is true, the file doesn’t exist. See Checking if a File Exists for a full example.

What does 0644 mean in os.WriteFile?

It’s a Unix permission bitmask in octal notation. 0644 means the file owner can read and write, while the group and others can only read. It’s the standard permission for data files that don’t need to be executable.

How do I append to a file in Go?

Open the file with os.OpenFile using the os.O_APPEND|os.O_WRONLY flags, then write using f.WriteString or f.Write. Always defer f.Close() to release the file handle.

Can os.WriteFile overwrite an existing file?

Yes — if the file already exists, os.WriteFile truncates it to zero length before writing. To add content without overwriting, use os.OpenFile with os.O_APPEND.

Conclusion

If you understand reading and writing files in Go, you have the foundation for working with any file format — CSV, JSON, PNG, or proprietary data. The os package gives you everything you need without third-party dependencies.

Further Reading