File Handling in Go

By Syncfusion®, Inc. · Last updated

Create, read, write and append files with os and bufio, make and list directories, read file info and permissions, and handle missing-file and permission errors.

What Is File Handling?

File = data saved on a disk with a name, like marks.csv or notes.txt.

File handling = creating, reading, writing and deleting files from your program.

Simple lesson: file handling lets a program save data and read it back later.

A Real-World Example

Think of a class notebook kept in the staff room.

  • Without it, the teacher would forget the marks when the bell rings.
  • She writes the marks in the notebook.
  • Next week, she reads them back.
  • At the end of term, she adds new pages.

Now the mapping:

  • The notebook = a file.
  • The staff room shelf = a folder (directory).
  • Writing, reading, adding pages = WriteFile, ReadFile, appending.

Why File Handling Is Important

  • Variables vanish when the program ends. Files stay.
  • Settings are kept in files, like config.json.
  • Data is shared between programs through files: reports, logs, CSV sheets.
  • Logs in files help you find out what went wrong later.

In this lab, every program gets the same pretend disk in its folder:

  • notes.txt, marks.csv, and a read-only locked.txt
  • a data folder with students.json and config.json

Each run starts fresh. On your computer, the same code reads and writes your real disk.

What You Will Learn

In this lesson, you will learn:

  1. How to create, read, write and append files.
  2. The quick helpers os.ReadFile() and os.WriteFile().
  3. How to create and read directories.
  4. How to read file information and permissions.
  5. How to handle missing files and permission errors.

File Operations

The Quick Way: os.ReadFile() and os.WriteFile()

Read a whole file

package main

import (
	"fmt"
	"os"
)

func main() {
	data, err := os.ReadFile("notes.txt")
	if err != nil {
		fmt.Println("could not read:", err)
		return
	}
	fmt.Print(string(data))
	fmt.Println("bytes:", len(data))
}
Bring your geometry box.
Maths test on Friday.
bytes: 47
  • os.ReadFile reads the whole file into a []byte.
  • string(data) turns the bytes into text.
  • Always check err. See Error Handling.

Write a file

package main

import (
	"fmt"
	"os"
)

func main() {
	text := "Annual day: 20 December\n"
	err := os.WriteFile("notice.txt", []byte(text), 0644)
	fmt.Println(err)

	data, _ := os.ReadFile("notice.txt")
	fmt.Print(string(data))
}
<nil>
Annual day: 20 December
  • os.WriteFile creates the file, or replaces everything in it.
  • 0644 is the permission: you can read and write, others can only read. See Permissions.

Try every action on the pretend disk.

Compare these:

  • WriteFile twice replaces the old text.
  • Append adds a line at the end.
  • ReadFile on a missing name gives no such file or directory.
  • WriteFile on locked.txt gives permission denied.

Create Files

os.Create = make a new empty file (or empty an old one), and open it for writing.

Create and write step by step

package main

import (
	"fmt"
	"os"
)

func main() {
	f, err := os.Create("attendance.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer f.Close()

	fmt.Fprintln(f, "Roll 1: present")
	fmt.Fprintln(f, "Roll 2: absent")
	f.WriteString("Roll 3: present\n")

	data, _ := os.ReadFile("attendance.txt")
	fmt.Print(string(data))
}
Roll 1: present
Roll 2: absent
Roll 3: present
  • os.Create gives an *os.File.
  • fmt.Fprintln(f, ...) writes a line into the file, like Println writes to the screen.
  • defer f.Close() makes sure the file is closed when main ends. See Functions.

Read Files Line by Line

For big files, read one line at a time with bufio.Scanner.

Read marks.csv line by line

  • bufio.Scanner uses little memory, even for a huge file.
  • Use strings.Split(sc.Text(), ",") to cut a CSV line into fields. See Strings.

Append Files

Append = add to the end of a file, keeping what is already there.

Add a line to a log

package main

import (
	"fmt"
	"os"
)

func main() {
	f, err := os.OpenFile("notes.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Fprintln(f, "Science project due Monday.")
	f.Close()

	data, _ := os.ReadFile("notes.txt")
	fmt.Print(string(data))
}
Bring your geometry box.
Maths test on Friday.
Science project due Monday.
FlagMeans
os.O_APPENDwrite at the end
os.O_WRONLYopen for writing only
os.O_CREATEcreate the file if it is missing
os.O_TRUNCempty the file first
  • Join flags with |. See Operators.

Which function to use

Simple lesson: use ReadFile and WriteFile for small files, a Scanner for line-by-line reading, and O_APPEND to add to the end.

Directory Operations

Directory = a folder that holds files and other folders.

Create Directories

Make folders

package main

import (
	"fmt"
	"os"
)

func main() {
	err := os.Mkdir("reports", 0755)
	fmt.Println(err)
	err = os.MkdirAll("reports/2025/term1", 0755)
	fmt.Println(err)
	err = os.Mkdir("reports", 0755)
	fmt.Println(err)
}
<nil>
<nil>
mkdir reports: file exists
  • os.Mkdir makes one folder. It fails if the folder exists.
  • os.MkdirAll makes every missing folder on the path, and does not fail if they exist.

Read Directories

List a folder

package main

import (
	"fmt"
	"os"
)

func main() {
	entries, err := os.ReadDir(".")
	if err != nil {
		fmt.Println(err)
		return
	}
	for _, e := range entries {
		kind := "file"
		if e.IsDir() {
			kind = "folder"
		}
		fmt.Println(kind, e.Name())
	}
}
folder data
file locked.txt
file marks.csv
file notes.txt
  • "." means "the current folder".
  • os.ReadDir gives the entries sorted by name.

Deleting

  • os.Remove(name) deletes one file, or one empty folder.
  • os.RemoveAll(path) deletes a folder and everything inside. Use it with great care.
  • os.Rename(old, new) renames or moves a file.

Simple lesson: MkdirAll makes folders, ReadDir lists them, and Remove deletes.

File Metadata

Metadata = data about a file: its name, size, type, permissions and when it changed.

File Info

os.Stat

package main

import (
	"fmt"
	"os"
)

func main() {
	info, err := os.Stat("marks.csv")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("Name:", info.Name())
	fmt.Println("Size:", info.Size(), "bytes")
	fmt.Println("Folder?", info.IsDir())
	fmt.Println("Mode:", info.Mode())
}
Name: marks.csv
Size: 53 bytes
Folder? false
Mode: -rw-r--r--
  • info.ModTime() gives the last time the file changed.

Permissions

Permission = who may read, write or run a file.

  • Written as three digits, in octal: 0644, 0755.
  • The digits are for: owner, group, everyone else.
  • Each digit adds: read = 4, write = 2, run (execute) = 1.
NumberLettersMeaning
0644-rw-r--r--owner reads and writes, others read
0755drwxr-xr-x (folder)owner does everything, others read and enter
0444-r--r--r--everyone can only read
  • On Windows, Go keeps only "read-only or not". The numbers matter most on Linux and macOS.

Simple lesson: os.Stat tells you a file's size, type and permissions.

File Error Handling

Missing Files

Check for a missing file

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	_, err := os.ReadFile("homework.txt")
	fmt.Println(err)
	if errors.Is(err, fs.ErrNotExist) {
		fmt.Println("No homework file yet. Creating one.")
		os.WriteFile("homework.txt", []byte("none\n"), 0644)
	}
	data, _ := os.ReadFile("homework.txt")
	fmt.Print(string(data))
}
open homework.txt: no such file or directory
No homework file yet. Creating one.
none
  • errors.Is(err, fs.ErrNotExist) checks for "file does not exist", even when wrapped.
  • os.IsNotExist(err) is an older way to ask the same question.

Permission Errors

A read-only file

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	err := os.WriteFile("locked.txt", []byte("changed"), 0644)
	fmt.Println(err)
	fmt.Println(errors.Is(err, fs.ErrPermission))

	data, _ := os.ReadFile("locked.txt")
	fmt.Print(string(data))
}
open locked.txt: permission denied
true
Only the office can change this.
  • Reading still works. Writing is refused.
  • The file is not changed.

Always close files you open: defer f.Close() right after a successful Open or Create.

An open file uses system resources, and written data may not be saved until the file is closed.

Easy way to remember

  • Small file: os.ReadFile, os.WriteFile
  • Line by line: os.Open + bufio.Scanner
  • Add to the end: os.OpenFile with O_APPEND
  • Missing: errors.Is(err, fs.ErrNotExist)

Simple lesson: check every file error, and use errors.Is to tell "missing" from "not allowed".

TL;DR

  1. File handling = save and load data that outlives the program.
  2. Read all = os.ReadFile(name). Write all = os.WriteFile(name, data, 0644).
  3. Create = os.Create(name), then fmt.Fprintln(f, ...), defer f.Close().
  4. Line by line = os.Open + bufio.NewScanner + sc.Scan() / sc.Text().
  5. Append = os.OpenFile(name, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644).
  6. Folders = os.Mkdir, os.MkdirAll, os.ReadDir, os.Remove, os.RemoveAll.
  7. Metadata = os.Stat → Name(), Size(), IsDir(), Mode().
  8. Errors = fs.ErrNotExist, fs.ErrPermission, checked with errors.Is.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Ignoring the error from OpenThe file is always thereCheck err firstA nil-file panic later.
Forgetting f.Close()Go closes files for youClose with defer f.Close()Leaked files, lost writes.
os.WriteFile to add a lineIt appendsIt replaces the whole fileOld content is lost.
os.Mkdir("a/b/c", 0755)It makes every folderUse MkdirAllno such file or directory
Permission 644 without 0Same as 0644Without 0, it is decimal 644Strange permissions.
err == os.ErrNotExistErrors compare with ==Use errors.IsFalse for wrapped errors.
string(data) on a huge fileIt is fine for any sizeRead big files line by lineSlow, high memory use.

Check Yourself

Exercises

What to Remember

  • Files keep data after the program ends.
  • os.ReadFile and os.WriteFile handle small files in one call.
  • Use os.Open + bufio.Scanner for line-by-line reading.
  • O_APPEND adds to the end. WriteFile replaces everything.
  • MkdirAll, ReadDir and Stat work with folders and file details.
  • Close files with defer f.Close(), and check errors with errors.Is.

Next: JSON. You will learn how to turn Go structs into JSON text, and read JSON back into structs.