What does a //go:embed directive require of the variable it precedes in a Go file?
answer
- the line directly under it matters
- package scope only
- three legal types, no more
- one import you never reference
- one file for a string, a tree for an FS
basics
~20 sThe directive must sit immediately above a package-level variable whose type is a string type, a byte slice, or embed.FS, and the file must import the embed package (a blank import when the type is string or []byte). Patterns are relative to the package directory.
solid answer
~50 s`//go:embed` only works on a **package-level** variable declared immediately below it — only blank lines and `//` comments may sit between the directive and the declaration. The variable's type must be a string type, a slice of bytes, or `embed.FS`. The file has to import `"embed"`; when the variable is a `string` or `[]byte` the package name is never referenced, so you write a blank import `_ "embed"`. String and `[]byte` can hold exactly one file; anything matching multiple files, or a directory, needs `embed.FS`. Patterns are interpreted relative to the directory of the source file and may not use `..`, absolute paths, or otherwise reach outside the package directory. Inside the resulting `embed.FS`, paths keep the prefix as written in the pattern — `templates/index.html`, not `index.html` — and `fs.Sub` strips that prefix if you want a rooted filesystem.
code
go · 7 linesimport "embed"
//go:embed templates
var templates embed.FS
//go:embed migrations/*.sql
var migrations embed.FSgo deeper
Recall the shape: directive directly above a package-level var, the embed import present, and embed.FS when you want a whole directory rather than one file.
Explain each rule and why it exists — the blank import for string and []byte, package scope only, patterns relative to the package directory with no .. escapes, and one file per string.
Talk about the operational consequences: assets are frozen at build time so the binary cannot drift from its templates, binary size grows with the data, and configuration that operators must change does not belong inside it.
Own the tradeoff of shipping assets inside the artefact versus alongside it — reproducibility and one-file deploys against rebuild-to-change and image size, and which classes of file your organisation should never embed.
## The point of the feature `//go:embed` lets the compiler copy files from your source tree into the binary, so that a program ships as one self-contained executable carrying its own HTML templates, `.sql` migration files, a built web bundle, a default configuration, or a licence text. Before it existed, teams either shipped a directory next to the binary or ran a third-party step to convert files into a giant Go string literal. Embedding is a language-level feature of the toolchain, and its rules are checked at compile time. ## The declaration rules, precisely ```go import "embed" //go:embed templates var templates embed.FS ``` 1. **The directive must immediately precede a declaration of a single variable.** Only blank lines and `//`-style line comments may come between them. A directive followed by a function, a type, a `const`, or a multi-variable declaration is a compile error. 2. **The variable must be at package scope.** You cannot embed into a local variable inside a function. This is a compile error, and it is the rule people trip over most often when they try to "load" a file inside a handler. 3. **The type must be a string type, a slice of a byte type, or `embed.FS`** (or an alias of it). Nothing else — not a `map`, not a custom struct. 4. **The file must import `embed`.** If the variable is `embed.FS`, you reference the package name so an ordinary import is natural. If the variable is a `string` or `[]byte`, nothing in the file mentions the package, and an unused import is a compile error — hence the blank import: ```go import _ "embed" //go:embed version.txt var version string ``` 5. **String and byte-slice variables take exactly one file.** A pattern that matches a directory or more than one file must be embedded into an `embed.FS`. 6. **The directive can carry several patterns**, space separated, and multiple directives can stack above one variable; all of their matches land in the same `embed.FS`. ## Pattern rules Patterns use `path.Match` syntax and are interpreted **relative to the directory containing the source file**. They may not contain `.` or `..` path elements, may not be absolute, and may not match files outside the package directory — you cannot embed a file from a sibling package or from anywhere up the tree. If a pattern matches nothing at all, the build fails with `pattern <p>: no matching files found`, which is a genuine safety feature: a typo in a whole pattern is caught by the compiler. A pattern naming a directory embeds the entire subtree beneath it. ## What you get at runtime `embed.FS` is a read-only filesystem value. It implements `fs.FS`, and also `fs.ReadDirFS` and `fs.ReadFileFS`, so it has three methods of its own: - `Open(name string) (fs.File, error)` - `ReadFile(name string) ([]byte, error)` - `ReadDir(name string) ([]fs.DirEntry, error)` Because it satisfies `fs.FS`, it plugs directly into the standard library: `template.ParseFS(templates, "templates/*.html")` parses embedded templates, `http.FS(templates)` adapts it for `http.FileServer`, and `fs.WalkDir(templates, ".", ...)` walks it. The names inside the FS are exactly the paths as matched, using forward slashes on every operating system, and they **keep the directory prefix from the pattern**. With `//go:embed templates`, the file is `templates/index.html` inside the FS, not `index.html`. If you want the directory to be the root — for serving over HTTP, say — wrap it: ```go sub, err := fs.Sub(templates, "templates") ``` ## Consequences worth naming in an interview - **The data is read-only and fixed at build time.** Changing a template means rebuilding. That is usually the point: the binary and its assets cannot drift apart in production. It is also the cost, and it is why people keep operator-editable configuration outside the binary. - **Binary size grows by roughly the size of the embedded bytes.** Embedding a large media directory is a real decision, not a free one. - **Empty directories are not represented.** The FS holds files; a directory with no files under it simply does not appear. - **`embed.FS` is a value, safe to read concurrently** from many goroutines, which is why it is comfortable as a package-level variable in a server. ## The usual first mistake Someone writes `//go:embed config.yaml` above `var cfg string` inside a function, or forgets the import, and gets a compile error they read as a bug in the toolchain. Both messages are accurate: package scope and the `embed` import are hard requirements, and remembering the blank import for the string and byte-slice cases is the detail that separates someone who has used the feature from someone who has read about it.
- Why is the import written as _ "embed" when the variable is a string?Because the directive needs the `embed` package linked in, but nothing in the file refers to the identifier `embed`, and Go rejects an unused import as a compile error. The blank identifier import satisfies both. When the variable is declared as `embed.FS`, the package name is referenced in the type, so an ordinary import is correct.
- Can you put //go:embed on a variable inside a function?No. Embedding is only permitted on package-scope variables, and a directive above a local declaration is a compile error. If a function needs the data, embed it into a package-level variable and read it from there — `embed.FS` is read-only and safe for concurrent use, so a package-level value is the normal shape.
- What are the file names inside the embed.FS, and how do you strip the directory prefix?They are the matched paths exactly as written in the pattern, always with forward slashes — `//go:embed templates` gives you `templates/index.html`. To make the directory the root, wrap it with `fs.Sub(templates, "templates")`, which returns an `fs.FS` rooted there. That is the usual step before handing it to `http.FS` for a file server.
saying these in an interview costs you the question
- Puts //go:embed above a variable declared inside a function
- Omits the embed import and blames the compiler error
- Uses a plain import for a string embed, hitting unused-import
- Tries to embed ../shared/config.yaml from a sibling directory
- Expects to write to embed.FS at runtime
- Assumes the file is opened from disk at startup rather than compiled in