Explain io/fs and fs.FS. How do you walk a directory with filepath.WalkDir and test file-system code with testing/fstest.MapFS?
fs.FS (Go 1.16) is a minimal read-only interface: Open(name string) (fs.File, error). Optional interfaces add capabilities: ReadDirFS, ReadFileFS, StatFS, SubFS and GlobFS. The helpers fs.ReadFile, fs.WalkDir and similar use them when present. Implementations include os.DirFS, embed.FS, zip.Reader, os.Root.FS() and fstest.MapFS. Paths are always slash-separated, unrooted, with no .. (fs.ValidPath), even on Windows. filepath.WalkDir walks OS paths, and fs.WalkDir walks any fs.FS. Both are faster than the old filepath.Walk because they pass a DirEntry instead of calling Lstat on every file.
func countGo(fsys fs.FS) (int, error) {
n := 0
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err // d may be nil when the root itself fails
}
if d.IsDir() && d.Name() == "vendor" {
return fs.SkipDir // fs.SkipAll (Go 1.20) stops the whole walk
}
if !d.IsDir() && strings.HasSuffix(path, ".go") {
n++
}
return nil
})
return n, err
}
// production: countGo(os.DirFS("/src"))
func TestCountGo(t *testing.T) {
fsys := fstest.MapFS{
"main.go": {Data: []byte("package main")},
"pkg/a.go": {Data: []byte("package pkg")},
"vendor/x/b.go": {}, // parent dirs are synthesized
"README.md": {},
}
n, err := countGo(fsys)
if err != nil || n != 2 {
t.Fatalf("got %d, %v; want 2", n, err)
}
}
What the interviewer is looking for: depend on fs.FS instead of calling os directly, so the code can be tested without temp dirs. fstest.TestFS checks that your own FS implementation follows the contract. fs.FS is read-only, so writes still need os or your own interface.
More on More Standard Library Essentials
- Q540How do you run external commands safely with os/exec? Explain CommandContext, capturing stdout/stderr, exit codes, and why shell injection is not the default risk.
- Q541How do you build a TCP server with net.Listen? Handle per-connection goroutines, read/write deadlines, and graceful shutdown of the listener.
- Q543What is os.Root (Go 1.24), and how does it prevent path-traversal attacks compared to filepath.Join plus filepath.Clean?
- Q544How do you parse CLI flags with the flag package? How do subcommands work, and why is flag.Parse in init() a bug?
- Q545Compare strconv with fmt for number/string conversion. What do strconv.ParseInt's base and bitSize arguments do, and how do you check for range errors?
- Q546When would you use encoding/binary, encoding/gob or protobuf for serialization? What are the byte-order and compatibility pitfalls?