Question

How to Check If a File Exists in Go

gofilestandard-library

Question

How can I check whether a file exists in Go using the standard library, similar to Python's os.path.exists?

Is there an idiomatic Go approach for checking whether a file exists or does not exist?

For example, the goal is to determine whether a path points to an existing file before performing an operation on it.

Short Answer

By the end of this page, you will understand the idiomatic Go way to check whether a file or path exists. You will learn how to use os.Stat, how to detect a missing file with errors.Is(err, os.ErrNotExist), why Go often prefers trying the operation directly, and how to avoid common mistakes when handling file-related errors.

Concept

In Go, checking whether a file exists is usually done by asking the operating system for information about a path.

The most common function for this is:

info, err := os.Stat(path)

This function tries to retrieve file metadata such as size, permissions, and modification time.

  • If err == nil, the path exists.
  • If err indicates os.ErrNotExist, the path does not exist.
  • If err is something else, the path might exist, but Go could not access it for another reason, such as permission issues.

A common beginner idea is to want a simple boolean function like exists(path). While you can write one yourself, Go's standard library usually returns both a result and an error so you can tell why something failed.

That matters because these cases are different:

  • the file truly does not exist
  • the file exists but you do not have permission to read it
  • the path is invalid
  • the disk or filesystem has another problem

Go code is considered idiomatic when it handles these cases explicitly instead of collapsing everything into true or false.

Another important idea: in real Go programs, developers often avoid checking existence first unless they truly need that information. Instead, they try the operation directly and handle the error. This avoids a time-of-check to time-of-use problem, where a file could be deleted or changed between the check and the actual operation.

Mental Model

Think of os.Stat like asking a building receptionist, "Does room 204 exist, and can you tell me about it?"

Possible answers:

  • "Yes, here are the details." → the file exists
  • "That room does not exist." → the file does not exist
  • "I cannot tell you because you are not allowed access." → permission error
  • "Something else went wrong." → another filesystem error

So in Go, you do not just ask "yes or no?" You ask for information, and then inspect the response carefully.

Take Quiz

Syntax and Examples

The basic pattern is:

info, err := os.Stat("example.txt")
if err == nil {
    fmt.Println("Path exists")
    _ = info
} else if errors.Is(err, os.ErrNotExist) {
    fmt.Println("Path does not exist")
} else {
    fmt.Println("Other error:", err)
}

You need these imports:

import (
    "errors"
    "fmt"
    "os"
)

Example: check whether a path exists

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    path := "data.txt"

    _, err := os.Stat(path)
    if err == nil {
        fmt.Println("The path exists")
        return
    }

    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("The path does not exist")
        return
    }

    fmt.Println("Could not check path:", err)
}

Example: helper function

If you want a reusable helper, you can write:

func fileExists(path string) bool {
    _, err := os.Stat(path)
    return err == nil
}

This is simple, but it hides important errors. A better version is:

func pathExists(path string) (bool, error) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil
    }
    if errors.Is(err, os.ErrNotExist) {
        return false, nil
    }
    return false, err
}

This version distinguishes between "does not exist" and "something else went wrong."

os.Stat vs os.Lstat

  • os.Stat(path) follows symbolic links.
  • os.Lstat(path) checks the link itself instead of the target.

Use os.Lstat when you care about whether the path is a symlink itself.

Step by Step Execution

Consider this example:

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    path := "report.txt"

    _, err := os.Stat(path)

    if err == nil {
        fmt.Println("File exists")
    } else if errors.Is(err, os.ErrNotExist) {
        fmt.Println("File does not exist")
    } else {
        fmt.Println("Other error:", err)
    }
}

Step by step:

  1. path := "report.txt"

    • A string variable stores the path we want to check.
  2. _, err := os.Stat(path)

    • Go asks the operating system for file information.
    • _ ignores the returned file info because this example only cares whether the path exists.
    • err stores any problem that occurred.
  3. if err == nil

    • If there was no error, Go successfully found the path.
    • The program prints File exists.
  4. else if errors.Is(err, os.ErrNotExist)

    • If there was an error, this checks whether the error means the path is missing.
    • If yes, the program prints File does not exist.
  5. else

    • Any remaining error is something else.
    • Example: permission denied, broken filesystem, invalid path.
    • The program prints the actual error.

This pattern is useful because it handles all major outcomes safely.

Real World Use Cases

Checking path existence appears in many practical programs:

  • Configuration loading

    • Before loading config.json, a program may check whether the file is present.
  • Log file setup

    • A service may check whether a log directory exists before writing files.
  • Import/export tools

    • A script may verify that an input CSV file exists before parsing it.
  • CLI utilities

    • A command-line tool may warn the user if a specified path is missing.
  • Safe file creation

    • An app may avoid overwriting an existing file unless the user passes a --force flag.
  • Deployment scripts

    • Automation code may check for environment files, certificate files, or generated artifacts.

That said, many of these tasks are even better solved by attempting the real operation and handling the error directly.

Take Quiz

Real Codebase Usage

In real Go codebases, existence checks are usually used in a few common patterns.

Guard clauses

A program may stop early if a required file is missing:

_, err := os.Stat(configPath)
if errors.Is(err, os.ErrNotExist) {
    return fmt.Errorf("config file not found: %s", configPath)
}
if err != nil {
    return fmt.Errorf("could not access config file: %w", err)
}

Validation before processing

Code may validate user input paths before doing expensive work:

func validateInput(path string) error {
    _, err := os.Stat(path)
    if errors.Is(err, os.ErrNotExist) {
        return fmt.Errorf("input file does not exist")
    }
    return err
}

Prefer the operation itself

Often developers skip an existence check and just try to open the file:

f, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
    return fmt.Errorf("file not found: %s", path)
}
if err != nil {
    return err
}
defer f.Close()

This is usually more robust because the file could change between checking and opening.

Create only if missing

If you need to create a file only when it does not already exist, use an operation designed for that instead of checking first:

f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
    return err
}
defer f.Close()

This avoids race conditions and is a common production pattern.

Common Mistakes

1. Treating every error as "file does not exist"

Broken:

_, err := os.Stat(path)
if err != nil {
    fmt.Println("File does not exist")
}

Why it is wrong:

  • err != nil could mean permission denied, not found, or another error.

Better:

_, err := os.Stat(path)
if err == nil {
    fmt.Println("Exists")
} else if errors.Is(err, os.ErrNotExist) {
    fmt.Println("Does not exist")
} else {
    fmt.Println("Other error:", err)
}

2. Checking first, then assuming the result stays true

Broken idea:

if fileExists(path) {
    f, _ := os.Open(path)
    // assume this will always work
}

Why it is wrong:

  • The file may be deleted or changed between the check and the open.

Better:

f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close()

3. Writing a helper that hides useful errors

Too simple:

func exists(path string) bool {
    _, err := os.Stat(path)
    return err == nil
}

Problem:

  • false could mean not found or permission denied.

Prefer returning both a boolean and an error when needed.

4. Forgetting symbolic link behavior

  • os.Stat follows symlinks.
  • os.Lstat inspects the symlink itself.

If symlink behavior matters, choose the correct function.

5. Using old-style error comparisons carelessly

Older code may use helpers like os.IsNotExist(err). You may still see it, but modern Go often uses:

errors.Is(err, os.ErrNotExist)

This works well with wrapped errors.

Comparisons

ApproachWhat it doesGood forLimitation
os.Stat(path)Gets file info and follows symlinksGeneral existence checksMust inspect errors carefully
os.Lstat(path)Gets info about the path itself, including symlinksWorking with symlinksDifferent behavior from Stat
os.Open(path)Tries to open the file directlyReal work like reading filesNot just a check; actually opens the file
errors.Is(err, os.ErrNotExist)Checks whether an error means "not found"Idiomatic error handlingOnly useful after an operation returns an error
os.IsNotExist(err)Older helper for not-exist checksSeen in older codeLess flexible than errors.Is

os.Stat vs trying the operation directly

PatternExampleWhen to use
Check firstos.Stat(path)When you specifically need to know whether it exists
Try directlyos.Open(path)When your actual goal is to read the file

In many real programs, trying the real operation directly is the better choice.

Take Quiz

Cheat Sheet

info, err := os.Stat(path)

Results

  • err == nil → path exists
  • errors.Is(err, os.ErrNotExist) → path does not exist
  • other err → another problem occurred

Basic pattern

_, err := os.Stat(path)
if err == nil {
    // exists
} else if errors.Is(err, os.ErrNotExist) {
    // does not exist
} else {
    // other error
}

Imports

import (
    "errors"
    "os"
)

Prefer direct operation when possible

f, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
    // missing file
}

Symlink note

  • os.Stat follows symlinks
  • os.Lstat checks the symlink itself

Good rule

Do not assume err != nil means "missing file." Always inspect the error.

FAQ

Is there a built-in exists() function in Go?

No. Go usually expects you to use os.Stat or try the real operation directly and inspect the returned error.

What is the idiomatic way to check if a file exists in Go?

Use os.Stat(path) and then check whether the error is os.ErrNotExist with errors.Is(err, os.ErrNotExist).

Should I use os.Stat or os.Open?

If you only need to know whether the path exists, os.Stat is fine. If your real goal is to read the file, prefer os.Open and handle the error directly.

How do I check if a file does not exist in Go?

Use:

if errors.Is(err, os.ErrNotExist) {
    // file does not exist
}

Does os.Stat work for directories too?

Yes. os.Stat checks whether the path exists, whether it is a file, directory, or another filesystem entry.

What if I get a permission error?

That does not mean the file is missing. It means Go could not access it. Handle it separately from os.ErrNotExist.

What is the difference between os.Stat and os.Lstat?

os.Stat follows symbolic links. os.Lstat returns information about the symlink itself.

Is os.IsNotExist(err) still valid?

Yes, you may still see it in existing code. In modern Go, errors.Is(err, os.ErrNotExist) is commonly preferred.

Take Quiz

Mini Project

Description

Build a small Go program that checks whether a user-provided path exists and reports whether it is a file, a directory, or missing. This demonstrates idiomatic path checking, error handling, and basic file metadata usage.

Goal

Create a command-line tool that accepts a path and prints a clear result about its existence and type.

Requirements

  • Read a path from the command-line arguments.
  • Use the Go standard library to inspect the path.
  • Print whether the path exists, does not exist, or could not be accessed.
  • If the path exists, print whether it is a file or a directory.
Take Quiz

Keep learning

Related questions

View more