Question
I cannot find a file.ReadLine function in Go.
How can I read a file line by line in Go?
Short Answer
By the end of this page, you will understand how to read a file line by line in Go, why bufio.Scanner is usually the simplest choice, when to use bufio.Reader instead, and how this pattern is used in real programs for logs, configuration files, and text processing.
Concept
In Go, reading a file line by line is usually done with the bufio package rather than a method directly on os.File.
An os.File gives you access to the file itself, but buffered helpers such as bufio.Scanner and bufio.Reader make reading text much easier.
The most common beginner-friendly approach is:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// use line
}
if err := scanner.Err(); err != nil {
// handle error
}
Why this matters:
- Many real programs process text files one line at a time.
- Reading line by line is memory-efficient for large files.
- It lets you validate, transform, or filter data as you read it.
- This pattern is common for logs, CSV-like text, config files, and command-line tools.
In short, Go separates responsibilities:
os.Openopens the file.bufio.Scannerorbufio.Readerhelps you read structured text from it.- Your code decides what to do with each line.
Mental Model
Think of a file as a long roll of paper.
os.Filegives you access to the roll.bufio.Scanneracts like a reading guide that moves from one line break to the next.- Each time you call
Scan(), it points to the next line. Text()gives you the current line.
So instead of asking the file itself, “give me one line,” you wrap it in a tool designed specifically for line-by-line reading.
Syntax and Examples
Using bufio.Scanner
This is the most common way to read a text file line by line in Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("error opening file:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
fmt.Println("error reading file:", err)
}
}
How it works
os.Open("example.txt")opens the file for reading.defer file.Close()ensures the file is closed whenmainfinishes.bufio.NewScanner(file)creates a scanner that reads from the file.scanner.Scan()moves to the next line and returnstruewhile there is data.
Step by Step Execution
Consider this code:
file, err := os.Open("example.txt")
if err != nil {
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println(err)
}
Assume example.txt contains:
apple
banana
cherry
Step by step
-
os.Open("example.txt")- Go opens the file.
filenow refers to that file.
-
defer file.Close()- Go schedules the file to be closed later.
- This helps prevent resource leaks.
-
scanner := bufio.NewScanner(file)- A scanner is created to read from the file.
- By default, it splits input by lines.
-
First
scanner.Scan()
Real World Use Cases
Reading files line by line is common in many Go programs.
Log processing
A script can scan a server log and count error lines:
if strings.Contains(line, "ERROR") {
errorCount++
}
Configuration loading
Some tools read simple config files line by line, skipping blank lines and comments.
if line == "" || strings.HasPrefix(line, "#") {
continue
}
Importing text data
A program can read user IDs, email addresses, or product codes from a file and process them one at a time.
Command-line utilities
Many CLI tools stream input files instead of loading everything into memory at once.
Data cleanup scripts
A Go script can read each line, trim spaces, validate the format, and write cleaned output to another file.
Real Codebase Usage
In real projects, developers usually wrap line-reading logic in reusable functions and combine it with validation and error handling.
Common pattern: open, scan, process
func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
// process line
}
return scanner.Err()
}
Guard clauses
Developers often return early when a file cannot be opened or a line is invalid.
if err != nil {
return err
}
Validation while reading
Instead of reading the whole file first, code often validates each line immediately.
if !strings.Contains(line, ":") {
continue
}
Streaming large files
For large logs or exports, line-by-line reading avoids loading the entire file into memory.
Common Mistakes
1. Forgetting to check scanner.Err()
Broken example:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
Problem:
- The loop stops on both end-of-file and read error.
- Without
scanner.Err(), you cannot tell whether something went wrong.
Fix:
if err := scanner.Err(); err != nil {
fmt.Println("read error:", err)
}
2. Forgetting to close the file
Broken example:
file, _ := os.Open("example.txt")
scanner := bufio.NewScanner(file)
Problem:
- Open files use system resources.
Fix:
defer file.Close()
3. Ignoring the error from os.Open
Broken example:
file, _ := os.Open("missing.txt")
scanner := bufio.NewScanner(file)
Comparisons
bufio.Scanner vs bufio.Reader
| Feature | bufio.Scanner | bufio.Reader |
|---|---|---|
| Best for | Simple line-by-line reading | More control over reading |
| Ease of use | Very easy | Slightly more manual |
| Returns newline | No | Can keep newline |
| Large lines | Limited by token buffer unless adjusted | Better for very large lines |
| Custom delimiters | Limited but possible with split functions | Flexible |
os.ReadFile vs line-by-line reading
Cheat Sheet
Quick pattern with bufio.Scanner
file, err := os.Open("example.txt")
if err != nil {
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
_ = line
}
if err := scanner.Err(); err != nil {
return
}
Rules to remember
os.Open()opens the file.defer file.Close()closes it later.bufio.NewScanner(file)creates a line scanner.scanner.Scan()moves to the next line.scanner.Text()gets the current line.scanner.Err()checks for errors after the loop.
Use Scanner when
- you want simple line-by-line reading
- lines are normal text size
- you do not need newline characters
Use Reader when
- you need to keep
\n
FAQ
Is there a ReadLine method on os.File in Go?
No. In Go, line-based reading is usually done with helpers from the bufio package, such as bufio.Scanner or bufio.Reader.
What is the easiest way to read a file line by line in Go?
Use bufio.Scanner. It is simple, readable, and designed for token-based input such as lines.
Does scanner.Text() include the newline character?
No. It returns the line content without the trailing newline.
What should I use for very large lines?
If lines may be very large, either increase the scanner buffer or use bufio.Reader for more control.
Should I use os.ReadFile instead?
Only if the file is small and you want all its contents at once. For large files, line-by-line reading is more memory-efficient.
Why do I need to check scanner.Err() after the loop?
Because Scan() returns false for both end-of-file and actual read errors. scanner.Err() tells you whether an error occurred.
Mini Project
Description
Build a small Go program that reads a log file line by line and counts how many lines contain the word ERROR. This demonstrates opening a file, scanning line by line, checking each line, and handling errors properly.
Goal
Create a command-line program that prints the total number of error lines in a text file.
Requirements
- Open a text file named
app.log - Read the file one line at a time
- Count lines that contain the word
ERROR - Print the final count
- Handle file and scanning errors correctly
Keep learning
Related questions
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.
Convert String to Integer in Go: Idiomatic Parsing with strconv.Atoi
Learn the idiomatic way to convert a string to an int in Go using strconv.Atoi, with examples, errors, and common mistakes.