Zurück zum Blog

Senior-Go-Interview vorbereiten: Was 2026 wirklich abgefragt wird

Senior-Go-Interviews überspringen die Syntax und prüfen dein Urteilsvermögen bei Nebenläufigkeit, Fehlerbehandlung und Design. Dieser Leitfaden zeigt, was gefragt wird und wie du darauf antwortest.

Senior-Go-Interview vorbereiten: Was 2026 wirklich abgefragt wird

Ein Senior-Go-Interview ist kein Syntax-Quiz. Wer einen Senior Engineer einstellt, interessiert sich nicht dafür, ob du die for-Schleife mit drei Klauseln aufsagen kannst oder weißt, dass append neu allokieren kann. Man will wissen, ob du einen Goroutine-Leak aufspüren, deine Art der Fehlerbehandlung verteidigen und eine API entwerfen kannst, mit der das Team zwei Jahre lang leben kann. Die Fragen sind absichtlich offen gestellt. Der Interviewer beobachtet, wie du denkst, und prüft nicht, ob du die Standardbibliothek auswendig gelernt hast.

Dieser Leitfaden behandelt die Themen, die Senior-Kandidaten von Mid-Level-Kandidaten unterscheiden, jeweils mit lauffähigem Code. Er basiert darauf, was Go-Entwickler nach eigener Aussage bauen und womit sie kämpfen, nicht auf zusammenkopierte Listen mit den „Top 50 Fragen“.

Kurzfassung

  • Bei der Nebenläufigkeit zahlt sich Vorbereitung am meisten aus. Die beiden Dinge, die Go-Entwickler am häufigsten bauen, sind API/RPC-Services (75 %) und Kommandozeilentools (62 %), und beide stützen sich stark auf Goroutines, Channels und context (Go Developer Survey 2024 H2).
  • Die am häufigsten genannte Herausforderung ist idiomatischer Code, nicht fehlende Features. 33 % der Befragten nannten „sicherzustellen, dass unser Go-Code Best Practices und Idiomen folgt“ als ihre größte Herausforderung, mehr als irgendein fehlendes Sprachfeature nannten (2025 Go Developer Survey).
  • Data Races passieren in Produktion, also rechne auch im Interview damit. Ubers Race Detector fand im Go-Monorepo rund 2.000 Data Races, und 210 Engineers haben etwa 1.100 davon innerhalb von sechs Monaten behoben (Uber Engineering).
  • Du solltest errors.Is, errors.As und %w im Schlaf beherrschen. 28 % der Befragten sagten, Go fehle ein Feature, das sie aus einer anderen Sprache schätzen, und Fehlerbehandlung steht auf dieser Liste ganz oben. Deshalb fragen Interviewer genau nach, wie du damit umgehst (2025 Go Developer Survey).
  • Generics können drankommen, sind aber auch eine Falle. Sie kamen mit Go 1.18, das das Go-Team „die größte Änderung, die es je an der Sprache gab“ nannte (Go Blog). Das Senior-Signal ist, zu wissen, wann du sie nicht einsetzt.
  • Orientiere dich an der aktuellen Toolchain. Go 1.26 ist das neueste stabile Release (Release Notes zu Go 1.26). Interviewer merken, wenn du das Verhalten einer alten Version beschreibst.

Inhaltsverzeichnis

Was ein Senior-Go-Interview wirklich testet

Keine maßgebliche Umfrage bringt „die häufigsten Go-Interviewfragen“ in eine Rangfolge, und du solltest jedem Blog misstrauen, der dafür einen genauen Prozentsatz nennt. Du kannst aber von dem ausgehen, womit Go-Entwickler ihre Zeit verbringen. Die Umfrage von 2024 ergab, dass 75 % der Befragten API- oder RPC-Services bauen und 62 % Kommandozeilentools (Go Developer Survey 2024 H2). Diese Arbeit ist nebenläufig, netzwerkgebunden und kann auf viele Arten schiefgehen. Deshalb kommen Interviews immer wieder auf Nebenläufigkeit, context und Fehlerbehandlung zurück.

Die Umfrage von 2025 fragte nach den größten Herausforderungen. 33 % nannten das Befolgen von Best Practices und Idiomen. 28 % nannten ein Feature, das sie aus einer anderen Sprache schätzen und das Go nicht hat, am häufigsten Fehlerbehandlung, Summentypen und Schutz vor nil-Pointern. 26 % nannten das Finden vertrauenswürdiger Module (2025 Go Developer Survey). Ein Senior-Interview besteht zu einem guten Teil darin, dass das Unternehmen prüft, ob du die Idiome verinnerlicht hast, mit denen sich ein Drittel der Community schwertut.

Das Interview testet also vor allem dein Urteilsvermögen. Wählst du das passende Nebenläufigkeitsprimitiv, oder startest du aus Reflex eine Goroutine? Kannst du erklären, warum du einen Fehler gewrappt hast, statt ihn zu loggen und weiterzumachen? Kannst du sagen „Hier würde ich keine Generics verwenden“ und das begründen?

Nebenläufigkeit: Der Kern jedes Senior-Go-Interviews

Bei der Nebenläufigkeit verlieren Mid-Level-Kandidaten Angebote. Der klassische Fehler ist, für jede Arbeitseinheit eine Goroutine zu starten, ohne Obergrenze, ohne Fehlerpfad und ohne Möglichkeit, sie zu stoppen. Senior-Kandidaten gehen von einem begrenzten Muster aus.

Goroutines sind günstig, und genau das macht den naiven Ansatz so verlockend. Aus der Go FAQ:

Der anfängliche Stack ist auf den meisten Plattformen etwa 2KB groß und wird von der Runtime verwaltet. Deshalb kann ein Programm Hunderttausende Goroutines gleichzeitig ausführen. Günstig heißt allerdings nicht kostenlos. Unbegrenzte Goroutines fressen Speicher, überfluten nachgelagerte Services und verschleiern Leaks. Die übliche Lösung ist ein Worker Pool.

Worker Pools

Ein Worker Pool startet eine feste Anzahl von Goroutines und versorgt sie über einen Channel mit Arbeit. Das ist die Standardantwort auf „Verarbeite 10.000 URLs, ohne 10.000 Sockets gleichzeitig zu öffnen“.

example.gogo
package main

import (
	"fmt"
	"sync"
)

// fetchStatus stands in for any bounded I/O call (HTTP, DB, RPC).
func fetchStatus(url string) string {
	return "200 " + url
}

func main() {
	urls := []string{"a", "b", "c", "d", "e"}
	const workers = 3

	jobs := make(chan string)
	results := make(chan string)

	var wg sync.WaitGroup
	for range workers {
		wg.Go(func() {
			for url := range jobs {
				results <- fetchStatus(url)
			}
		})
	}

	// Close results once every worker has returned.
	go func() {
		wg.Wait()
		close(results)
	}()

	go func() {
		for _, url := range urls {
			jobs <- url
		}
		close(jobs)
	}()

	for r := range results {
		fmt.Println(r)
	}
}

Interviewer achten auf drei Details. Du schließt jobs, damit die range-Schleifen der Worker enden. Du nutzt eine sync.WaitGroup, um zu wissen, wann du results gefahrlos schließen kannst. Und du schließt results aus einer separaten Goroutine, damit die Hauptschleife den Channel leeren kann. Wenn du das richtig machst, hast du gezeigt, dass du das Ganze verstehst. Zwei neuere Details zeigen, dass du mit der Toolchain Schritt hältst. for range workers iteriert über einen Integer, was seit Go 1.22 funktioniert. wg.Go kam mit Go 1.25 und ersetzt das alte Paar aus wg.Add(1) und defer wg.Done(). Damit fällt der häufigste WaitGroup-Bug weg, nämlich eine Hälfte des Paars zu vergessen.

Data Races und der Race Detector

Rechne mit einer Frage nach dem Muster „Was ist an diesem Code falsch?“, in der ein Race steckt. Races tauchen ständig in Produktionscode auf, nicht nur im Interview. Uber hat die Zahlen veröffentlicht. Das Go-Monorepo umfasst rund 50 Millionen Zeilen Code in etwa 2.100 eigenständigen Go-Services. Der Race Detector fand knapp 2.000 Data Races, und 210 Engineers haben rund 1.100 davon in sechs Monaten behoben (Uber Engineering).

Das Lehrbuchbeispiel für ein Race ist ein gemeinsam genutzter Zähler:

example.gogo
// BROKEN: concurrent writes to count are a data race.
func countBroken(items []int) int {
	count := 0
	var wg sync.WaitGroup
	for _, n := range items {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if n%2 == 0 {
				count++ // unsynchronized write
			}
		}()
	}
	wg.Wait()
	return count
}

Die richtige Lösung hängt von der Form des Problems ab. Für einen einfachen Zähler passt sync/atomic oder ein sync.Mutex. Bei Aggregation funktioniert oft ein Channel besser, über den jede Goroutine ihr Ergebnis meldet, während eine einzige Goroutine die Summe verwaltet. Eine Senior-Antwort benennt die Abwägung. Ein Mutex ist leichter zu lesen, Atomics sind unter Contention schneller, und ein Channel („share memory by communicating“) beseitigt den gemeinsamen Zustand ganz. Sag dann „Ich würde es mit go test -race laufen lassen“, denn diese Gewohnheit fängt Races ab, bevor sie ausgeliefert werden.

Fan-Out, Fan-In

Fan-Out verteilt Arbeit auf mehrere Goroutines, und Fan-In führt ihre Ergebnisse wieder in einem Channel zusammen. Du nutzt das für parallelen I/O, der einen einzelnen Consumer beliefert, und es passt gut zum Worker Pool von oben. Kandidaten stolpern meist über den Merge-Schritt. Um den zusammengeführten Channel zu schließen, brauchst du eine WaitGroup über die Producer, genau wie beim results-Channel des Worker Pools. Wenn du erklären kannst, warum beide Muster ihren Output auf dieselbe Weise schließen, hast du gezeigt, dass du Channel-Ownership verstehst und nicht nur ein Snippet wiedergibst.

Context: Abbruch und Deadlines

Go-Services reichen context.Context durch den gesamten Call Stack, und Interviewer werden dich bitten, es korrekt einzusetzen. Die Regeln lauten, dass context der erste Parameter ist, ctx heißt und du ihn nie in einem Struct speicherst. Er transportiert Abbruchsignale, Deadlines und requestbezogene Werte über API-Grenzen hinweg.

Die häufigste Folgefrage lautet: „Wie stoppst du einen Worker, der bei einem langsamen Aufruf blockiert?“ Du nutzt ein select auf die Arbeit und ctx.Done() zugleich:

example.gogo
func process(ctx context.Context, jobs <-chan string) error {
	for {
		select {
		case <-ctx.Done():
			return ctx.Err() // context.Canceled or DeadlineExceeded
		case job, ok := <-jobs:
			if !ok {
				return nil // channel closed, work done
			}
			if err := handle(ctx, job); err != nil {
				return fmt.Errorf("handling %q: %w", job, err)
			}
		}
	}
}

Wenn du ctx.Err() zurückgibst, erfährt der Aufrufer, warum du aufgehört hast, und kann eine abgelaufene Deadline von einem bewussten Abbruch unterscheiden. Schwächere Antworten ignorieren den Context oder prüfen ihn nur am Anfang der Schleife. Dann bemerkt eine Goroutine, die in handle feststeckt, den Abbruch nie. Starke Kandidaten erwähnen außerdem, dass der aufrufende Code die cancel-Funktion aufrufen muss, die context.WithCancel oder context.WithTimeout zurückgibt, meist mit defer cancel(), um Ressourcen freizugeben.

Sei auf die Frage vorbereitet: „Was ist der Unterschied zwischen context.WithCancel und context.WithTimeout?“ Der erste bricht ab, wenn du cancel aufrufst. Der zweite bricht zusätzlich ab, sobald eine Deadline verstrichen ist. In beiden Fällen solltest du defer cancel() schreiben, denn eine geleakte interne Goroutine eines Context ist ebenfalls ein Ressourcenleck.

Fehlerbehandlung auf Senior-Niveau

Fehlerbehandlung ist das Feature, das Go-Entwickler am meisten aus anderen Sprachen vermissen. 2025 gaben 28 % der Befragten an, dass Go ein Feature fehlt, das sie anderswo schätzen, und Fehlerbehandlung führte diese Liste an (2025 Go Developer Survey). Dieser Frust ist der Grund, warum Interviewer das Thema testen. Sie wollen sehen, dass du Fehler als Werte behandelst, die etwas bedeuten, und nicht als Rauschen, das man loggt und vergisst.

Die Grundlage ist Wrapping mit %w, damit Aufrufer die Kette untersuchen können:

example.gogo
var ErrNotFound = errors.New("not found")

func loadUser(ctx context.Context, id string) (*User, error) {
	row, err := db.Query(ctx, id)
	if err != nil {
		// Wrap, don't replace. The caller can still see the root cause.
		return nil, fmt.Errorf("loadUser %s: %w", id, err)
	}
	if row == nil {
		return nil, fmt.Errorf("loadUser %s: %w", id, ErrNotFound)
	}
	return row, nil
}

Danach prüft errors.Is auf einen Sentinel-Wert, und errors.As entpackt auf einen konkreten Typ:

example.gogo
user, err := loadUser(ctx, id)
if errors.Is(err, ErrNotFound) {
	http.Error(w, "user not found", http.StatusNotFound)
	return
}

var validationErr *ValidationError
if errors.As(err, &validationErr) {
	http.Error(w, validationErr.Field+" is invalid", http.StatusBadRequest)
	return
}

Sei bereit zu erklären, wann du was einsetzt. Ein Sentinel mit errors.Is passt, wenn der Aufrufer nur wissen muss, welcher Fehler aufgetreten ist. Ein typisierter Fehler mit errors.As passt, wenn der Aufrufer Daten daraus braucht, etwa einen Feldnamen oder einen Statuscode. Wrappe mit %w nur, wenn der gewrappte Fehler wirklich Teil deines API-Vertrags ist, denn zu viel Wrapping legt Implementierungsdetails offen. Sprich außerdem laut aus, dass panic für Programmierfehler und nicht behebbare Fehler beim Start gedacht ist, nicht für erwartbare Situationen wie einen fehlenden Datensatz. Wer panic für einen gewöhnlichen Fehler nutzt, fällt schnell durch ein Senior-Screening.

Generics: Wann du sie einsetzen solltest

Generics kamen mit Go 1.18. So hat das Go-Team sie angekündigt:

Interviewer fragen nach Generics, um herauszufinden, ob du dich zurückhalten kannst. Die ehrliche Antwort lautet: Der meiste Code braucht sie nicht, und die Pakete slices und maps der Standardbibliothek decken die üblichen Fälle bereits ab. Setz Typparameter ein, wenn du sonst dieselbe Logik für mehrere Typen kopieren oder auf interface{} ausweichen und damit Typsicherheit verlieren würdest.

Ein guter Einsatz ist eine Hilfsfunktion mit Constraint, die die Standardbibliothek nicht schon bereitstellt:

example.gogo
import "cmp"

// Clamp constrains v to the range [lo, hi] for any ordered type.
func Clamp[T cmp.Ordered](v, lo, hi T) T {
	if v < lo {
		return lo
	}
	if v > hi {
		return hi
	}
	return v
}

Der Constraint cmp.Ordered aus dem Standardpaket cmp beschränkt T auf Typen, die < und > unterstützen. Auch deine Wahl des Beispiels sagt etwas aus. Schreib kein generisches Max. Go hat seit 1.21 die Built-ins max und min, und slices.Max und slices.Min erledigen das für Slices. Eine selbst geschriebene Version deutet also darauf hin, dass du aus Gewohnheit zu Generics greifst. Clamp rechtfertigt seinen Typparameter, weil nichts in der Standardbibliothek dieselbe Aufgabe erfüllt. Vor Generics brauchte eine solche Funktion entweder eine Kopie pro Typ oder interface{} mit Type Assertions, die zur Laufzeit eine Panic auslösen konnten. Heute prüft das der Compiler. Wenn die Funktion aber immer nur int verarbeitet, schreib sie für int. Vorzeitige Verallgemeinerung ist in Go ein Code Smell wie überall sonst, und der Interviewer hofft, dass du das sagst.

Interfaces und API-Design

Fragen zu Interfaces testen Designgespür, und das ist ein großer Teil dessen, was „Senior“ bedeutet. Das Go-Sprichwort, das du hier kennen solltest, lautet „accept interfaces, return structs“ (Interfaces annehmen, Structs zurückgeben). Eine Funktion sollte das schmalste Interface akzeptieren, das sie tatsächlich nutzt, und einen konkreten Typ zurückgeben, damit Aufrufer sich ihre Optionen offenhalten.

example.gogo
// Good: accepts the minimal behavior it needs.
func Copy(dst io.Writer, src io.Reader) (int64, error) {
	return io.Copy(dst, src)
}

Copy ist es egal, ob src eine Datei, eine Netzwerkverbindung oder ein Buffer ist. Weil die Funktion io.Reader akzeptiert, funktioniert sie mit allen davon. Deshalb sind io.Reader und io.Writer mit jeweils einer Methode die am häufigsten wiederverwendeten Interfaces der Sprache.

Ein paar weitere Punkte lohnen sich. Halte Interfaces klein, idealerweise eine oder zwei Methoden. Definiere sie in dem Paket, das sie nutzt, und nicht in dem, das sie implementiert, damit Pakete nicht ohne Grund voneinander abhängen. Und leg die in anderen Sprachen verbreitete Gewohnheit ab, vor alles ein Interface zu setzen. Ein Struct mit nur einer Implementierung braucht noch kein Interface. Füge eins hinzu, wenn eine zweite Implementierung oder ein Test-Double es wirklich braucht. Interviewer merken, wenn Abstraktionen auf Verdacht entstehen, und genau mit dieser Art von Code haben die 33 % zu tun, denen Idiome schwerfallen (2025 Go Developer Survey).

Die Live-Coding-Runde: Einen Rate Limiter bauen

In der Live-Runde sollst du meist etwas Kleines, aber Nebenläufiges bauen. Rate Limiter kommen oft vor, weil sie Goroutines, Channels, Zeit und Aufräumen kombinieren. Ein Token-Bucket-Limiter auf Basis von time.Ticker ist kurz genug, um ihn vor Ort zu schreiben, und gibt dir reichlich Gesprächsstoff:

example.gogo
type Limiter struct {
	tokens chan struct{}
	stop   chan struct{}
}

func NewLimiter(perSecond int) *Limiter {
	l := &Limiter{
		tokens: make(chan struct{}, perSecond), // burst capacity
		stop:   make(chan struct{}),
	}
	ticker := time.NewTicker(time.Second / time.Duration(perSecond))
	go func() {
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				select {
				case l.tokens <- struct{}{}: // refill one token
				default: // bucket full, drop the tick
				}
			case <-l.stop:
				return
			}
		}
	}()
	return l
}

// Allow reports whether a request may proceed right now.
func (l *Limiter) Allow() bool {
	select {
	case <-l.tokens:
		return true
	default:
		return false
	}
}

func (l *Limiter) Close() { close(l.stop) }

Der Code zählt weniger als die Art, wie du darüber sprichst. Weise darauf hin, dass dir der gepufferte tokens-Channel die Burst-Kapazität gratis liefert. Erkläre, dass das innere select mit default Refill-Ticks verwirft, wenn der Bucket voll ist, statt zu blockieren. Erwähne, dass Close die Hintergrund-Goroutine stoppt, damit sie nicht leakt, und dass ticker.Stop() den Timer freigibt. Bring dann die Alternative ins Spiel. In Produktion würdest du normalerweise golang.org/x/time/rate verwenden, statt das selbst zu schreiben, und zu wissen, wann die Standardlösung besser ist als eine eigene, gehört zu dem Urteilsvermögen, für das man dich einstellt.

Häufig gestellte Fragen

Welche Themen kommen in einem Senior-Go-Interview am häufigsten vor?

Nebenläufigkeit, context, Fehlerbehandlung, Interfaces und Generics, ungefähr in dieser Reihenfolge. Die Gewichtung folgt dem, was Go-Entwickler bauen. 75 % schreiben API/RPC-Services und 62 % CLI-Tools (Go Developer Survey 2024 H2), und beide Arten von Arbeit sind nebenläufig und können auf viele Arten schiefgehen. Keine glaubwürdige veröffentlichte Statistik zeigt, wie oft bestimmte Fragen vorkommen. Sei also skeptisch bei jedem Blog, der eine solche Statistik nennt.

Muss ich für ein Go-Interview Generics kennen?

Du solltest sie verstehen, und noch wichtiger ist, dass du weißt, wann du sie nicht einsetzt. Generics kamen mit Go 1.18 als größte Änderung, die es je an der Sprache gab (Go Blog), also können sie drankommen. Der meiste idiomatische Go-Code verzichtet trotzdem weiterhin darauf, und Senior-Kandidaten punkten, wenn sie erklären, dass die Standardpakete slices und maps die meisten Anforderungen abdecken.

Wie wichtig ist der Race Detector?

So wichtig, dass Interviewer erwarten, dass du ihn ansprichst, ohne gefragt zu werden. Ubers Race Detector fand knapp 2.000 Data Races im Go-Monorepo, und 210 Engineers haben etwa 1.100 davon in sechs Monaten behoben (Uber Engineering). Wenn du sagst „Ich würde go test -race laufen lassen“, zeigst du dem Interviewer, dass du an Produktionscode gearbeitet hast.

Auf welche Go-Version sollte ich mich einstellen?

Auf die aktuelle stabile Toolchain, Anfang 2026 also Go 1.26 (Release Notes zu Go 1.26). Wenn du das Verhalten alter Versionen beschreibst, wirkt dein Wissen veraltet, etwa wenn du behauptest, man brauche für die Iteration über einen Integer eine Schleife mit drei Klauseln. Die Form for range n funktioniert seit Go 1.22.

Lohnt es sich noch, sich auf Go zu spezialisieren?

Ja. 17,4 % der professionellen Entwickler gaben an, im vergangenen Jahr intensiv mit Go gearbeitet zu haben (Stack Overflow Developer Survey 2025). In der offiziellen Umfrage gaben 91 % der Befragten an, mit der Sprache zufrieden zu sein, und 63 % sagten, sie seien sehr zufrieden (2025 Go Developer Survey). Die Nachfrage nach Senior Engineers für Go folgt dieser Verbreitung.

Starte deinen Weg zum Senior-Go-Entwickler

Über diese Themen zu lesen heißt nicht, dass du sie unter Druck schreiben kannst. Senior-Interviews belohnen Muskelgedächtnis: einen Worker Pool, den du ohne Nachdenken tippst, eine Fehlerkette, die du reflexartig wrappst, ein Generic, das du bewusst weglässt. Dahin kommst du, indem du jeden Tag echtes Go auf der aktuellen Toolchain schreibst und Feedback dazu bekommst, ob dein Code idiomatisch ist. Genau dieses Feedback ist schwer zu finden, und Idiome sind das, womit 33 % der Go-Community am meisten kämpfen.

Dafür ist LevelUpGo gebaut. Jede Lektion ist eine Übung im Browser, die auf dem neuesten stabilen Go-Release läuft und deinen Code automatisch bewertet. So übst du auf die Art, wie das Interview dich prüft, statt nur zu lesen.

Hier übst du die einzelnen Themen aus diesem Artikel:

  • Nebenläufigkeit, context und Channels: Der Kurs Go Fundamentals erarbeitet Goroutines, Channels und Abbruch von Grund auf. Der Einstieg ist kostenlos und ohne Kreditkarte möglich.
  • Fehlerbehandlung, Interfaces und Idiome: Der Lernpfad Clean Go Code enthält bewertete Übungen zu errors.Is/errors.As, kleinen Interfaces und dem Designgespür aus diesem Artikel.
  • Die komplette LevelUpGo-Roadmap zeigt, wie die Kurse von den Grundlagen bis zum Design auf Senior-Niveau aufeinander aufbauen.

Starte den kostenlosen Kurs und schreib bis zum Interview jeden Tag Go. Dann gehst du ins Gespräch und kannst deine Überlegungen erklären, statt zu raten.

Schreib Go wie ein Senior Engineer

Interaktive Lektionen im Browser. Die ersten sind kostenlos.

Kostenlose Lektion testenOder kostenloses Konto erstellen