skip to content

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%

answer

  1. one action doing two jobs
  2. define plus template, written in one place
  3. the body between them is a default
  4. the last non-empty definition wins

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.

solid answer

~50 s

`{{block "name" pipeline}}...{{end}}` is exactly `{{define "name"}}...{{end}}` followed by `{{template "name" pipeline}}`, written in one place: it declares the template *and* calls it there, so the body between the delimiters is a default that renders when nobody supplies another. A page overrides it by contributing its own `{{define "name"}}` to the same set — parse the layout together with that one page, and the later non-empty definition replaces the earlier one. Two rules bite. A definition whose body is only whitespace and comments is treated as empty and does *not* replace an existing body, so a blank override is silently ignored. And you then execute the *layout* by name, not the page file, because the layout holds the surrounding document. In `html/template` you cannot parse new definitions into a set once it has been executed.

code

go · 7 lines
go
const base = `<html><body>{{block "content" .}}<p>nothing yet</p>{{end}}</body></html>`
const page = `{{define "content"}}<h1>{{.Title}}</h1>{{end}}`

t := template.Must(template.New("base").Parse(base))
t = template.Must(t.Parse(page)) // replaces "content", leaves "base" alone
_ = t.Execute(os.Stdout, struct{ Title string }{"Hi"})
// prints: <html><body><h1>Hi</h1></body></html>

go deeper

for a junior

Recall the shape: the layout carries {{block "content" .}}default{{end}}, a page carries {{define "content"}}...{{end}}, you parse both together and render the layout.

for a middle

Explain block as sugar for define plus template, say which definition wins when a name is declared twice, and note that a whitespace-only body never replaces an existing one.

for a senior

Show how per-page overrides are kept from colliding when every page defines the same name, and how escaping and the parse-after-execute rule of html/template fix when the set can still change.

for a principal

Decide whether the codebase uses block-style inheritance at all. The choice sets how many template sets the process holds, how a new page is reviewed, and how much indirection a reader must follow to find rendered markup.

## What block actually is `{{block "content" .}}<p>nothing yet</p>{{end}}` is pure sugar. The parser expands it into two things: a template definition named `content` whose body is `<p>nothing yet</p>`, and an invocation of that template at the point where the block was written, with `.` as its dot. Anything you can do with block you can do with a define somewhere plus a `{{template}}` call at the right place; block just keeps the default body next to the hole it fills. That single fact answers most questions about it. Does block render? Yes — it contains an invocation. Can the same name be called again later? Yes, because the name is now an ordinary entry in the set: `{{template "content" .Other}}` further down runs the same body against different data. Is the default mandatory? No — an empty block body is a legal, empty default. ## How overriding resolves A parsed template value is a namespace mapping names to bodies. Associating a name with a new, non-empty body replaces whatever that name held. So if the layout's block defined `content` and a page file then contributes `{{define "content"}}<h1>{{.Title}}</h1>{{end}}`, the page's body is what renders. Order is the order the text is parsed: the arguments to `ParseFiles` in the order given, the patterns to `ParseFS` in the order given and each pattern's matches in glob order, or successive `Parse` calls. There is one deliberate exception, and it is the one that surprises people. A definition whose body contains only whitespace and comments is considered *empty*, and an empty definition never replaces a body that already exists. That rule exists so that parsing an extra file into a set cannot silently wipe out the template the receiver is named for — the file's own top-level body is usually empty, and without the rule every additional `Parse` would erase the first one. The side effect is that `{{define "content"}}{{end}}` as an override does nothing at all: the layout's default keeps rendering. If a page really should show nothing there, make the layout's default empty and have pages add content, rather than trying to override with a blank body. Within a *single* parse, two non-empty definitions of the same name are an error rather than a replacement — that is a duplicate in one text. Across separate parses it is a replacement, which is what makes layout override work. ## Executing the right template After parsing a layout and a page together you execute the layout: `t.ExecuteTemplate(w, "base.html", data)`. Executing the page by its own name renders nothing useful, because the page file's own body — everything outside its define — is empty. This is the single most common way a block-based layout produces a blank page. ## html/template's extra constraint `html/template` runs its contextual auto-escaper the first time a template is executed, rewriting the parse trees in place with the escaping appropriate to each position. Once that has happened the set is fixed: parsing further definitions into it returns an error rather than quietly producing text that was never escaped for its context. Practically, this means all definition-building happens during initialisation. You cannot serve a request, then decide to add this page's `content` to the shared set. ## The pattern that scales Because every page wants to define the *same* name, `content`, they cannot all live in one set: the last one parsed would win and every page would render identically. The two workable shapes are one set per page (parse the layout plus that page, or clone a pre-parsed layout and parse the page into the clone), or unique names per page with the layout invoking a name chosen at render time. The first is far more common because it keeps the templates themselves free of indirection. ## What to say in an interview Block is define plus template in one place; the body is a default; a later non-empty definition of that name replaces it; whitespace-only definitions never replace anything; execute the layout, not the page; and in html/template all of this must be settled before the first execution.

  • What happens if a page overrides with {{define "content"}}{{end}} — an empty body?
    Nothing changes. A definition whose body is only whitespace and comments counts as empty and is not allowed to replace an existing body, so the layout's default keeps rendering. The rule exists so that parsing another file into a set cannot silently erase a template. If a page really needs that section blank, make the layout's default empty and let pages add content instead.
  • Why do you execute the layout template rather than the page's template?
    Because the layout is the one whose body holds the surrounding document and the block or {{template}} call. A page file containing only a define has an empty body of its own, so executing it by its file name writes nothing. Call ExecuteTemplate with the layout's name, and the page's definition is pulled in where the block sits.
  • Can the same block name be rendered twice with different data?
    Yes. Block defines and calls once, but the name is now an ordinary entry in the set, so a later `{{template "content" .Other}}` executes the same body with a different dot. Nothing about block makes the definition single-use — it is only shorthand for a define plus one invocation.

saying these in an interview costs you the question

  • Thinks {{block}} only declares and never renders
  • Expects the first definition of a name to win
  • Overrides with an empty body and expects blank output
  • Executes the page file instead of the layout
  • Parses new definitions into an html/template set already executed