📐 Controlling Whitespace in Go Templates: Nested Loops 🔗

Controlling Whitespace in Go Templates: Nested LoopsControlling Whitespace in Go Templates: Nested Loops

When working with Go’s text/template package, formatting nested data structures often results in unwanted blank lines. Because Go templates preserve all whitespace—including the newlines used to format the template code itself—standard loops produce bloated output.

Here is how to solve the whitespace problem using Go’s template trim markers.

📑 Table of Contents 🔗


⚠️ The Problem: Preserved Newlines 🔗

Say we want to format this nested data structure:

var data = map[string]map[string][]string{
	"1": {
		"1.1": {"1.1.1", "1.1.2", "1.1.3"},
	},
	"2": {
		"2.1": {"2.1.1", "2.1.2", "2.1.3"},
	},
}

A natural way to write the template is to use standard {{ range }} blocks:

{{ range $name, $map := . }}
{{ $name }}:
  {{ range $name, $values := $map }}
  {{ $name }}:
    {{ range $value := $values }}
    {{ $value }}
    {{ end }}
  {{ end }}
{{ end }}

Because every newline and indentation space in the template is printed exactly as written, the output is littered with blank spaces:

1:
  
  1.1:
    
    1.1.1
    
    1.1.2
    
    1.1.3
    
  

2:
...

✂️ The Solution: The Trim Marker (-) 🔗

You can instruct the Go template engine to consume adjacent whitespace by adding a minus sign (-) inside the action tags:

To fix our nested loop, we strategically place the - marker to eat the template’s formatting newlines without removing our intended indentation:

{{- range $name, $map := . -}}
{{ $name }}:
  {{- range $name, $values := $map }}
  {{ $name }}:
    {{- range $value := $values }}
    {{ $value }}
    {{- end }}
  {{- end }}
{{ end -}}

By stripping the newlines immediately following the range declarations and the end blocks, we get clean, tightly packed output:

1:
  1.1:
    1.1.1
    1.1.2
    1.1.3
2:
  2.1:
    2.1.1
    2.1.2
    2.1.3

💻 Full Runnable Example 🔗

You can copy and run this code to test the behavior directly.

package main

import (
	"fmt"
	"os"
	"text/template"
)

var data = map[string]map[string][]string{
	"1": {
		"1.1": {"1.1.1", "1.1.2", "1.1.3"},
	},
	"2": {
		"2.1": {"2.1.1", "2.1.2", "2.1.3"},
	},
}

func main() {
	const tpl = `
{{- range $name, $map := . -}}
{{ $name }}:
  {{- range $name, $values := $map }}
  {{ $name }}:
    {{- range $value := $values }}
    {{ $value }}
    {{- end }}
  {{- end }}
{{ end -}}
`
	fmt.Println("Output:")
	fmt.Println("'''")
	
	t := template.Must(template.New("nested").Parse(tpl))
	t.Execute(os.Stdout, data)
	
	fmt.Println("'''")
}

📚 Download My Free Ebook 🔗

Get the curated offline edition of Kaue's Way of Life—a practical field guide to optimizing 🍏 Health, 💰 Wealth, ❤️ Love, and 🧘 Meaning.

Kaue's Way of Life

Kaue's Way of Life

💬 Comments & Discussion 🔗

Have a question, perspective, or suggestion? Share your thoughts below. Comments are powered by GitHub Discussions (ad-free and spam-free)—click Sign in with GitHub below to comment or react (creating a free GitHub account takes less than a minute).



Updated on Oct 11, 2026.

DISCLAIMER: The content on this site is for informational and educational purposes only and does not constitute medical, psychological, financial, investment, tax, or legal advice. The ideas, curation, and opinions presented here are my own and do not represent those of my employer. Book, lecture, and article summaries are independent educational commentary and remain the intellectual property of their respective authors. AI tools may be used to assist with editing, formatting, and synthesis; while every effort is made to ensure accuracy, errors may occur. PRIVACY: This site uses third-party services for comments (giscus / GitHub Discussions) and embedded media (YouTube), which may process visitor IP addresses and cookies in accordance with their respective privacy policies.