Go file
last modified July 22, 2026
In this tutorial we explore file operations in Go. The article is organized into logical groups: querying file metadata (existence, size, modification time), CRUD operations (create, write, read, delete, remove directories, rename), file manipulation (copy, append, truncate), temporary files, directory listing and walking, and advanced operations such as file seeking.
The primary package for file operations is os. It provides
functions for opening, creating, and deleting files, as well as retrieving
file metadata via os.Stat. Helper packages include
path/filepath for directory walking and bufio for
buffered reading.
Since Go 1.16, many common file operations that previously required the
ioutil package are available directly on os.
For instance, ioutil.ReadFile and ioutil.WriteFile
are now os.ReadFile and os.WriteFile.
Among the key functions, os.Stat returns the
FileInfo structure describing a file, which provides its size,
modification time, and permissions.
Go check if file exists
In the following example, we check if the given file exists.
package main
import (
"errors"
"fmt"
"os"
)
func main() {
_, err := os.Stat("words.txt")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("file does not exist")
} else {
fmt.Println("file exists")
}
}
We call the os.Stat function on the file. If the function returns
the os.ErrNotExist error, the file does not exist.
Go file size
In the following example, we get the file size.
package main
import (
"fmt"
"log"
"os"
)
func main() {
fInfo, err := os.Stat("words.txt")
if err != nil {
log.Fatal(err)
}
fsize := fInfo.Size()
fmt.Printf("The file size is %d bytes\n", fsize)
}
First, we get the FileInfo structure with os.Stat.
Then we get the size of the file in bytes from the structure with
Size function.
Go file last modified time
In the following example, we get the last modification time of the given file.
package main
import (
"fmt"
"log"
"os"
)
func main() {
fileName := "words.txt"
fileInfo, err := os.Stat(fileName)
if err != nil {
log.Fatal(err)
}
mTime := fileInfo.ModTime()
fmt.Println(mTime)
}
We get the last modification time from the FileInfo structure using
its ModTime function.
Go create file
The os.Create function creates or truncates the given file. If the
file already exists, it is truncated. If the file does not exist, it is created
with mode 0666.
package main
import (
"fmt"
"log"
"os"
)
func main() {
file, err := os.Create("empty.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
fmt.Println("file created")
}
The example creates an empty file.
Go write file
The os.WriteFile function writes data to the specified file. If
the file does not exist, it is created; otherwise it is truncated before
writing.
package main
import (
"fmt"
"log"
"os"
)
func main() {
fileName := "data.txt"
val := "old\nfalcon\nsky\ncup\nforest\n"
data := []byte(val)
err := os.WriteFile(fileName, data, 0644)
if err != nil {
log.Fatal(err)
}
fmt.Println("done")
}
The example writes a few words into a file.
val := "old\nfalcon\nsky\ncup\nforest\n" data := []byte(val)
We have a string from which we create a slice of bytes.
err := os.WriteFile(fileName, data, 0644)
We write the slice of bytes to the given filename with the 0644 permissions.
The permission value is an octal number: 6 grants read and write
for the owner, 4 grants read-only for the group, and the last
4 grants read-only for others. The effective permissions may be
further modified by the system's umask, which subtracts
permissions from the requested value.
In the next example, we write a slice of strings to a file.
package main
import (
"fmt"
"log"
"os"
)
func main() {
fileName := "data.txt"
f, err := os.Create(fileName)
if err != nil {
log.Fatal(err)
}
defer f.Close()
words := []string{"sky", "falcon", "rock", "hawk"}
for _, word := range words {
_, err := f.WriteString(word)
if err != nil {
log.Fatal(err)
}
_, err = f.WriteString("\n")
if err != nil {
log.Fatal(err)
}
}
fmt.Println("done")
}
The WriteString method writes a string to the file.
Go read file
The os.ReadFile function reads the file specified as a parameter
and returns the contents. It reads the whole file into memory at once;
therefore, it should not be used for very large files.
package main
import (
"fmt"
"log"
"os"
)
func main() {
content, err := os.ReadFile("words.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(content))
}
In the code example, we read the contents of a text file and print it to the console.
A more appropriate way for a large file is to read it line by line. This way the program does not take huge amounts of memory.
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
f, err := os.Open("words.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
The example reads a text file line by line.
f, err := os.Open("words.txt")
The os.Open function opens the specified file for reading. If
successful, the functions on the returned file can be used for reading; the
associated file descriptor has mode O_RDONLY.
scanner := bufio.NewScanner(f)
The bufio.NewScanner function returns a new Scanner to
read from.
for scanner.Scan() {
fmt.Println(scanner.Text())
}
With the Scan function we advance to the next token. We get the
advancement with the Text function. In the default mode, the
Scan function advances by lines.
Go delete file
The os.Remove deletes the given file.
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := os.Remove("words.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("file deleted")
}
The example removes a file.
Go remove directory
The os.RemoveAll function removes a file or an entire directory
and all its contents. Unlike os.Remove, it works on non-empty
directories.
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := os.RemoveAll("mydir")
if err != nil {
log.Fatal(err)
}
fmt.Println("directory removed")
}
The example removes the mydir directory and everything inside it.
Go rename file
The os.Rename function renames (moves) a file or directory.
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := os.Rename("words.txt", "words_backup.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("file renamed")
}
The example renames words.txt to words_backup.txt.
Go copy file
The io.Copy function copies from a source Reader to a
destination Writer. It is more memory-efficient than reading the
entire file into memory.
package main
import (
"io"
"log"
"os"
)
func main() {
src := "words.txt"
dest := "words2.txt"
srcFile, err := os.Open(src)
if err != nil {
log.Fatal(err)
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
log.Fatal(err)
}
defer destFile.Close()
written, err := io.Copy(destFile, srcFile)
if err != nil {
log.Fatal(err)
}
log.Printf("copied %d bytes", written)
}
In the code example, we copy a file with io.Copy. It reads from
the source file and writes to the destination file, returning the number of
bytes copied.
Go append to file
In order to append to a file, we include the os.O_APPEND flag
to the flags of the os.OpenFile function.
package main
import (
"log"
"os"
)
func main() {
fileName := "words.txt"
f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
if _, err := f.WriteString("cloud\n"); err != nil {
log.Fatal(err)
}
}
The example appends one word to the words.txt file using the
WriteString function.
Go truncate file
The os.Truncate function changes the size of a file without
modifying its permissions. It is useful for quickly emptying a file without
recreating it.
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := os.Truncate("words.txt", 0)
if err != nil {
log.Fatal(err)
}
fmt.Println("file truncated")
}
The example truncates words.txt to zero length, effectively
emptying it.
Go create temp file and directory
The os.CreateTemp and os.MkdirTemp functions create
temporary files and directories with automatically generated names. They are
commonly used in tests and data processing pipelines. The temporary items
should be cleaned up with os.RemoveAll.
package main
import (
"fmt"
"log"
"os"
)
func main() {
tmpFile, err := os.CreateTemp("", "example-*.txt")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpFile.Name())
fmt.Println(tmpFile.Name())
_, err = tmpFile.WriteString("temporary data")
if err != nil {
log.Fatal(err)
}
tmpDir, err := os.MkdirTemp("", "example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmpDir)
fmt.Println(tmpDir)
}
The example creates a temp file and a temp directory. The * in the
pattern is replaced with a random string. Both are cleaned up with
defer.
Go list directory
The os.ReadDir function (Go 1.16+) reads the contents of a
directory and returns a slice of os.DirEntry values.
package main
import (
"fmt"
"log"
"os"
)
func main() {
entries, err := os.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
fmt.Println(entry.Name())
}
}
The example lists all files and directories in the current working directory.
Go list files
The filepath.Walk walks the file tree, calling the specified
function for each file or directory in the tree, including root. The function is
recursively walking all subdirectories.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
var files []string
root := "."
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return nil
}
if !info.IsDir() && filepath.Ext(path) == ".txt" {
files = append(files, path)
}
return nil
})
if err != nil {
log.Fatal(err)
}
for _, file := range files {
fmt.Println(file)
}
}
In the code example, we search for files with .txt extension.
var files []string
The matching files are stored in the files slice.
root := "."
This is the root directory where we start searching.
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
The first parameter of the filepath.Walk is the root directory. The
second parameter is the walk function; the function called by
filepath.Walk to visit each each file or directory.
if err != nil {
fmt.Println(err)
return nil
}
Print the error if there is one, but continue searching elsewhere.
if !info.IsDir() && filepath.Ext(path) == ".txt" {
files = append(files, path)
}
We append the file to the files slice if the file is not a
directory and it has the .txt extension.
for _, file := range files {
fmt.Println(file)
}
Finally, we go over the files slice and print all matching files
to the console.
Go walk directory with WalkDir
Starting from Go 1.16, filepath.WalkDir is available as a more
efficient alternative to filepath.Walk. Instead of
os.FileInfo, the callback receives os.DirEntry which
avoids an extra os.Stat call on every visited item.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
var files []string
root := "."
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
fmt.Println(err)
return nil
}
if !d.IsDir() && filepath.Ext(path) == ".txt" {
files = append(files, path)
}
return nil
})
if err != nil {
log.Fatal(err)
}
for _, file := range files {
fmt.Println(file)
}
}
The example is a rewrite of the previous filepath.Walk example
using WalkDir. The callback uses os.DirEntry instead
of os.FileInfo, and the root directory is set to the current
working directory (.).
Go file seek
The Seek method sets the offset for the next read or write on a
file. It returns the new offset relative to the start of the file. This enables
random access — reading or overwriting data at a specific position.
package main
import (
"fmt"
"log"
"os"
)
func main() {
f, err := os.OpenFile("words.txt", os.O_RDWR, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// seek to the 6th byte
_, err = f.Seek(6, 0)
if err != nil {
log.Fatal(err)
}
buf := make([]byte, 4)
n, err := f.Read(buf)
if err != nil {
log.Fatal(err)
}
fmt.Printf("read %d bytes: %s\n", n, string(buf))
}
The example opens a file in read-write mode, seeks to byte offset 6, and reads four bytes from that position.
Source
In this article we have worked with files in Golang.
Author
List all Go tutorials.