skip to content

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%

answer

  1. the placeholder that is not an error
  2. structs shout, maps whisper
  3. one option string, three settings
  4. turn it on in the test, decide it in production
  5. the payload shape is the real bug

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.

solid answer

~50 s

`<no value>` is what `text/template` prints when an action evaluates to an invalid or nil value — in practice, indexing a map with a key that is not there. The default option is `missingkey=default` (equivalently `invalid`): execution continues and writes `<no value>`. That is why a payload of `map[string]string` turns a renamed key into wrong output rather than a failed render, while the same typo against a **struct** field is always an execution error (`can't evaluate field ...`), because struct fields are resolved by reflection on a known type. The fix is `t.Option("missingkey=error")`, which makes `Execute` return an error for the missing key; `missingkey=zero` is the middle setting, substituting the map's element zero value. In a renderer I would run every template against every payload shape in a test with `missingkey=error` on, and prefer typed structs over `map[string]any` so the failure is loud by construction.

code

go · 13 lines
go
type Payload struct {
	Subject string
	Vars    map[string]string
}

const src = "Hi {{.Vars.name}}, your plan is {{.Vars.plan}}\n"

t, err := template.New("welcome").Option("missingkey=error").Parse(src)
if err != nil {
	return err
}
// Execute now returns an error when Vars has no "plan" key
return t.Execute(w, Payload{Subject: "Welcome", Vars: vars})

go deeper

for a junior

Recognise <no value> as output, not a crash: something the template asked for was not in the data. Know that it typically means a map key was missing rather than a template typo.

for a middle

Explain the asymmetry between struct fields and map keys and name the three missingkey settings, including that the option is set on the template and affects map lookups only.

for a senior

Walk the incident: find the placeholder in the output, reproduce with a template-times-payload test under missingkey=error, then decide the production posture and move payloads to typed structs to remove the class.

for a principal

Own the tradeoff between a failed send and a wrong message, and set it as policy per surface. Argue for typed payloads and a rendering contract test so correctness does not depend on every author remembering an option string.

## What actually prints `<no value>` When a template action evaluates to a value that reflection reports as invalid — most commonly a map index whose key is absent, or an untyped nil — the default behaviour of `text/template` is to write the literal text `<no value>` and carry on. It is not an error, not a warning, and nothing in the output distinguishes it from text you meant to print. The important asymmetry is what dot is: - **Dot is a struct.** `{{.FirstName}}` is resolved against a known type. If the field does not exist, or is unexported, `Execute` returns an error like `can't evaluate field FirstName in type mail.Payload`, and nothing further is rendered. Loud. - **Dot is a map.** `{{.FirstName}}` becomes a map index. A missing key is a perfectly legal lookup that yields nothing, and the default option prints `<no value>`. Silent. So the same field rename produces a hard failure or a wrong document depending purely on whether the payload is typed. That is the fact to lead with in an interview, because it also dictates the fix. ## The missingkey option `(*Template).Option` takes option strings and returns the template, so it chains: ```go t, err := template.New("welcome").Option("missingkey=error").Parse(src) ``` The three settings: - `missingkey=default` (alias `missingkey=invalid`) — the default. Continue and print `<no value>`. - `missingkey=zero` — substitute the zero value of the map's element type. An absent string key renders as empty, an absent int as `0`. Quieter than the default and still not an error. - `missingkey=error` — `Execute` stops and returns an error naming the missing key. It applies to **map index operations only**. It does not make struct field misses stricter (they are already errors) and it does not validate anything at parse time. ## Diagnosing the incident Suppose a notification worker sent a batch of emails reading "Hi <no value>, your plan renews on <no value>". The template is fine; the payload changed. A field in the variables map was renamed upstream — `first_name` became `given_name` — and nothing failed. The diagnosis is mechanical once you know the rule: grep the rendered output for `<no value>`, then check whether the actions that produced it index a map. From there: 1. **Make it fail in tests.** Build a table of (template, representative payload) pairs and execute every one with `Option("missingkey=error")`. Any key a template needs and a payload lacks now returns an error, and the test names both sides. This is cheap, runs in milliseconds, and catches the renamed-key class completely for the payloads you enumerated. 2. **Decide the production posture.** `missingkey=error` in production converts a cosmetically wrong message into a failed send. For a notification worker that is usually right — a retry queue plus an alert beats a customer reading `<no value>`. For something where partial output is better than none, `missingkey=zero` at least removes the alarming placeholder. The decision is per system, and it should be a deliberate one rather than the default nobody chose. 3. **Remove the class.** Replace `map[string]any` payloads with a struct per template. Then a renamed field is a compile error in the Go code that builds the payload, or at worst a loud `Execute` error — never a silently wrong document. Maps are convenient exactly where they are dangerous: when the shape is not written down. 4. **Add an output guard.** A rendered-body check for `<no value>` before sending is a crude but effective backstop for the templates you cannot convert yet. ## Related traps in the same family - A nil pointer field prints `<nil>` rather than `<no value>` — different text, different cause, and worth recognising in output. - A key present with an empty value renders as empty, indistinguishable in the output from a key you never referenced. `missingkey=error` does not help there; only a content assertion does. - Chained access `{{.Vars.plan.name}}` where `plan` is missing fails differently depending on the type at each hop; test the whole chain, not the first field. ## What to say in an interview The strongest answer names the mechanism (`<no value>` is the default map-miss behaviour), the asymmetry (structs error, maps do not), the switch (`Option("missingkey=error")` and its three settings), and the systemic fix (typed payloads plus a template-times-payload test), and closes with the judgment call about whether a missing key should stop a send in production.

  • Does missingkey=error also catch a misspelled struct field?
    No, and it does not need to. A struct field that does not exist is already an execution error — `Execute` returns something like `can't evaluate field X in type Y` — because the field is resolved by reflection against a concrete type. The option governs map index operations only, which is exactly the case that is silent by default. It is one more reason to prefer struct payloads.
  • What does missingkey=zero do differently, and when would you pick it?
    It substitutes the zero value of the map's element type for an absent key: empty string, 0, nil. Execution continues, so output stays complete but the placeholder text disappears. Pick it when a partial document is genuinely better than none and an empty field is visually acceptable — a dashboard render, say. For anything a customer reads as a statement of fact, prefer the error and a retry.
  • How would you prove a fix like this actually covers your templates?
    Write a table test over (template, representative payload) pairs and execute each with `Option("missingkey=error")` — the test names the template and the missing key on failure. Add each incident's real payload to the table. It only covers shapes you enumerated, so pair it with typed payload structs, which move the same class of bug to compile time.

saying these in an interview costs you the question

  • Thinks <no value> is a template syntax error
  • Believes missing fields always fail the render
  • Expects missingkey to be checked at Parse time
  • Says missingkey=error fixes missing struct fields
  • Ships untyped map payloads and relies on review to catch renames