When should you use Go's path package instead of path/filepath?
answer
- two packages, nearly the same names
- one of them never sees a backslash
- which one would a URL want?
- no VolumeName in the slash-only package
basics
~20 sUse path for strings that are slash-separated on every platform: URL paths, archive entry names, slash-separated keys. Use path/filepath only for names you hand to the operating system, because it follows the local separator and understands Windows volume names.
solid answer
~50 sThe two packages export nearly the same names — `Join`, `Clean`, `Base`, `Dir`, `Ext`, `Split`, `Match` — but they operate on different kinds of string. `path` treats `/` as the one and only separator, on every platform; it has no notion of a drive letter or a backslash. `path/filepath` uses `filepath.Separator` for the platform the binary was compiled for, understands Windows volume names, and adds `Abs`, `Rel`, `ToSlash`, `FromSlash`, `VolumeName` and `EvalSymlinks`, which `path` has no reason to have. So a URL path (`url.URL.Path`), an archive entry name, or any identifier that must read identically on every machine goes through `path`; anything you are about to give to `os.Open` goes through `filepath`. On Linux the two produce the same output for ordinary input, which is exactly why the wrong choice survives review and only fails when someone builds for Windows.
code
go · 6 linesu, err := url.Parse("https://example.com/pkg/")
if err != nil {
return err
}
u.Path = path.Join(u.Path, "docs", "index.html")
fmt.Println(u.String()) // https://example.com/pkg/docs/index.htmlgo deeper
Know that two packages share these function names and that path/filepath is the one for real files on disk. Being able to say which one you would use before calling os.Open is enough at this level.
Explain the actual difference: path is slash-only and identical on every platform, while path/filepath follows the compiled-for separator, handles volume names, and adds the ToSlash and FromSlash conversions.
Show the boundary you draw in a codebase — where an operating-system path becomes a slash-separated identifier and back — and how you keep that decision from being re-made ad hoc inside every function.
Argue the policy: which representation stored and transmitted names use, who owns the conversion, and what it costs to discover the wrong choice only when the first Windows user reports an unusable name.
## Two packages, one vocabulary Go ships two path packages, and beginners reasonably assume one supersedes the other. Neither does. - **`path`** manipulates **slash-separated** names. A slash is the only separator it recognises, on every operating system, forever. A backslash is just an ordinary character to it, and a leading `C:` is just an element name. - **`path/filepath`** manipulates **operating-system** names. It uses `filepath.Separator` — the separator of the platform the program was compiled for — and on Windows it knows about drive letters and UNC prefixes. They deliberately share function names (`Join`, `Clean`, `Base`, `Dir`, `Ext`, `Split`, `Match`) so that porting code between the two is mechanical. That symmetry is also the trap: `path.Join` and `filepath.Join` compile equally well at any call site, and on Linux they return the same string. ## Deciding which one a string wants The question is never "which package do I like" but **"where is this string going?"** **`path` — the name is slash-separated by definition:** - the `Path` field of a `net/url.URL`, and anything you route on; - entry names inside an archive, which are slash-separated in the format itself; - keys and identifiers stored in a database or sent over the wire, which must read the same whichever machine wrote them; - any hierarchical name that is not a file on this machine. **`filepath` — the name is going to the operating system:** - everything you pass to `os.Open`, `os.Stat`, `os.MkdirAll`, `os.ReadFile`; - everything you receive from the operating system and want to take apart with `Base`, `Dir` or `Ext`; - anything you print for a user who will paste it into their own shell. ## What only `filepath` has `path` has no `Abs`, no `Rel`, no `ToSlash`, no `FromSlash`, no `VolumeName`, no `EvalSymlinks`, and no `Separator` constant that could differ from `/`. Every one of those exists because an operating-system path has properties a slash-separated name does not: a working directory it can be resolved against, a platform separator that may not be a slash, a volume prefix, and a real filesystem behind it that can contain symbolic links. `filepath.VolumeName` is the sharpest illustration. On Windows, `filepath.VolumeName` reports the leading drive or UNC prefix of a path; on a Unix build it always returns the empty string, because there is nothing there to report. There is no sensible `path.VolumeName`, because a slash-separated name has no volume. ## Crossing between the two worlds When a slash-separated name has to become a local file name, or the reverse, the conversion is explicit and belongs at the boundary: - **`filepath.ToSlash`** turns an operating-system path into a slash-separated one. On Unix it returns the string unchanged, because the separator is already a slash. - **`filepath.FromSlash`** turns a slash-separated name into an operating-system one. - Recent Go also offers **`filepath.Localize`**, which converts a slash-separated name into an operating-system path and reports an error when the name cannot be represented as a local one on this platform. Doing the conversion by hand with a string replacement is wrong in both directions: on Unix a backslash is a perfectly legal character inside a file name, so blindly replacing backslashes with slashes corrupts real names. ## Why the mistake survives review On Linux and macOS, `filepath.Separator` is `/`, so `filepath.Join` and `path.Join` agree on ordinary input, `filepath.Clean` and `path.Clean` agree, and `filepath.ToSlash` is the identity function. A test suite that only runs on Linux cannot tell the two apart. The bug is latent until the day a Windows build writes a name into shared data — and then it is not a crash but a wrong string, which is a slower and more embarrassing failure. The defensible discipline is therefore not "remember to use the right one" but **"know which representation each layer speaks"**: operating-system paths at the edges where files are opened, slash-separated names everywhere they are stored, transmitted or compared, and one named conversion function where the two meet.
- Both packages export Clean. How do path.Clean and filepath.Clean differ on Windows?`path.Clean` treats a backslash as an ordinary character, so only slashes separate elements and a leading drive letter is just text. `filepath.Clean` on a Windows build replaces forward slashes with the platform separator and understands a volume prefix. On Linux the two agree for ordinary input, which is why the difference hides from a Linux-only test suite.
- Which functions exist in path/filepath but have no counterpart in path?`Abs`, `Rel`, `ToSlash`, `FromSlash`, `VolumeName` and `EvalSymlinks`, plus the `Separator` and `ListSeparator` constants. Each of them exists because an operating-system path has something a slash-separated name lacks: a working directory to resolve against, a platform separator, a volume prefix, or a real filesystem behind it.
- You receive a slash-separated name and must open it as a local file. How do you convert?`filepath.FromSlash` turns the slashes into the platform separator, and you then join the result under your base directory with `filepath.Join`. Recent Go also provides `filepath.Localize`, which does the conversion and returns an error when the name cannot be represented as a local operating-system path at all.
saying these in an interview costs you the question
- Says path and path/filepath are interchangeable
- Uses filepath.Join to assemble a URL path
- Thinks path is the old, deprecated version of filepath
- Expects path.Clean to understand a Windows drive letter
- Assumes slashes are fine because CI only runs Linux