#Reading And Writing To Files in Go
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.
| Value | Meaning |
|---|---|
0644 | Owner can read/write; group and others can read |
0600 | Owner can read/write; no access for others |
0755 | Owner 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
- Parsing JSON Files with Go
- Parsing XML Files with Go
- Go JSON Tutorial
- Error Handling in Go
- Reading and Writing Files - Quick Reference
- Checking if a File Exists
- Getting File Info
Continue Learning
Structured Logging in Go with log/slog - The Complete Guide
Learn structured logging in Go with the standard library log/slog package - handlers, levels, context, custom handlers, and why it replaces logrus, zap and zerolog.
An Introduction to Go Closures - Tutorial
Learn how closures work in Go with simple, practical examples. Understand lexical scoping and how closures capture and maintain their own state.
Makefiles for Go Developers
Learn how to use Makefiles in Go projects to automate builds, cross-compilation, and common dev tasks with a single `make` command.
Go Interfaces Tutorial
Learn how Go interfaces work — implicit satisfaction, defining contracts, and writing flexible, testable code without inheritance or explicit implements keywords.