File I/O

Most applications interact with files.

Common use cases:

  • Configuration files

  • JSON documents

  • Logs

  • Images

  • Databases

  • Binary data

Go provides both high-level and low-level APIs for working with files.


Reading an Entire File

The simplest way to read a file:

data, err := os.ReadFile("hello.txt")
if err != nil {
    fmt.Println(err)
    return
}
 
fmt.Println(string(data))

Suppose hello.txt contains:

Hello Go

Output:

Hello Go

Why Does ReadFile Return []byte?

os.ReadFile() returns:

[]byte

instead of

string

because files are fundamentally sequences of bytes.

A file may contain:

  • Plain text

  • JSON

  • Images

  • PDFs

  • ZIP archives

  • Executables

  • Encrypted data

Returning []byte avoids assuming that every file contains text.

The programmer decides how to interpret the bytes.

For example:

Convert to text:

text := string(data)

Decode JSON:

json.Unmarshal(data, &config)

Decrypt:

plaintext := Decrypt(data)

Writing a File

err := os.WriteFile(
    "hello.txt",
    []byte("Hello Go"),
    0644,
)
 
if err != nil {
    fmt.Println(err)
}

File Permissions

Example:

0644

Meaning:

Owner : Read + Write
Group : Read
Others: Read

This is the standard permission for normal files on Unix-like systems.


High-Level API

os.ReadFile() internally performs:

Open

Read

Close

The caller never receives the file handle.

Therefore no cleanup is required.


Low-Level API

file, err := os.Open("hello.txt")
if err != nil {
    return
}
 
defer file.Close()

The caller owns the file handle and is responsible for closing it.


Reading Large Files

Loading an entire file into memory is not always appropriate.

Instead:

scanner := bufio.NewScanner(file)
 
for scanner.Scan() {
    line := scanner.Text()
 
    fmt.Println(line)
}

This reads one line at a time.

Memory usage remains nearly constant regardless of file size.


Streaming

Streaming means:

Read

Process

Discard

Read Next

Instead of:

Load Entire File

Process

Streaming is essential for:

  • Large log files

  • Network protocols

  • Video processing

  • Data pipelines


Choosing the Right API

Small files:

os.ReadFile()

Advantages:

  • Simple

  • Concise

  • Easy to read

Suitable for:

  • Configuration

  • JSON

  • Small text files


Large files:

os.Open()
 
bufio.Scanner

Advantages:

  • Constant memory usage

  • Better scalability

Suitable for:

  • Logs

  • Large CSV files

  • Streaming data


File Paths

Relative path:

hello.txt

Resolved relative to the current working directory.

Absolute paths:

Linux:

/home/user/file.txt

Windows:

C:\Users\User\file.txt

Current Working Directory

Useful for debugging:

wd, err := os.Getwd()
 
fmt.Println(wd)

Many “file not found” errors are caused by running the program from an unexpected directory.


High-Level vs Low-Level APIs

Go often provides two levels of abstraction.

Example:

ReadFile()

Open()

Read()

Similarly:

WriteFile()

OpenFile()

Write()

The high-level API is convenient for common tasks.

The low-level API provides more control.


Common Beginner Mistakes

Forgetting Error Handling

Incorrect:

data, _ := os.ReadFile(...)

During learning, always check errors.


Forgetting []byte

Incorrect:

os.WriteFile(
    "file.txt",
    "Hello",
    0644,
)

Correct:

os.WriteFile(
    "file.txt",
    []byte("Hello"),
    0644,
)

Forgetting string()

Incorrect:

fmt.Println(data)

Output:

[72 101 108 108 111]

Correct:

fmt.Println(string(data))

Using ReadFile for Very Large Files

For small files:

os.ReadFile()

For large files:

bufio.Scanner

Choose the API appropriate for the size of the data.


Comparison with C++

GoC++
os.ReadFile()std::ifstream + read all
os.WriteFile()std::ofstream
[]bytestd::vector<uint8_t>
string(data)Construct std::string from bytes
defer file.Close()RAII cleanup

Session Summary

Topics covered:

  • Reading files

  • Writing files

  • []byte

  • File permissions

  • High-level file APIs

  • Low-level file APIs

  • Streaming large files

  • Current working directory

Practical skills gained:

  • Reading text files

  • Writing text files

  • Converting between bytes and strings

  • Choosing between convenience and streaming APIs

  • Understanding resource ownership


Key Takeaways

  • Files are sequences of bytes.

  • os.ReadFile() returns []byte, not string.

  • os.ReadFile() opens, reads, and closes the file internally.

  • Use string(data) only when the file actually contains text.

  • Use os.ReadFile() for small files.

  • Use os.Open() with bufio.Scanner for large files.

  • Go often provides both a simple API and a more flexible low-level API.


Mental Model

Small file

ReadFile()
 
Large file

Open()

Scanner

Read → Process → Discard → Repeat

Next Topics