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.
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
os.ReadDir(".")asks the operating system to read the current directory.- Go returns two values:
entries: a list of items in that directoryerr: an error if something went wrong
- The
if err != nilcheck stops the program if the directory cannot be read. - The
for _, entry := range entriesloop processes each item one by one. entry.Name()prints the item's name.entry.IsDir()printstrueif the item is a directory, otherwisefalse.
Example output
main.go false
assets true
README.md false
This tells you:
main.gois a fileassetsis a directoryREADME.mdis 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.
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
| Task | Best choice | Why |
|---|---|---|
| List one directory level | os.ReadDir | Simple and direct |
| Walk all nested directories | filepath.Walk or filepath.WalkDir | Designed for recursion |
| Check whether a path exists and get metadata | os.Stat | Returns file info for one path |
| Open and read file contents | os.Open | Used for reading file data |
os.ReadDir vs filepath.Walk
| Feature | os.ReadDir | filepath.Walk |
|---|---|---|
| Reads one directory only | Yes | No |
| Recursive by default | No | Yes |
| Good for simple listing | Yes | Less ideal |
| Gives direct entries | Yes | Callback-based traversal |
os.ReadDir vs older directory APIs
Older Go code may use functions like ioutil.ReadDir. In modern Go, prefer os.ReadDir.
| API | Status | Notes |
|---|---|---|
os.ReadDir | Preferred | Modern standard choice |
ioutil.ReadDir | Older style | Superseded in newer Go versions |
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.Walkorfilepath.WalkDirfor 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.
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
- Read a directory path from a variable or command-line argument.
- Use
os.ReadDirto load the directory contents. - Print each entry name with either
FILEorDIR. - Handle errors if the directory cannot be read.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.