Go-Interviewvorbereitung

Interviewfragen für Go-Backend-Entwickler

Ausgewählte Go-Fragen für Backend-Entwickler, nach Themen gruppiert und aus demselben Fragenkatalog gerendert wie die EngineerSpeak-Praxis.

Go-KI-Interview startenKeine Kreditkarte erforderlich. 1 kostenlose Session verfügbar.
Technische Interviews auf Englisch übenEin Modus, in dem Nicht-Muttersprachler technische Interviews üben können.

Typsystem

1Erklären Sie, wie die Nullwerte (zero values) in Go für eingebaute Typen und referenzähnliche Typen funktionieren und warum sie wichtig sind, wenn Variablen ohne explizite Initialisierung deklariert werden.

In Go wird eine Variable, die ohne expliziten Initialisierer deklariert wird, automatisch mit dem Nullwert ihres Typs initialisiert. Numerische Typen werden zu 0, bool wird zu false, string wird zu "", und Arrays oder structs werden Element für Element bzw. Feld für Feld auf den Nullwert gesetzt. Pointer- oder referenzähnliche Typen wie Pointer, slices, maps, channels, Funktionen und Interfaces haben nil als Nullwert. Das ist wichtig, weil Go-Variablen und ausgelassene struct-Felder in einem deterministischen Zustand starten, statt undefinierte Speicherinhalte zu enthalten, und viele APIs so entworfen sind, dass der Nullwert ein nützlicher Standard ist, auch wenn einige nil-Werte vor bestimmten Operationen dennoch initialisiert werden müssen.

Diese Frage mit einem KI-Coach beantworten

2Wie behandelt Go Gleichheit bei structs, und was passiert, wenn ein struct nicht vergleichbare Felder enthält?

Struct-Werte in Go können nur dann mit `==` und `!=` verglichen werden, wenn jedes Feld im struct vergleichbar ist. Gleichheit vergleicht entsprechende Felder nach der jeweiligen Gleichheitsregel des Feldtyps. Enthält ein struct ein nicht vergleichbares Feld wie ein slice, eine map oder eine Funktion, ist der struct-Typ nicht vergleichbar, und der Vergleich zweier Werte dieses struct-Typs mit `==` ist ein Compile-Zeit-Fehler. Für solche structs sollte man benutzerdefinierte Vergleichslogik oder einen geeigneten Helfer für tiefe Gleichheit verwenden, besonders in Tests.

Diese Frage mit einem KI-Coach beantworten

3Erklären Sie die Unveränderlichkeit von Strings in Go und die Beziehung zwischen string, []byte, bytes.Buffer und strings.Builder.

Ein Go-`string` ist eine unveränderliche Folge von Bytes, oft UTF-8-Text, aber nicht zwingend gültiges UTF-8. Einen string kann man nicht in place verändern; um Inhalte zu ändern, konvertiert man typischerweise zu `[]byte` für byteweise Änderungen oder zu `[]rune` für Änderungen auf Code-Point-Ebene und konvertiert dann zurück. Normale Konvertierungen zwischen `string` und `[]byte` kopieren Daten und können allozieren, daher können wiederholte Konvertierungen oder wiederholte Konkatenation in Schleifen teuer sein. `strings.Builder` ist für das effiziente Erzeugen von Strings optimiert, während `bytes.Buffer` ein veränderlicher Byte-Buffer ist, der für byteorientierte Daten und I/O nützlich ist und ebenfalls einen string liefern kann.

Diese Frage mit einem KI-Coach beantworten

4Erklären Sie, wie nil in Go für Pointer, slices, maps, channels, Funktionen und Interfaces unterschiedlich funktioniert.

In Go ist nil der Nullwert für Pointer, slices, maps, channels, Funktionen und Interfaces, aber die Operationen auf diesen nil-Werten unterscheiden sich je nach Typ. Ein nil-Pointer kann mit nil verglichen werden, aber sein Dereferenzieren führt zu einem panic. Ein nil-slice hat Länge und Kapazität 0 und kann mit range iteriert und mit append erweitert werden. Eine nil-map kann gelesen und mit range iteriert werden, aber eine Zuweisung in sie führt zu einem panic. Senden an einen nil-channel oder Empfangen von einem nil-channel blockiert für immer, und das Schließen eines nil-channel führt zu einem panic. Das Aufrufen einer nil-Funktion führt zu einem panic. Ein Interface ist nur dann nil, wenn es weder einen dynamischen Typ noch einen dynamischen Wert hat; ein Interface, das einen typed nil-Wert enthält, etwa einen nil-Pointer, ist selbst nicht nil.

Diese Frage mit einem KI-Coach beantworten

5Was sind vergleichbare Typen in Go, und wie wirken sich die Regeln zur Vergleichbarkeit auf map-Schlüssel, Gleichheit und Constraints in Generics aus?

Vergleichbare Typen in Go sind Typen, deren Werte mit `==` und `!=` verglichen werden können. Grundtypen, Pointer, channels, Interfaces und structs/Arrays, deren Felder oder Elemente vergleichbar sind, sind vergleichbar; slices, maps und Funktionen sind außer im Vergleich mit `nil` nicht vergleichbar. Map-Schlüssel müssen vergleichbar sein. Gleichheit folgt den Vergleichsregeln des Typs, und der Vergleich von Interfaces hängt von den dynamischen konkreten Werten ab; enthält ein verglichenes Interface einen nicht vergleichbaren dynamischen Wert, führt der Vergleich zu einem panic. In Generics erlaubt das vordefinierte Constraint `comparable`, Typparameter mit `==`/`!=` zu vergleichen und als map-Schlüssel zu verwenden.

Diese Frage mit einem KI-Coach beantworten

6Wie stellt Go Bytes, runes und UTF-8-kodierten Text dar, und warum kann len(s) von der Anzahl der für Nutzer sichtbaren Zeichen abweichen?

In Go ist `byte` ein Alias für `uint8` und repräsentiert ein rohes Byte, während `rune` ein Alias für `int32` ist und einen Unicode-Code-Point repräsentiert. Ein `string` ist eine schreibgeschützte Folge von Bytes, meist UTF-8-kodierter Text, kann aber beliebige Bytes enthalten. `len(s)` gibt die Anzahl der Bytes zurück, nicht die Anzahl der runes oder sichtbaren Zeichen. Das Indexieren eines string liefert ein Byte; Iteration mit range über einen string dekodiert UTF-8 und liefert Byte-Indizes plus runes. `len(s)` kann von der Anzahl sichtbarer Zeichen abweichen, weil UTF-8 für manche Code-Points mehrere Bytes verwendet und weil ein für Nutzer sichtbares Zeichen aus mehreren Code-Points bestehen kann, etwa durch kombinierende Zeichen oder Emoji-Sequenzen.

Diese Frage mit einem KI-Coach beantworten

Datenstrukturen

7Beschreiben Sie den Unterschied zwischen Arrays und slices in Go, einschließlich des Verhaltens von Länge, Kapazität und zugrunde liegendem Speicher.

Ein Array in Go hat eine feste Länge, die Teil seines Typs ist, zum Beispiel [3]int; es speichert seine Elemente direkt, und das Zuweisen oder Übergeben eines Arrays kopiert den gesamten Array-Wert. Ein slice, zum Beispiel []int, ist ein kleiner Deskriptor über einem zugrunde liegenden Array: Konzeptionell enthält es einen Pointer auf die Elemente, eine Länge und eine Kapazität. Die Länge eines slice ist die Anzahl der sichtbaren Elemente; seine Kapazität ist, wie viele Elemente ab dem Anfang des slice genutzt werden können, bevor das Ende des backing array erreicht ist. Slices sind flexibel: Reslicing ändert den Deskriptor, und append kann dasselbe zugrunde liegende Array wiederverwenden, wenn die Kapazität ausreicht, oder ein neues allozieren, wenn nicht.

Diese Frage mit einem KI-Coach beantworten

8Wie verhält sich der map-Typ in Go in Bezug auf Schlüsseltypen, fehlende Schlüssel, nil-maps und Iterationsreihenfolge?

Schlüsseltypen von Go-maps müssen vergleichbar sein; slices, maps und Funktionen können nicht direkt als Schlüssel verwendet werden. Das Nachschlagen eines nicht vorhandenen Schlüssels liefert den Nullwert des Elementtyps zurück, daher wird die comma-ok-Form (`v, ok := m[k]`) verwendet, um Abwesenheit von einem vorhandenen Nullwert zu unterscheiden. Eine nil-map kann gelesen und mit range iteriert werden, aber eine Zuweisung in sie führt zu einem panic; initialisieren Sie sie vor dem Schreiben. Die Iterationsreihenfolge von maps ist nicht spezifiziert, und Code darf sich nicht darauf verlassen.

Diese Frage mit einem KI-Coach beantworten

9Beschreiben Sie, wie Reslicing und Zuweisung von slices dazu führen können, dass mehrere slices dasselbe zugrunde liegende Array gemeinsam nutzen, und welche Bugs dadurch entstehen können.

Ein slice-Wert ist ein Header, der auf ein zugrunde liegendes Array zeigt. Das Zuweisen eines slice oder seine Übergabe an eine Funktion kopiert nur diesen Header, nicht die Elemente. Reslicing erzeugt einen weiteren Header, der auf einen Bereich desselben backing array zeigt. Daher können mehrere slices dasselbe Storage aliasen: Eine Änderung eines Elements über ein slice kann über ein anderes sichtbar werden, und append auf ein slice kann Daten überschreiben, die über ein anderes sichtbar sind, wenn noch freie Kapazität vorhanden ist. Zu den Bugs gehören überraschende Mutationen, korrupte Ergebnisse, das Behalten großer backing arrays über kleine subslices sowie Data Races, wenn Aliase gleichzeitig verwendet werden. Um unbeabsichtigtes Sharing zu vermeiden, macht man mit copy oder append([]T(nil), s...) eine defensive Kopie oder begrenzt die Kapazität vor append mit einem full-slice expression.

Diese Frage mit einem KI-Coach beantworten

10Erklären Sie das Wachstum von slices bei append auf konzeptioneller Ebene und die Performance-Auswirkungen wiederholter Reallokation.

Wenn append Elemente zu einem slice hinzufügt, schreibt es in das vorhandene backing array, sofern das slice genügend Kapazität hat. Reicht die Kapazität nicht aus, allokiert Go ein größeres backing array, kopiert die vorhandenen Elemente, schreibt die neuen Elemente und gibt einen slice header zurück, der auf den neuen Speicher zeigt. Die genaue Wachstumsstrategie ist implementierungsabhängig, aber konzeptionell wächst die Kapazität so, dass wiederholtes append amortisiert effizient ist. Wiederholte Reallokationen kosten dennoch CPU für das Kopieren, erzeugen Allokationen, erhöhen den GC-Druck und können Sharing mit alten slice-Aliasen aufbrechen. Wenn Sie die erwartete Größe kennen, allokieren Sie mit make([]T, 0, n) vor, wenn per append aufgebaut wird, oder mit make([]T, n), wenn per Index gefüllt wird, um Reallokationen zu reduzieren.

Diese Frage mit einem KI-Coach beantworten

Sprachsemantik

11Wie funktioniert der Blank Identifier in Go für unbenutzte Werte, Imports und compile-time-Interface-Prüfungen?

Der Blank Identifier `_` ist ein schreibgeschützter Platzhalter. Eine Zuweisung an ihn verwirft den Wert und erzeugt keine nutzbare Variable. Er wird verwendet, um nicht benötigte Rückgabewerte oder Schleifenvariablen zu ignorieren, um ein Paket nur wegen seiner Side Effects mit `import _ "pkg"` zu importieren, und für compile-time-Prüfungen der Interface-Implementierung wie `var _ io.Reader = (*MyReader)(nil)`. Ein Blank Import führt weiterhin die Initialisierung des importierten Pakets aus. Eine Interface-Prüfungszuweisung kompiliert nicht, wenn das Method Set des konkreten Typs das Interface nicht erfüllt.

Diese Frage mit einem KI-Coach beantworten

Pakete

12Wie funktioniert die Reihenfolge der Paketinitialisierung in Go, einschließlich init-Funktionen und importierter Abhängigkeiten?

Go initialisiert Pakete in Abhängigkeitsreihenfolge. Die importierten Abhängigkeiten eines Pakets werden vor dem importierenden Paket initialisiert. Innerhalb eines Pakets werden Paketvariablen initialisiert, bevor irgendwelche `init`-Funktionen laufen, wobei die Initialisierung von Variablen nach Abhängigkeiten und Deklarationsreihenfolge gemäß der Sprache geordnet ist. Danach laufen die `init`-Funktionen des Pakets automatisch; ein Paket kann mehrere `init`-Funktionen haben, und sie können nicht direkt aufgerufen werden. Jedes Paket wird genau einmal initialisiert. Für ein ausführbares Programm wird zuerst der Import-Graph initialisiert, dann das Paket `main`, und schließlich wird `main.main` aufgerufen.

Diese Frage mit einem KI-Coach beantworten

13Erklären Sie die Sichtbarkeitsregeln für Pakete in Go, einschließlich exportierter Bezeichner und der `internal/`-Verzeichnis-Konvention.

In Go wird die Sichtbarkeit auf Paketebene durch die Benennung von Bezeichnern gesteuert, nicht durch Zugriffsschlüsselwörter. Ein Bezeichner, dessen Name mit einem großgeschriebenen Unicode-Buchstaben beginnt, ist exportiert und kann aus anderen Paketen referenziert werden; andere Bezeichner sind nicht exportiert und nur innerhalb desselben Pakets verwendbar. Das gilt für Funktionen, Typen, Methoden, Variablen, Konstanten und struct-Felder. Pakete verwenden exportierte Bezeichner, um ihre öffentliche API zu definieren, und halten Implementierungsdetails nicht exportiert. Unabhängig davon darf ein Paket unter einem `internal/`-Verzeichnis nur von Code importiert werden, dessen Importpfad innerhalb des übergeordneten Baums dieses `internal`-Verzeichnisses liegt; das wird von der Go-Toolchain erzwungen.

Diese Frage mit einem KI-Coach beantworten

Kontrollfluss

14Wie funktioniert `defer` in Go, einschließlich Ausführungsreihenfolge, Zeitpunkt der Argumentauswertung und Zusammenspiel mit Rückgabewerten?

`defer` plant einen Funktionsaufruf so ein, dass er ausgeführt wird, wenn die umgebende Funktion beendet wird – egal ob durch normales `return` oder durch panic unwinding. Mehrere verschobene Aufrufe werden in Last-in-first-out-Reihenfolge ausgeführt. Der Wert der verzögert aufgerufenen Funktion und ihre Argumente werden sofort ausgewertet, wenn die `defer`-Anweisung ausgeführt wird, aber der eigentliche Aufruf läuft erst später. Bei benannten Rückgabewerten weist eine `return`-Anweisung zuerst die Rückgabewerte zu, dann werden die verzögerten Funktionen ausgeführt; daher kann eine verzögerte Closure benannte Ergebnisvariablen beobachten oder verändern, bevor der Aufrufer sie erhält. Dadurch ist `defer` nützlich für Cleanup wie das Schließen von Dateien, das Freigeben von mutexes und das Freigeben anderer Ressourcen.

Diese Frage mit einem KI-Coach beantworten

Fehlerbehandlung

15Erklären Sie das Fehlerbehandlungsmodell von Go und die üblichen Arten, wie Fehler erzeugt, zurückgegeben und geprüft werden.

Go behandelt Fehler als gewöhnliche Werte, nicht als Exceptions. Das eingebaute Interface `error` wird von jedem Typ erfüllt, der eine Methode `Error() string` hat. Funktionen geben konventionell ein `error` als letztes Ergebnis zurück; `nil` bedeutet Erfolg und ein nicht-`nil`-Fehler bedeutet, dass der Aufrufer den Fehler behandeln oder weitergeben muss. Einfache Fehler werden üblicherweise mit `errors.New` erzeugt, formatierte Fehler mit `fmt.Errorf`, und Aufrufer prüfen typischerweise mit `if err != nil { ... }`.

Diese Frage mit einem KI-Coach beantworten

16How should panic recovery be handled in Go backend services, including what happens when a goroutine panics and when a process should recover versus crash?

A panic unwinds the current goroutine, running its deferred functions. `recover` only works when called from a deferred function in that same goroutine; one goroutine cannot recover another goroutine's panic. If a panic is not recovered, the process crashes. In backend services, recovery should usually be placed at isolation boundaries such as request handlers, RPC middleware, or worker goroutine entrypoints so one failing request or job does not take down the whole service. But if a panic may have corrupted shared state or made process integrity untrustworthy, it is safer to let the process crash and restart rather than recover and continue blindly.

Diese Frage mit einem KI-Coach beantworten

Nebenläufigkeit

17Was sind nil-Kanäle in Go, und wie können sie Code versehentlich kaputtmachen oder select-Cases absichtlich deaktivieren?

Ein nil-Kanal ist eine Kanalvariable, deren Wert nil ist, oft weil sie nicht mit make initialisiert oder explizit auf nil gesetzt wurde. Senden an einen nil-Kanal oder Empfangen von einem nil-Kanal blockiert für immer. In einem select ist ein Case mit einem nil-Kanal niemals bereit; daher kann das Setzen einer Kanalvariablen auf nil diesen Case absichtlich deaktivieren. Die versehentliche Verwendung eines nil-Kanals kann dazu führen, dass goroutines hängen bleiben oder dass select-Logik erwartete Ereignisse nicht mehr verarbeitet.

Diese Frage mit einem KI-Coach beantworten

18Wie unterscheiden sich atomare Operationen in sync/atomic von mutex-basierter Synchronisation, und wann sind sie geeignet?

sync/atomic stellt unteilbare Operationen auf einzelnen Speicherstellen bereit, etwa load, store, add, swap und compare-and-swap, mit Synchronisations-/Memory-Ordering-Garantien. Ein mutex schützt einen kritischen Abschnitt und kann daher beliebigen Code sowie Invarianten über mehrere Lese-, Schreibzugriffe oder Felder hinweg schützen. Atomics sind für einfachen unabhängigen Zustand geeignet, etwa Zähler, Flags, Sequenznummern oder sorgfältig entworfene lock-free-Strukturen. Ein mutex ist vorzuziehen, wenn Operationen zusammengesetzt sind, mehrere Werte konsistent bleiben müssen oder die atomare Variante schwer korrekt zu durchdenken oder nachzuweisen wäre.

Diese Frage mit einem KI-Coach beantworten

19Wie sollten Channel-Ownership und der Lebenszyklus von goroutines gestaltet werden, um Goroutine-Leaks zu vermeiden?

goroutines sollten mit einem expliziten Besitzer, einem klaren Shutdown-Signal und einem garantierten Exit-Pfad entworfen werden. Die Producer-Seite ist im Allgemeinen für das Schließen eines Channels zuständig, insbesondere eines Output-Channels; Receiver sollten einen Channel nicht schließen, solange Sender noch aktiv sein können. Jeder blockierende Send, Receive, jede Schleife, jeder Timer oder externe Aufruf sollte entweder garantiert abgeschlossen werden oder sich über Cancellation entblocken lassen, üblicherweise über context.Context oder einen done-Channel. Verwende WaitGroup, errgroup oder ähnliche Koordination, damit auf Worker gewartet wird und Channels erst geschlossen werden, nachdem Sender beendet sind.

Diese Frage mit einem KI-Coach beantworten

20What are common causes of goroutine leaks in Go services, and how do you detect and fix them in production?

Common goroutine leaks in Go services come from goroutines blocked forever on channel sends or receives, waiting on other blocking operations without cancellation, stuck I/O without deadlines, background loops or tickers that never stop, and request-scoped goroutines that outlive the request. In production, you look for sustained growth in goroutine count and related symptoms, then inspect goroutine dumps or pprof goroutine profiles to see where goroutines are stuck. Fixing the leak means changing the code so those goroutines can exit: add cancellation and deadlines, stop tickers, close channels correctly, avoid detached request goroutines, and bound concurrency where needed.

Diese Frage mit einem KI-Coach beantworten