fmt.Println ist die Funktion, mit der so gut wie jede Go-Karriere beginnt — fmt.Println("Hallo, Welt") steht in jedem Einsteiger-Tutorial. Sie schreibt ihre Argumente positional nach Stdout, formatiert jeden Operand mit dem Default-Verb %v, trennt sie durch Leerzeichen und beendet die Zeile mit einem Newline.
Genau diese drei Eigenheiten machen Println zur idiomatischen Wahl, wenn man einfach etwas ausgeben will, ohne über Format-Strings nachzudenken: immer Leerzeichen zwischen Operanden, immer Newline am Ende, immer %v als Formatierung.
Signatur
func Println(a ...any) (n int, err error) — variadisch, akzeptiert beliebig viele Argumente beliebigen Typs. Die Rückgabewerte sind dieselben wie bei Print und Printf: die Anzahl geschriebener Bytes und ein optionaler Fehler.
Wichtig ist das ...any (früher ...interface{}): jeder Operand darf einen beliebigen Typ haben, und Println entscheidet zur Laufzeit über Reflection, wie er formatiert wird. Genau wie bei Print interessieren uns die Rückgabewerte in der Praxis selten — fast jeder Aufruf in echtem Code lässt n und err ungenutzt.
Leerzeichen-Regel — anders als Print
Der wichtigste Unterschied zu fmt.Print: Println fügt immer Leerzeichen zwischen allen Operanden ein — unabhängig von deren Typ. Während Print nur dann ein Leerzeichen einschiebt, wenn beide Nachbarn keine Strings sind, ist Println in dieser Hinsicht radikal einfach: zwischen je zwei Argumenten landet genau ein Space im Output.
package main
import "fmt"
func main() {
fmt.Println("a", "b")
fmt.Println(1, 2)
fmt.Println("a", 1, "c")
fmt.Println("Name:", "Klaus")
}a b
1 2
a 1 c
Name: KlausIm letzten Beispiel sieht man, warum diese Regel so angenehm ist: Println("Name:", "Klaus") liefert Name: Klaus — das Leerzeichen kommt geschenkt. Bei Print müsste man entweder "Name: " mit Trailing-Space schreiben oder einen Nicht-String-Operand dazwischen schmuggeln.
Newline am Ende
Println hängt immer ein \n an die ausgegebene Zeile. Das ist nicht konfigurierbar, nicht abschaltbar, nicht unterdrückbar. Wer das Newline nicht will, ist bei Println falsch und greift zu Print oder Printf.
package main
import "fmt"
func main() {
fmt.Println("eins")
fmt.Println("zwei")
fmt.Println("drei")
}eins
zwei
dreiEin nachgestelltes \n im String selbst (Println("eins\n")) ist überflüssig und produziert eine Leerzeile, weil Println sein eigenes Newline trotzdem dranhängt.
Operand-Formatierung
Jeder Operand wird so formatiert, als hätte man Printf mit dem Default-Verb %v aufgerufen. Für Strings: unverändert. Für Zahlen: dezimal. Für Structs: feldweise in geschweiften Klammern. Für Typen mit String() string-Methode (Stringer-Interface): genau diese Methode.
package main
import "fmt"
type Ampel int
const (
Rot Ampel = iota
Gelb
Gruen
)
func (a Ampel) String() string {
switch a {
case Rot:
return "ROT"
case Gelb:
return "GELB"
case Gruen:
return "GRÜN"
}
return "?"
}
func main() {
fmt.Println("Aktuelle Phase:", Rot)
fmt.Println("Reihenfolge:", Rot, Gelb, Gruen)
}Aktuelle Phase: ROT
Reihenfolge: ROT GELB GRÜNOhne String()-Methode hätte Println die rohen Integer-Werte 0, 1, 2 ausgegeben. So aber liefert es genau die sprechende Repräsentation, die der Typ selbst definiert.
Vergleich zu Print und Printf
Am klarsten wird Println im direkten Vergleich mit seinen beiden Geschwistern. Dieselbe Eingabe, drei Funktionen, drei deutlich unterschiedliche Ergebnisse.
package main
import "fmt"
func main() {
fmt.Print("a", "b", 1, "\n")
fmt.Println("a", "b", 1)
fmt.Printf("%v%v%v\n", "a", "b", 1)
}ab 1
a b 1
ab1Print schiebt nur zwischen "b" und 1 ein Leerzeichen ein, Println liefert sauber a b 1, und Printf ohne Spaces im Format-String klebt alles zusammen.
Anwendungsbereiche
Println ist überall dort die richtige Wahl, wo man keinen Format-String braucht und eine Zeile pro Aufruf möchte. Vier Szenarien: Hello-World, Debug-Output während der Entwicklung, einfache CLI-Output-Zeilen, und überall dort, wo der Output-Inhalt ohnehin schon strukturiert vorliegt (z. B. durch einen Stringer).
In Production-Code für Server, Daemons oder größere Tools verschiebt sich der Fokus: dort will man strukturiertes Logging mit Levels, Kontextfeldern und maschinenlesbarem Output. Dafür ist log/slog aus der Standardbibliothek die richtige Adresse — Println bleibt das Werkzeug für Entwickler-Output und kleine, menschenlesbare CLIs.
Println vs Printf
Eine häufige Verwechslung: „Ich nehme einfach Printf("%v %v\n", a, b) statt Println." Das funktioniert, ist aber länger, fehleranfälliger und nicht idiomatisch.
package main
import "fmt"
func main() {
name := "Klaus"
alter := 42
fmt.Printf("%v %v\n", name, alter)
fmt.Println(name, alter)
}Klaus 42
Klaus 42Gleicher Output, aber Println spart Format-String, Newline und mentale Last. Sobald jedoch echte Formatierung ins Spiel kommt — feste Breiten, Hex, Float-Präzision — ist Printf wieder das richtige Werkzeug. Die Faustregel: Format nötig → Printf. Nur Werte ausgeben → Println.
Println bei Slices und Maps
Println formatiert auch zusammengesetzte Typen vollständig — Slices erscheinen in eckigen Klammern, Maps in geschweiften, jeweils mit space-separierten Elementen.
package main
import "fmt"
func main() {
zahlen := []int{1, 2, 3, 4}
preise := map[string]float64{"Apfel": 0.5, "Birne": 0.7}
fmt.Println("Zahlen:", zahlen)
fmt.Println("Preise:", preise)
}Zahlen: [1 2 3 4]
Preise: map[Apfel:0.5 Birne:0.7]Bei kleinen Datenstrukturen ist das perfekt. Bei großen, tief verschachtelten Structs wird der Output schnell unleserlich — dann lohnt sich fmt.Sprintf("%+v", x) mit anschließender Übergabe an einen Logger, oder direkt slog.Info("...", "key", x).
Praxis 1 — Vom Hello-World zur strukturierten Ausgabe
Der typische Lernweg mit Println beginnt mit einem einzigen Argument und wächst dann organisch mit.
package main
import "fmt"
type Nutzer struct {
Name string
Alter int
}
func (n Nutzer) String() string {
return fmt.Sprintf("%s (%d)", n.Name, n.Alter)
}
func main() {
fmt.Println("Hallo")
fmt.Println("Hallo,", "Welt")
fmt.Println("Antwort:", 42, "von", 100)
user := Nutzer{Name: "Klaus", Alter: 42}
fmt.Println("Nutzer:", user)
user2 := Nutzer{Name: "Anna", Alter: 35}
fmt.Println("Vergleich:", user, "vs.", user2)
}Hallo
Hallo, Welt
Antwort: 42 von 100
Nutzer: Klaus (42)
Vergleich: Klaus (42) vs. Anna (35)Der Schlüssel zur Progression: dieselbe Funktion trägt vom ersten Println("Hallo") bis zur strukturierten Vergleichszeile. Custom-Typen werden über String() formatiert, gemischte Argumente automatisch durch Spaces getrennt, jede Zeile durch das eingebaute Newline abgeschlossen.
Praxis 2 — Debug-Output während der Entwicklung
Eine der häufigsten echten Anwendungen von Println ist das explorative Debugging: man steckt mitten in einer Funktion, weiß nicht genau, welcher Wert wo ankommt, und streut schnell ein paar Println-Zeilen ein.
package main
import (
"fmt"
"strings"
)
func normalisiereName(roh string) string {
fmt.Println("Eingabe:", roh)
getrimmt := strings.TrimSpace(roh)
fmt.Println("Nach Trim:", getrimmt)
klein := strings.ToLower(getrimmt)
fmt.Println("Nach ToLower:", klein)
mitBindestrich := strings.ReplaceAll(klein, " ", "-")
fmt.Println("Endergebnis:", mitBindestrich)
return mitBindestrich
}
func main() {
_ = normalisiereName(" Klaus Müller ")
}Eingabe: Klaus Müller
Nach Trim: Klaus Müller
Nach ToLower: klaus müller
Endergebnis: klaus-müllerJede Zwischenstufe wird sichtbar — mit Label, Wert und automatischem Newline. Wichtig zur Abgrenzung: in Produktivcode haben solche Println-Zeilen nichts zu suchen. Dort übernimmt log/slog, das Levels, strukturierte Felder und konfigurierbare Senken bietet.
Interessantes
Immer Leerzeichen zwischen Operanden
Println fügt zwischen jedem Argumentpaar genau ein Space ein — unabhängig vom Typ. Das ist der entscheidende Unterschied zu fmt.Print.
Immer ein Newline am Ende
Println hängt grundsätzlich \n an. Nicht konfigurierbar — wer kein Newline will, nimmt Print oder Printf.
Erste Wahl für Hello-World und Quick-Debug
Wenn kein Format-String nötig ist und der Output zeilenweise sein soll, ist Println idiomatisch und am kürzesten.
Operanden werden via %v formatiert
Jedes Argument durchläuft Default-Formatierung. Typen mit String() string werden über diese Methode dargestellt — Custom-Repräsentationen funktionieren automatisch.
Rückgabewerte meist verworfen
(n int, err error) interessieren in der Praxis selten — bei Bedarf mit _, _ = fmt.Println(...) explizit ignorieren.
Production: lieber log/slog
Println kennt keine Levels, keine Kontextfelder. Für Server und langlebige Dienste ist log/slog der richtige Weg.
Println vs Printf
Wenn man nur Werte ausgeben will, ist Println kürzer. Sobald echte Formatierung dazukommt, gewinnt Printf.
Slices und Maps werden vollständig ausgegeben
Praktisch für schnelles Debugging. Bei großen Strukturen lieber Sprintf("%+v", x) an einen Logger geben.