Inspiriert von Tom Dallings klassischem Artikel zeigt dieser Beitrag, wie man das eigentlich simple FizzBuzz-Problem in Go völlig übertrieben konstruieren („over-engineeren“) kann.
Wir beginnen mit der einfachsten Implementierung und bauen sie Schritt für Schritt um, um die Sprachfeatures von Go zu erkunden – von einfachen Bedingungen über Funktionen höherer Ordnung bis hin zur Nebenläufigkeit.
Die Ausgabe muss die klassische FizzBuzz-Folge von 1 bis 15 sein:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
Wir beginnen mit dem direktesten Ansatz. Er erledigt die Aufgabe, erfordert aber eine Verdopplung der Modulo-Logik für 3 und 5.
package main
import "fmt"
func main() {
for i := 1; i <= 15; i++ {
if i%3 == 0 && i%5 == 0 {
fmt.Println("FizzBuzz")
} else if i%3 == 0 {
fmt.Println("Fizz")
} else if i%5 == 0 {
fmt.Println("Buzz")
} else {
fmt.Println(i)
}
}
}
Um die Zahlen 3 und 5 sowie die Strings "Fizz" und "Buzz" nicht doppelt schreiben zu müssen, können wir den Ergebnis-String schrittweise aufbauen. Bleibt der String nach unseren Prüfungen leer, geben wir einfach die Zahl selbst aus.
package main
import "fmt"
func main() {
for i := 1; i <= 15; i++ {
res := ""
if i%3 == 0 {
res += "Fizz"
}
if i%5 == 0 {
res += "Buzz"
}
if res == "" {
res = fmt.Sprint(i)
}
fmt.Println(res)
}
}
Als Nächstes lagern wir die Regeln vollständig in eine Datenstruktur aus.
Hinweis: Im Originalartikel wurde hierfür eine map[int]string verwendet. Go randomisiert jedoch absichtlich die Iterationsreihenfolge von Maps. Würde man eine Map verwenden, käme gelegentlich „BuzzFizz“ statt „FizzBuzz“ heraus! Um das zu verhindern, nutzen wir einen Slice aus Structs, damit die Ausführungsreihenfolge garantiert erhalten bleibt.
package main
import "fmt"
type Rule struct {
Divisor int
Word string
}
func run(max int, rules []Rule) []string {
var all []string
for i := 1; i <= max; i++ {
res := ""
for _, rule := range rules {
if i%rule.Divisor == 0 {
res += rule.Word
}
}
if res == "" {
res = fmt.Sprint(i)
}
all = append(all, res)
}
return all
}
func main() {
rules := []Rule{
{3, "Fizz"},
{5, "Buzz"},
}
all := run(15, rules)
for _, word := range all {
fmt.Println(word)
}
}
Was wäre, wenn wir statt bloßer Daten direkt Verhalten übergeben? Wir können einen Typ Trigger als Funktion definieren, die abhängig vom aktuellen Schleifendurchlauf einen String-Pointer verändert.
package main
import "fmt"
type Trigger func(res *string, i int)
func run(max int, triggers []Trigger) []string {
var all []string
for i := 1; i <= max; i++ {
res := ""
for _, trigger := range triggers {
trigger(&res, i)
}
all = append(all, res)
}
return all
}
func main() {
all := run(15, []Trigger{
func(res *string, i int) {
if i%3 == 0 {
*res += "Fizz"
}
},
func(res *string, i int) {
if i%5 == 0 {
*res += "Buzz"
}
},
func(res *string, i int) {
if *res == "" {
*res = fmt.Sprint(i)
}
},
})
for _, word := range all {
fmt.Println(word)
}
}
Diese anonymen Funktionen jedes Mal auszuschreiben, ist repetitiv. Wir können den Code weiter bereinigen, indem wir eine Factory-Funktion schreiben, die eine Funktion zurückgibt. Dabei machen wir uns Closures in Go zunutze.
package main
import "fmt"
type Trigger func(res *string, i int)
func run(max int, triggers []Trigger) []string {
var all []string
for i := 1; i <= max; i++ {
res := ""
for _, trigger := range triggers {
trigger(&res, i)
}
all = append(all, res)
}
return all
}
// create fungiert als Factory und gibt eine maßgeschneiderte Trigger-Funktion zurück
func create(number int, word string) Trigger {
return func(res *string, i int) {
if i%number == 0 {
*res += word
}
}
}
func main() {
all := run(15, []Trigger{
create(3, "Fizz"),
create(5, "Buzz"),
func(res *string, i int) {
if *res == "" {
*res = fmt.Sprint(i)
}
},
})
for _, word := range all {
fmt.Println(word)
}
}
Machen wir das Ganze zum Schluss noch zu idiomatischem Go, indem wir Nebenläufigkeit (Concurrency) einsetzen. Anstatt alle Ergebnisse in einem Slice zu sammeln und erst am Ende zurückzugeben, können wir die Zahlen in einer Goroutine verarbeiten und die Ergebnisse über einen Channel laufend an den Hauptthread streamen.
Beachten Sie die Signatur chan<- string in run. Sie beschränkt die Funktion sauber darauf, Daten nur in den Channel zu senden – eine bewährte Best Practice in Go.
package main
import "fmt"
type Trigger func(res *string, i int)
func run(max int, out chan<- string, triggers []Trigger) {
for i := 1; i <= max; i++ {
res := ""
for _, trigger := range triggers {
trigger(&res, i)
}
out <- res // Ergebnis streamen
}
close(out) // Channel nach Abschluss immer schließen
}
func create(number int, word string) Trigger {
return func(res *string, i int) {
if i%number == 0 {
*res += word
}
}
}
func main() {
// Einen ungepufferten Channel erstellen
results := make(chan string)
// Die Generierung in einer Hintergrund-Goroutine starten
go run(15, results, []Trigger{
create(3, "Fizz"),
create(5, "Buzz"),
func(res *string, i int) {
if *res == "" {
*res = fmt.Sprint(i)
}
},
})
// Über den Channel iterieren, um Werte beim Eintreffen auszulesen
for word := range results {
fmt.Println(word)
}
}
Indem wir ein einfaches Problem wie FizzBuzz absichtlich überkonstruiert haben, sind wir von einfachen Schleifen über datengesteuerte Regeln (samt der Tücke ungeordneter Maps!), Closures und Funktionen höherer Ordnung bis hin zu Goroutinen und Channels gelangt. Eine schöne Erinnerung daran, wie viele Wege es gibt, ein und dasselbe Problem in Go zu lösen!
Lade die kuratierte Offline-Ausgabe von Kaues Lebensweise herunter – ein praktischer Leitfaden zur Optimierung von 🍏 Gesundheit, 💰 Wohlstand, ❤️ Liebe und 🧘 Sinn.
Erhalte eine Benachrichtigung, wenn ich neue Essays, Buchzusammenfassungen und praktische Leitfäden für ein gutes Leben veröffentliche. Kein Spam – jederzeit abbestellbar.
Hast du eine Frage, eine Perspektive oder einen Vorschlag? Teile deine Gedanken unten mit. Die Kommentare werden über GitHub Discussions bereitgestellt (werbefrei und spamfrei) – klicke unten auf Mit GitHub anmelden, um zu kommentieren oder zu reagieren (ein kostenloses GitHub-Konto ist in weniger als einer Minute erstellt).
Aktualisiert am 11.10.2026.
HAFTUNGSAUSSCHLUSS: Die Inhalte auf dieser Website dienen ausschließlich Informations- und Bildungszwecken und stellen keine medizinische, psychologische, finanzielle, steuerliche oder rechtliche Beratung sowie keine Anlageberatung dar. Die hier präsentierten Ideen, Zusammenstellungen und Meinungen sind meine eigenen und spiegeln nicht die meines Arbeitgebers wider. Zusammenfassungen von Büchern, Vorträgen und Artikeln sind unabhängige Bildungskommentare und bleiben geistiges Eigentum der jeweiligen Autoren. KI-Tools können zur Unterstützung bei der Bearbeitung, Formatierung und Synthese eingesetzt werden; obwohl größte Sorgfalt auf Richtigkeit gelegt wird, können Fehler auftreten. DATENSCHUTZ: Diese Website nutzt Dienste von Drittanbietern für optionale Newsletter-Abonnements (follow.it), Kommentare (giscus / GitHub Discussions) und eingebettete Medien (YouTube), die IP-Adressen und Cookies der Besucher gemäß ihren jeweiligen Datenschutzrichtlinien verarbeiten können.