skip to content

In a Go text/template, what does {{.Name}} resolve against, and how do Parse and Execute fit together?

level: juniorimportance: must knowfreq 72%

answer

  1. one cursor into your data
  2. two phases, two kinds of error
  3. text compiled once, data applied many times
  4. field, then map key, then method
  5. the compiler never reads the template

basics

~20 s

Dot is the current data value, so {{.Name}} reads a Name field, map key or method from it. Parse compiles the template text once; Execute applies it to one data value and writes the output.

solid answer

~50 s

In Go's `text/template` (and `html/template`), the dot `.` is the value currently being rendered. `Execute(w, data)` sets dot to `data` at the top level, so `{{.Name}}` looks for an exported field `Name` on a struct, the key `"Name"` in a map, or a nullary method `Name()` — in that order of what the value supports. `{{.}}` prints the whole current value using `fmt` printing rules. The two steps are separate on purpose: `template.New("greet").Parse(src)` compiles the text and reports *syntax* errors (an unclosed `{{if}}`, a bad pipeline); `t.Execute(w, data)` walks the parsed tree against your data and reports *execution* errors (an unexported field, a nil method call). Parsing does not know your data's type, so field-name typos are never caught at parse time. Parse once at startup and reuse the `*template.Template` — it is safe to execute concurrently.

code

go · 11 lines
go
type User struct {
	Name string
	Age  int
}

t, err := template.New("greet").Parse("Hello {{.Name}}, age {{.Age}}\n")
if err != nil {
	return err // template syntax error
}
// writes: Hello Ada, age 36
return t.Execute(os.Stdout, User{Name: "Ada", Age: 36})

go deeper

for a junior

Be ready to write four lines: New, Parse, check the error, Execute into a writer. Know that dot is the value you passed to Execute and that {{.Name}} reads a field, map key or method from it.

for a middle

Explain the split cleanly: Parse reports syntax errors with no knowledge of your data, Execute reports data errors. Say why that makes field-name typos a runtime failure and why parsing belongs at startup.

for a senior

Show the operational habit: templates parsed once during startup so a syntax error fails the process rather than a request, plus a test that executes each template against a real payload. Mention that Execute is concurrency-safe after parsing.

for a principal

Frame it as where you want template failures to land — build time, boot time or request time — and what the team pays for each. Pushing every template through a startup parse and a payload test moves a whole class of defect off the critical path.

## The two packages Go ships two template engines with the same syntax: `text/template` for plain text (emails, config files, generated code) and `html/template` for HTML, which adds automatic contextual escaping on top of identical actions. Everything in this answer is about the action syntax, which both share. ## Actions and dot Template text is literal bytes plus **actions** delimited by `{{` and `}}`. The simplest action evaluates a value and writes it out. The cursor into your data is called **dot**, written `.`: - `{{.}}` writes the current value itself, formatted the way `fmt.Print` would format it. - `{{.Name}}` writes the `Name` *field* of the current value if it is a struct, the value at *key* `"Name"` if it is a map with string keys, or the result of calling the nullary *method* `Name()` if one exists. - `{{.User.Address.City}}` chains: each step is applied to the result of the previous one. Field access follows Go's export rule. `{{.name}}` on a struct with an unexported `name` field is not a silent blank — execution fails with an error saying `name` is an unexported field. Templates can only see what an outside package could see. At the start of execution, dot is exactly the second argument you handed to `Execute`. Nothing else sets it at the top level; actions like `range` and `with` rebind it inside their own bodies. ## Parse: compile once ```go t, err := template.New("greet").Parse("Hello {{.Name}}\n") ``` `template.New(name)` creates an empty template with a name; `Parse` compiles template text into it and returns the same `*template.Template` plus an error. The error covers only what is visible in the *text*: an unterminated action, `{{end}}` without a matching block, an unknown function name, a malformed pipeline. Parse has no idea what type you will render later, so `{{.Naem}}` parses perfectly. Because parsing is pure text work, do it once — at package init or when the service starts — and keep the `*template.Template` for the life of the process. Re-parsing on every render burns CPU and, worse, delays the discovery of a syntax error until the first request that touches that template. ## Execute: apply data, write output ```go err = t.Execute(os.Stdout, User{Name: "Ada"}) ``` `Execute(w io.Writer, data any) error` walks the parsed tree, resolves each action against `data`, and writes literal text and rendered values to `w` as it goes. Errors surfaced here are *data* errors: a field that does not exist on the struct you passed, an unexported field, a method that returned a non-nil error, a pipeline whose types do not fit. Two consequences follow directly from this design and they are worth saying out loud in an interview: 1. **Field-name typos are runtime failures, not compile-time ones.** The compiler never sees the template text. This is the whole reason people run every template against a representative payload in a test. 2. **Output is streamed, not buffered.** `Execute` writes as it goes, so an error partway through leaves whatever was already written sitting in the writer. `Execute` is safe to call concurrently from many goroutines on the same parsed template, as long as parsing finished first — that is what makes the parse-once pattern correct as well as fast. ## Values, not statements An action is an expression, not a statement: it evaluates a *pipeline* and either writes the result or controls the surrounding block. There are no assignments back into your data, no arbitrary Go expressions, no operators — comparisons are function calls such as `eq` and `lt`. That deliberate poverty is the point: a template can format data but cannot compute much, which keeps logic in Go code where it can be tested. ## Whitespace `{{- .Name -}}` trims all whitespace immediately before or after the action, which is how you keep indentation in the template source without leaking blank lines into the output. It is cosmetic but it comes up constantly in generated text. ## A minimal mental model Think of a parsed template as a function of one argument. `Parse` builds the function; `Execute` calls it with dot bound to your argument and a writer to print into. Everything else in the template language — `range`, `with`, `if`, pipelines, variables — is about temporarily changing what dot is, or about choosing which parts of the body run.

  • Why does a typo in {{.Naem}} not fail until Execute runs?
    Parse works on template text alone and never sees the type you will render. It checks syntax — delimiters, block nesting, known function names — and nothing about your data. Resolution of `.Naem` against a struct happens during `Execute`, so a misspelt field is a runtime error on the first render that reaches that action. That is why templates get a test that executes them against a representative payload.
  • Can the same parsed template be executed from several goroutines at once?
    Yes. Once parsing is complete, `Execute` only reads the parsed tree and writes to the writer you hand it, so a single `*template.Template` can be executed concurrently. The unsafe part is mutating it — calling `Parse`, `Funcs` or `Option` — while other goroutines execute. The standard pattern is: build and configure the template during startup, then treat it as read-only.
  • What does {{.}} print when dot is a struct value?
    It prints the struct the way `fmt.Print` would: the field values in braces, for example `{Ada 36}`. If the type has a `String() string` method, that is used instead, because template printing goes through the `fmt` machinery and honours `fmt.Stringer`. It is fine for debugging a payload, but production templates should name the fields they want.

saying these in an interview costs you the question

  • Says the compiler checks template field names
  • Thinks Parse validates the template against the data type
  • Re-parses the template text on every render
  • Expects unexported fields to be readable from a template
  • Confuses the name given to template.New with a data field