Question

How to List a Directory in Go

Question

I want to list the files and folders inside a single directory in Go without recursively visiting subdirectories.

I found filepath.Walk, but it automatically traverses into nested directories, which is not what I need.

What is the correct way to read just one directory level in Go?

// I do not want recursive traversal like this:
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
    return nil
})

Short Answer

By the end of this page, you will understand how to list the contents of a single directory in Go, when to use os.ReadDir instead of filepath.Walk, how to distinguish files from folders, and how this pattern is used in real applications.

Concept

In Go, there is an important difference between reading one directory and walking an entire directory tree.

  • Reading one directory means: “Show me the entries directly inside this folder.”
  • Walking a directory tree means: “Visit this folder and every nested folder inside it.”

If you only want the files and folders in one directory, filepath.Walk is usually the wrong tool because it is designed for recursive traversal.

For a single directory level, the right concept is to use directory-reading functions such as:

os.ReadDir(path)

This returns the entries directly inside the given directory. Each entry can represent:

  • a file
  • a directory
  • a symbolic link
  • another filesystem entry type

This matters because many programs need to inspect only one folder level, for example:

  • showing uploaded files
  • reading config files from a folder
  • building a simple file browser
  • scanning a log directory

Using the correct function makes your code:

  • simpler
  • faster to understand
  • less error-prone
  • more aligned with Go's standard library design

In modern Go, os.ReadDir is the usual choice for this task.

Mental Model

Think of a directory like a drawer in a filing cabinet.

  • os.ReadDir("myfolder") means: open this drawer and look at the items directly inside it.
  • filepath.Walk("myfolder", ...) means: open this drawer, then open every folder inside it, then every folder inside those, and keep going.

If you only need to know what is in one drawer, you should not search the entire building.

That is why os.ReadDir is a better fit for listing a single directory.

Take Quiz

Syntax and Examples

Basic syntax

entries, err := os.ReadDir(".")
if err != nil {
    log.Fatal(err)
}

for _, entry := range entries {
    fmt.Println(entry.Name())
}

Example: list files and folders in one directory

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    entries, err := os.ReadDir(".")
    if err != nil {
        log.Fatal(err)
    }

    for _, entry := range entries {
        if entry.IsDir() {
            fmt.Println("DIR :", entry.Name())
        } else {
            fmt.Println("FILE:", entry.Name())
        }
    }
}

What this does

  • os.ReadDir(".") reads the current directory.
  • It returns a slice of directory entries.
  • entry.Name() gives the name of each item.
  • entry.IsDir() tells you whether that item is a directory.

Example: read a specific folder

entries, err := os.ReadDir("/tmp")
if err != nil {
    log.Fatal(err)
}

for _, entry := range entries {
    fmt.Println(entry.Name())
}

If you need more file details

os.ReadDir gives you lightweight directory entries. If you also need size, permissions, or modification time, call Info() on an entry:

for _, entry := range entries {
    info, err := entry.Info()
    if err != nil {
        log.Println("could not read info for", entry.Name(), err)
        continue
    }

    fmt.Printf("%s %d bytes\n", entry.Name(), info.Size())
}

That gives you an os.FileInfo value with more metadata.

Step by Step Execution

Consider this program:

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(), entry.IsDir())
    }
}

Step by step

  1. os.ReadDir(".") asks the operating system to read the current directory.
  2. Go returns two values:
    • entries: a list of items in that directory
    • err: an error if something went wrong
  3. The if err != nil check stops the program if the directory cannot be read.
  4. The for _, entry := range entries loop processes each item one by one.
  5. entry.Name() prints the item's name.
  6. entry.IsDir() prints true if the item is a directory, otherwise false.

Example output

main.go false
assets true
README.md false

This tells you:

  • main.go is a file
  • assets is a directory
  • README.md is a file

Notice that Go does not automatically enter the assets directory. It only lists the current level.

Real World Use Cases

Listing a single directory is common in everyday Go programs.

Examples

  • Upload processing: read files waiting in an upload folder
  • Config loading: list config files in a configs/ directory
  • Static site tools: inspect templates or assets in one folder
  • Log management: list log files before archiving or deleting them
  • CLI utilities: build commands like a simplified ls
  • Backup tools: inspect top-level folders before deeper processing

Example: process only .json files in one folder

entries, err := os.ReadDir("configs")
if err != nil {
    log.Fatal(err)
}

for _, entry := range entries {
    if entry.IsDir() {
        continue
    }

    name := entry.Name()
    if filepath.Ext(name) == ".json" {
        fmt.Println("config file:", name)
    }
}

This is useful when you want to work with only the immediate contents of a folder and ignore nested directories.

Take Quiz

Real Codebase Usage

In real projects, developers rarely just print file names. They usually combine directory listing with filtering, validation, and error handling.

Common patterns

Guard clause for read errors

entries, err := os.ReadDir(dir)
if err != nil {
    return err
}

This keeps the rest of the function clean.

Skip directories early

for _, entry := range entries {
    if entry.IsDir() {
        continue
    }

    // process file
}

This is an example of an early-continue pattern.

Filter by extension

for _, entry := range entries {
    if entry.IsDir() {
        continue
    }
    if filepath.Ext(entry.Name()) != ".txt" {
        continue
    }

    // handle .txt file
}

Build full paths safely

When you need to open a file, combine the directory path and entry name with filepath.Join:

fullPath := filepath.Join(dir, entry.Name())

Avoid manual string concatenation because separators differ across operating systems.

Handle per-file metadata errors gracefully

for _, entry := range entries {
    info, err := entry.Info()
    if err != nil {
        log.Println("skipping:", entry.Name(), err)
        continue
    }

    fmt.Println(entry.Name(), info.Size())
}

In real codebases, this pattern is common because one bad file should not always stop the whole process.

Common Mistakes

1. Using filepath.Walk when recursion is not needed

Broken approach for this use case:

filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
    fmt.Println(path)
    return nil
})

Why it is a problem:

  • it visits subdirectories too
  • it does more work than needed
  • it makes simple code more complex

Use os.ReadDir instead.

2. Forgetting to handle errors

Broken code:

entries, _ := os.ReadDir("missing-folder")
for _, entry := range entries {
    fmt.Println(entry.Name())
}

Why this is bad:

  • if the directory does not exist, the failure is silently ignored
  • debugging becomes harder

Better:

entries, err := os.ReadDir("missing-folder")
if err != nil {
    log.Fatal(err)
}

3. Assuming names are full paths

Broken code:

for _, entry := range entries {
    os.Open(entry.Name())
}

Why this breaks:

  • entry.Name() is only the base name
  • it may fail unless your working directory matches the target directory

Better:

for _, entry := range entries {
    fullPath := filepath.Join(dir, entry.Name())
    _, err := os.Open(fullPath)
    if err != nil {
        log.Println(err)
    }
}

4. Forgetting to skip directories when you want files only

Broken code:

for _, entry := range entries {
    fmt.Println("processing", entry.Name())
}

If your logic expects files only, directories may cause later failures.

Better:

for _, entry := range entries {
    if entry.IsDir() {
        continue
    }
    fmt.Println("processing file", entry.Name())
}

5. Calling Info() unnecessarily

entry.IsDir() and entry.Name() are often enough. If you call Info() for every entry when you do not need extra metadata, you add unnecessary work.

Use Info() only when you actually need details like size or timestamps.

Comparisons

TaskBest choiceWhy
List one directory levelos.ReadDirSimple and direct
Walk all nested directoriesfilepath.Walk or filepath.WalkDirDesigned for recursion
Check whether a path exists and get metadataos.StatReturns file info for one path
Open and read file contentsos.OpenUsed for reading file data

os.ReadDir vs filepath.Walk

Featureos.ReadDirfilepath.Walk
Reads one directory onlyYesNo
Recursive by defaultNoYes
Good for simple listingYesLess ideal
Gives direct entriesYesCallback-based traversal

os.ReadDir vs older directory APIs

Older Go code may use functions like ioutil.ReadDir. In modern Go, prefer os.ReadDir.

APIStatusNotes
os.ReadDirPreferredModern standard choice
ioutil.ReadDirOlder styleSuperseded in newer Go versions
Take Quiz

Cheat Sheet

entries, err := os.ReadDir(dir)
if err != nil {
    return err
}

for _, entry := range entries {
    name := entry.Name()
    isDir := entry.IsDir()
    _ = name
    _ = isDir
}

Quick rules

  • Use os.ReadDir(path) to list one directory level.
  • Use filepath.Walk or filepath.WalkDir for recursive traversal.
  • entry.Name() gives the entry name, not the full path.
  • Use filepath.Join(dir, entry.Name()) to build full paths.
  • Use entry.IsDir() to check whether an item is a directory.
  • Use entry.Info() only when you need extra metadata.

Common pattern

entries, err := os.ReadDir(dir)
if err != nil {
    return err
}

for _, entry := range entries {
    if entry.IsDir() {
        continue
    }

    fullPath := filepath.Join(dir, entry.Name())
    fmt.Println(fullPath)
}

Edge cases

  • The directory may not exist.
  • Permissions may prevent reading the directory.
  • A directory can contain files, folders, links, and hidden files.
  • The order of entries should not be relied on unless you explicitly sort them.

FAQ

How do I list files in a directory in Go?

Use os.ReadDir(path), then loop over the returned entries.

How do I avoid recursion when listing directories in Go?

Do not use filepath.Walk for this. Use os.ReadDir, which reads only one directory level.

How can I tell whether an entry is a file or folder in Go?

Use entry.IsDir(). If it returns true, the entry is a directory.

How do I get the full path of a listed file?

Use filepath.Join(dir, entry.Name()).

Should I use ioutil.ReadDir or os.ReadDir?

Use os.ReadDir in modern Go code.

How do I get file size when listing a directory?

Call entry.Info() and then use info.Size().

Does os.ReadDir include subdirectories' contents?

No. It lists only the entries directly inside the given directory.

Take Quiz

Mini Project

Description

Build a small Go program that lists the contents of a chosen directory and labels each entry as either a file or a folder. This demonstrates how to read one directory level, handle errors, and work with directory entries in a practical way.

Goal

Create a command-line program that prints all items in a directory and clearly identifies whether each item is a file or directory.

Requirements

  1. Read a directory path from a variable or command-line argument.
  2. Use os.ReadDir to load the directory contents.
  3. Print each entry name with either FILE or DIR.
  4. Handle errors if the directory cannot be read.
Take Quiz

Keep learning

Related questions

View more