Question
I am learning Go and want to understand how to read from and write to regular files.
I can open a file like this:
inFile, err := os.Open(INFILE)
if err != nil {
// handle error
}
defer inFile.Close()
However, I do not understand how to actually read the file contents. The Read method takes a []byte parameter, and that part is confusing to me:
func (file *File) Read(b []byte) (n int, err error)
How do you use Read properly, and what is the standard way to read from and write to a file in Go?
Short Answer
By the end of this page, you will understand how file reading and writing work in Go, why Read uses a byte slice, and how to use common standard library functions to load whole files, read in chunks, and write data safely.
Concept
In Go, files are treated as streams of bytes. That means when you read from a file, Go gives you raw bytes, and when you write to a file, you send bytes back.
The key idea is this:
- A file on disk stores data as bytes
- Go's
Readmethod fills a byte slice you provide - The method returns how many bytes were actually written into that slice
- You then convert those bytes into a string or process them directly
This design matters because it is flexible:
- Text files can be read as bytes and converted to strings
- Binary files can be processed without conversion
- Large files can be read in small chunks instead of loading everything into memory
In beginner code, there are usually two common approaches:
- Read the whole file at once
- Best for small text files
- Simple and readable
- Read the file in chunks
- Better for large files
- Uses
Read([]byte)directly
For writing, Go follows the same byte-based model:
- You can write a string by converting it to bytes
- Or use helper functions that write strings directly
Modern Go code often uses the os and io packages together for this.
Mental Model
Think of a file like water coming through a pipe.
- The file itself is the pipe
- Your
[]bytebuffer is a bucket Read(buffer)fills the bucket with some amount of water- The returned
ntells you how full the bucket is this time
If the file is bigger than the bucket, you have to keep filling the bucket until there is no more data.
For writing, imagine the reverse:
- You have water to send into the pipe
Writepushes your bytes into the file
So the important thing is not that Read gives you data directly. Instead, you give Read a place to put the data.
Syntax and Examples
Reading an entire file
For small files, the easiest approach is:
package main
import (
"fmt"
"os"
)
func main() {
data, err := os.ReadFile("example.txt")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Println(string(data))
}
Explanation
os.ReadFilereads the whole file into memorydatais a[]bytestring(data)converts the bytes into readable text
Writing an entire file
package main
import (
"fmt"
"os"
)
func main() {
content := []byte("Hello from Go!\n")
err := os.WriteFile("output.txt", content, )
err != {
fmt.Println(, err)
}
fmt.Println()
}
Step by Step Execution
Consider this example:
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
buffer := make([]byte, 4)
for {
n, err := file.Read(buffer)
if err == io.EOF {
break
}
if err != nil {
fmt.Println(err)
return
}
fmt.Println("bytes read:", n)
fmt.Println("chunk:", string(buffer[:n]))
}
}
Assume example.txt contains:
HelloGo
What happens
os.Open("example.txt")opens the file for reading.defer file.Close()schedules the file to be closed when ends.
Real World Use Cases
File reading and writing appear in many common programs:
Reading use cases
- Loading a configuration file such as JSON, YAML, or plain text
- Reading templates or static content from disk
- Processing CSV or log files line by line
- Importing data for scripts and command-line tools
Writing use cases
- Saving reports to a text file
- Writing log output
- Exporting CSV or JSON data
- Creating cache files or generated code
Example scenarios
- A CLI tool reads
.envor config files at startup - A data-processing script reads a large input file in chunks
- A web server writes uploaded files to disk
- A batch job generates output files for other systems
Real Codebase Usage
In real Go projects, developers usually choose the file API based on file size and purpose.
Common patterns
1. Read the whole file for small inputs
data, err := os.ReadFile("config.json")
if err != nil {
return err
}
Used for:
- config files
- templates
- test fixtures
2. Use guard clauses for errors
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
This keeps code clean and avoids deep nesting.
3. Stream large files
buffer := make([]byte, 1024)
for {
n, err := file.Read(buffer)
if err == io.EOF {
break
}
if err != nil {
return err
}
process(buffer[:n])
}
Used for:
- logs
- large exports
- uploads
- binary processing
4. Validate before writing
Common Mistakes
1. Ignoring the number of bytes read
Broken code:
buffer := make([]byte, 100)
file.Read(buffer)
fmt.Println(string(buffer))
Problem:
- The file may contain fewer than 100 bytes
- The buffer may contain extra zero values or old data
Correct approach:
n, err := file.Read(buffer)
if err != nil && err != io.EOF {
fmt.Println(err)
return
}
fmt.Println(string(buffer[:n]))
2. Forgetting to close the file
Broken code:
file, err := os.Open("example.txt")
if err != nil {
return
}
// missing file.Close()
Fix:
file, err := os.Open("example.txt")
if err != nil {
return
}
defer file.Close()
3. Ignoring errors
Broken code:
Comparisons
| Approach | Best for | Reads whole file? | Memory usage | Example |
|---|---|---|---|---|
os.ReadFile | Small files, simple programs | Yes | Higher | Config files |
File.Read with buffer | Large files, streaming | No | Lower | Logs, big data |
bufio.Scanner | Reading line by line | No | Low | Text processing |
os.WriteFile | Simple full writes | Writes all at once | Depends on data size | Reports, configs |
Cheat Sheet
Quick reference
Open a file for reading
file, err := os.Open("input.txt")
if err != nil {
return
}
defer file.Close()
Read entire file
data, err := os.ReadFile("input.txt")
Read into a buffer
buffer := make([]byte, 1024)
n, err := file.Read(buffer)
Use only bytes actually read
buffer[:n]
Detect end of file
if err == io.EOF {
break
}
Create or overwrite a file
file, err := os.Create("output.txt")
Write bytes
_, err = file.Write([]byte("hello"))
Write whole file at once
FAQ
Why does Go file reading use []byte?
Because files are byte streams. This works for both text and binary data.
How do I read a file as a string in Go?
Use os.ReadFile, then convert the result:
data, err := os.ReadFile("file.txt")
text := string(data)
What is the difference between os.ReadFile and file.Read?
os.ReadFile reads the entire file at once. file.Read reads into a buffer you provide, which is better for streaming or large files.
How do I write text to a file in Go?
You can use:
err := os.WriteFile("out.txt", []byte("hello"), 0644)
or create a file and call Write.
Do I always need to close files in Go?
Yes, when you open or create a file handle, you should usually close it with defer file.Close().
What does mean?
Mini Project
Description
Build a small Go program that reads a text file, counts its lines, and writes a short report to another file. This project demonstrates both reading and writing files in a practical way.
Goal
Create a program that reads an input text file and writes a summary report containing the file contents and line count.
Requirements
- Read the contents of a text file named
input.txt. - Count how many lines are in the file.
- Write a new file named
report.txt. - Include the original content and the line count in the report.
- Handle file errors properly.
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.