skip to content

Template Rendering

text/template and html/template share a syntax of actions and pipelines, but only the html one escapes by output context, and that difference decides whether a page is injectable.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

19

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
open as a page

In Go's html/template, what do the {{define}} and {{template}} actions do, and when do you need ExecuteTemplate rather than Execute?

level: juniorimportance: must knowfreq 46%

basics

~20 s

{{define "name"}}...{{end}} names a template inside the parsed text; {{template "name" .}} renders it in place with the data you give it. Execute runs the receiver template; ExecuteTemplate picks one associated template from the set by name.

open as a page

What does html/template escape automatically that text/template leaves untouched?

level: juniorimportance: must knowfreq 60%

basics

~20 s

html/template escapes every value it substitutes, choosing the escaping from where the value lands in the page: HTML text, an attribute, a script block or a URL. text/template escapes nothing and writes the value as-is.

open as a page

Why must a Go template's Funcs call come before Parse when the template calls a custom function?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Parsing resolves every function name against the template's func map. Register the helper after Parse and the parser has never heard of it, so Parse fails with a "function not defined" error instead of failing later at Execute.

open as a page

Inside {{range .Items}} in a Go template, what happens to dot, and how do you reach the top-level value?

level: middleimportance: must knowfreq 60%

basics

~20 s

Inside a range body dot is rebound to the current element, so outer fields are unreachable through it. Use $, which stays bound to the value passed to Execute, or capture what you need in a variable before the loop.

open as a page

How does html/template choose an escaper for {{.Name}} from where it appears in the page?

level: middleimportance: must knowfreq 52%

basics

~20 s

html/template parses the markup around each action, tracks the output state there, and rewrites the tree to call a matching escaper: entities in text, a JavaScript literal in a script block, percent-encoding plus a scheme filter in a URL.

open as a page

What counts as false for {{if}} in a Go template, and how do you compare two values there?

level: middleimportance: should knowfreq 45%

basics

~20 s

Go templates test emptiness, not a boolean: false, 0, a nil pointer or interface, and any zero-length array, slice, map or string are empty. Comparison uses functions such as eq, ne, lt and gt, never operators.

open as a page

In Go templates, how does the {{block}} action differ from {{define}}, and how does a page override a layout's block?

level: middleimportance: should knowfreq 37%

basics

~20 s

{{block "name" .}}body{{end}} defines a template called "name" and executes it at that spot, so the body is a default. A page overrides it with its own {{define "name"}} parsed into the same set; the later non-empty definition wins.

open as a page

What results may a Go template.FuncMap helper return, and what happens when its error is non-nil?

level: middleimportance: should knowfreq 45%

basics

~20 s

A template function must return exactly one value, or two values whose second is of type error. Any other shape panics when you register it. If that error is non-nil during rendering, Execute stops there and returns the error to you, wrapped with the template name and position.

open as a page

A Go template rendered <no value> where a name should be. What produced it, and how would you make it an error?

level: seniorimportance: should knowfreq 40%

basics

~20 s

<no value> is what a missing map key prints when dot is a map; by default that is not an error. Set Option("missingkey=error") so Execute fails instead. A missing struct field is always an execution error anyway.

open as a page

Pages parsed with Go's template.ParseFS render blank or show another page's content — which naming rules explain it, and how do you confirm?

level: seniorimportance: should knowfreq 31%

basics

~20 s

ParseFS names each template by the file's base name, so same-named files in different directories collide and the last parsed wins. A file of only {{define}} blocks has an empty body, so executing its name writes nothing.

open as a page

A handler wraps a user's comment in template.HTML to keep its formatting — what breaks?

level: seniorimportance: should knowfreq 45%

basics

~20 s

template.HTML is a named string type meaning "this is already safe markup", so html/template emits it verbatim. Converting attacker-controlled text to it turns escaping off for exactly the field an attacker controls, which is a stored injection.

open as a page

A template helper errors mid-Execute after bytes reached the ResponseWriter — how do you avoid half-rendered pages?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Execute writes as it goes, so a helper that fails halfway leaves valid-looking output already sent under a 200 status. Render into a bytes.Buffer first, check the error from Execute, and only copy the buffer to the response writer once it returned nil.

open as a page

How do you decide whether a Go service's templates ship inside the binary via embed and ParseFS or load from disk at runtime?

level: principalimportance: should knowfreq 27%

basics

~20 s

Default to embedding: //go:embed with template.ParseFS makes one self-contained artefact and turns a missing file into a build failure. Choose runtime loading only when someone outside the release process must change markup without a redeploy, and price that in.

open as a page

What policy do you set for template.HTML, template.JS and template.URL across a team's codebase?

level: principalimportance: should knowfreq 38%

basics

~20 s

Treat the conversions as the only escaping off-switch and gate them like unsafe code: default deny, one small reviewed package holding every sanctioned conversion, a mechanical check that fails the build elsewhere, and a named owner for each exception.

open as a page

Which Go template builtins look up a dynamic map key, format a value, and invoke a function value?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

index looks up a map, slice or array by a key or position computed at render time; printf is the template name for fmt.Sprintf; call invokes a function value held in the data with the remaining arguments. All three are always available, with no registration.

open as a page

Why can a Go template's Execute leave half a document in the writer, and how do you avoid shipping that?

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

Execute streams to the io.Writer as it walks the template, so bytes written before a failing action stay written. Render into a bytes.Buffer first and forward it only when Execute returns nil.

open as a page

In html/template, when do you need Template.Clone before adding variant definitions to a shared parsed set?

level: seniorimportance: nice to knowfreq 19%

basics

~20 s

Clone whenever several variants must define the same name on top of one common base. It duplicates the whole namespace of associated templates so each variant is independent, and in html/template it must happen before the set has executed.

open as a page

Why does an html/template escaping error surface from Execute rather than from Parse?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Parse only builds the parse tree. html/template's escaping pass is a second walk that needs the whole template set, so it runs lazily on the first Execute or ExecuteTemplate — which means a template nobody renders is never checked.

open as a page