Skip to Content
Course content

189: filepath.Walk and filepath.WalkDir

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once spent an entire afternoon debugging a "slow" file indexing tool I wrote for a client. It was scanning a massive network-attached storage (NAS) drive containing millions of small files. I was using filepath.Walk, and the program felt like it was crawling through molasses. I initially blamed the network latency, but after some profiling, I realized the bottleneck was the Go code itself. Every single file the walker encountered triggered a separate os.Lstat system call to gather file information. On a local SSD, you might not notice it, but across a network or on a massive directory tree, those millions of extra syscalls add up to a staggering amount of wasted time.

The overhead of filepath.Walk

For a long time, filepath.Walk was the standard way to traverse a directory tree in Go. It takes a root path and a walk function with the signature func(path string, info os.FileInfo, err error) error. The problem is that os.FileInfo is comprehensive; it provides size, mode, and modification times. To give you that data, Go has to ask the operating system for the full status of every single file it encounters.

// The old way: filepath.Walk
err := filepath.Walk("my_project", func(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    if !info.IsDir() {
        fmt.Printf("Found file: %s\n", path)
    }
    return nil
})

If you only care about whether a path is a directory or what its name is, filepath.Walk is overkill. You're paying for a full metadata fetch when you only needed a name.

Switching to WalkDir for performance

Introduced in Go 1.16, filepath.WalkDir is the modern replacement. The key difference is in the signature: it uses os.DirEntry instead of os.FileInfo. A DirEntry is much lighter. In many operating systems, the directory reading process itself returns the file type, meaning Go can tell if something is a directory without making that extra, expensive system call.

// The modern way: filepath.WalkDir
err := filepath.WalkDir("my_project", func(path string, d os.DirEntry, err error) error {
    if err != nil {
        return err
    }
    // d.IsDir() is usually "free" because it's provided by the directory read
    if d.IsDir() {
        return nil 
    }
    fmt.Printf("Processing file: %s\n", path)
    return nil
})

I always recommend using WalkDir by default now. If you eventually realize you do need the full file size or modification time, you can still get it by calling d.Info(). This lets you be surgical—only paying the performance penalty for the specific files you actually need to inspect deeply.

Controlling the traversal

Whether you use Walk or WalkDir, you have a powerful lever to control the search: filepath.SkipDir. If your walk function returns this special error, Go will stop processing the current directory. If the current directory is the root you started with, the walk ends. If it's a subdirectory, Go just skips that entire branch and moves to the next sibling.

This is essential for ignoring heavy folders like .git or node_modules. Without this, your tool will waste minutes indexing internal version control history that you likely don't care about.

err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
    if err != nil {
        return err
    }
    if d.IsDir() && d.Name() == ".git" {
        return filepath.SkipDir // Don't go inside the .git folder
    }
    return nil
})



📋 Practical Task

Build a Project-Wide Go File Line Counter

Write a program that traverses the current directory and all subdirectories to count the total number of lines across all .go files. To make it professional, ensure the program ignores any directory named vendor or .git to avoid counting third-party dependencies or metadata.

Requirements:

  • Use filepath.WalkDir for the traversal.
  • Use filepath.SkipDir to ignore .git and vendor folders.
  • Only process files that end with the .go extension.
  • Open each file and count the newline characters (or use a bufio.Scanner).
  • Print the total line count to the console when finished.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.