Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
189: filepath.Walk and filepath.WalkDir
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.Walkis 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.WalkDiris the modern replacement. The key difference is in the signature: it usesos.DirEntryinstead ofos.FileInfo. ADirEntryis 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
WalkDirby default now. If you eventually realize you do need the full file size or modification time, you can still get it by callingd.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
WalkorWalkDir, 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
.gitornode_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.WalkDirfor the traversal. - Use
filepath.SkipDirto ignore.gitandvendorfolders. - Only process files that end with the
.goextension. - Open each file and count the newline characters (or use a
bufio.Scanner). - Print the total line count to the console when finished.
There are no comments for now.