Change: see fooobar.com/questions/1233292 / ... for a more current answer.
Original answer:
https://golang.org/pkg/text/template/#hdr-Variables :
The scope of the variable extends to the "end" action of the control structure ("if", "with" or "range") in which it is declared, or to the end of the template if there is no such control structure.
Thus, the $prev_year you defined with {{$prev_year:=$year}} is valid only until ... the next line ( {{end}} ).
There seems to be no way around this.
The βrightβ way to do this is to remove this logic from the template and do the grouping in Go code.
Here is a working example: https://play.golang.org/p/DZoSXo9WQR
package main import ( "fmt" "os" "text/template" "time" ) type Tournament struct { Place string Date time.Time } type TournamentGroup struct { Year int Tournaments []Tournament } func groupTournamentsByYear(tournaments []Tournament) []TournamentGroup { if len(tournaments) == 0 { return nil } result := []TournamentGroup{ { Year: tournaments[0].Date.Year(), Tournaments: make([]Tournament, 0, 1), }, } i := 0 for _, tournament := range tournaments { year := tournament.Date.Year() if result[i].Year == year { // Add to existing group result[i].Tournaments = append(result[i].Tournaments, tournament) } else { // New group result = append(result, TournamentGroup{ Year: year, Tournaments: []Tournament{ tournament, }, }) i++ } } return result } func main() { tournaments := []Tournament{ // for clarity - date is sorted, we don't need sort it again {"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)}, {"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)}, {"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)}, } t, err := template.New("").Parse(' {{$prev_year:=0}} {{range .}} Actions in year {{.Year}}: {{range .Tournaments}} {{.Place}}, {{.Date}} {{end}} {{end}} ') if err != nil { panic(err) } err = t.Execute(os.Stdout, groupTournamentsByYear(tournaments)) if err != nil { fmt.Println("executing template:", err) } }