Go-Interviews drehen sich um Goroutines, Channels, Interfaces und Fehlerbehandlung — die Sprache ist klein, deshalb gehen die Fragen eher in die Tiefe als in die Breite. Hier sind die häufigsten mit Musterantworten.
Zu jeder Frage gibt es eine Musterantwort, mit der du deine eigene vergleichen kannst.
1
Was ist eine Goroutine und wie unterscheidet sie sich von einem OS-Thread?
Antwort
Eine Goroutine ist eine Funktion, die vom Go-Runtime und nicht vom Betriebssystem geplant wird. Sie startet mit einem winzigen Stack von wenigen Kilobyte, der bei Bedarf wächst, während ein OS-Thread Megabyte reserviert, sodass ein Programm problemlos Hunderttausende Goroutinen halten kann. Der Runtime multiplext sie auf einen kleinen Pool von OS-Threads und wechselt an Blockierpunkten ohne einen Kernel-Kontextwechsel.
2
Was ist der Unterschied zwischen einem gepufferten und einem ungepufferten Channel?
Antwort
Ein ungepufferter Channel ist synchron: Der Sender blockiert, bis ein Empfänger bereit ist, weshalb er gleichzeitig als Synchronisationspunkt dient. Ein gepufferter Channel nimmt bis zu seiner Kapazität auf, ohne dass ein Empfänger wartet, und blockiert erst, wenn er voll ist. Ungepuffert nutzt du, wenn du eine Übergabegarantie brauchst, gepuffert, um Lastspitzen abzufedern — ein großer Puffer verdeckt aber oft eher ein Designproblem, als dass er es löst.
3
Wie funktioniert select? Was passiert, wenn mehrere Cases bereit sind?
Antwort
select wartet auf mehrere Channel-Operationen und macht mit der weiter, die als Erstes bereit ist. Sind mehrere gleichzeitig bereit, wird gleichverteilt zufällig gewählt, was verhindert, dass ein einzelner Case ausgehungert wird. Eine default-Klausel macht die gesamte Anweisung nicht blockierend, und ein select ganz ohne Cases blockiert für immer — gelegentlich wird das bewusst genutzt, um eine Goroutine zu parken.
4
Erkläre den Unterschied zwischen einem Slice und einem Array. Wie funktioniert append?
Antwort
Ein Array hat eine feste Länge, die Teil seines Typs ist, und wird per Wert kopiert. Ein Slice ist ein Header mit einem Zeiger auf ein zugrunde liegendes Array, einer Länge und einer Kapazität, sodass beim Weitergeben nur der Header kopiert wird. append schreibt in freie Kapazität, wenn Platz vorhanden ist; andernfalls allokiert es ein größeres Array und kopiert, weshalb das zurückgegebene Slice sich unter Umständen keinen Speicher mehr mit dem ursprünglichen teilt — und weshalb du das Ergebnis immer zuweisen musst.
5
Was ist ein Interface in Go? Warum nennt man sie implizit?
Antwort
Ein Interface ist eine Menge von Methodensignaturen; jeder Typ, der diese Methoden besitzt, erfüllt es, ohne das explizit zu deklarieren. Genau das bedeutet implizit — es gibt kein implements-Schlüsselwort, sodass du ein Interface im konsumierenden Package definieren kannst, das bestehende Typen bereits erfüllen. Die idiomatische Empfehlung lautet, Interfaces klein zu halten und dort zu definieren, wo sie benutzt werden, nicht neben der Implementierung.
6
Wie behandelst du Fehler in Go? Was ist Error Wrapping mit %w?
Antwort
Fehler sind gewöhnliche Werte, die zusammen mit dem Ergebnis zurückgegeben und an jeder Aufrufstelle explizit geprüft werden. Das Wrapping mit fmt.Errorf und dem Verb %w bewahrt den ursprünglichen Fehler innerhalb einer neuen Nachricht, sodass Aufrufer weiterhin errors.Is nutzen können, um auf einen Sentinel-Wert zu prüfen, oder errors.As, um einen typisierten Fehler zu extrahieren. Wrapping fügt Kontext hinzu, während der Fehler nach oben wandert, ohne ihn programmatisch unüberprüfbar zu machen.
7
Was ist context.Context und wozu wird es gebraucht?
Antwort
Context transportiert ein Cancellation-Signal, ein Deadline und Werte im Request-Scope über API-Grenzen hinweg. Wenn ein Client die Verbindung trennt oder ein Timeout auslöst, pflanzt sich das Abbrechen des Context auf jede daraus abgeleitete Goroutine fort, sodass Datenbank-Queries und HTTP-Aufrufe abgebrochen werden, statt zu leaken. Konvention ist, ihn als ersten Parameter zu übergeben und niemals in einem Struct zu speichern.
8
Was ist defer? In welcher Reihenfolge werden deferred Aufrufe ausgeführt?
Antwort
defer plant einen Aufruf für den Moment, in dem die umgebende Funktion zurückkehrt, egal auf welchem Weg oder durch welches Panic sie dorthin gelangt. Deferred Aufrufe laufen nach dem Last-in-first-out-Prinzip, der zuletzt registrierte defer wird also zuerst ausgeführt. Argumente werden im Moment ausgewertet, in dem defer geschrieben wird, nicht wenn es läuft — eine häufige Überraschungsquelle, wenn ein Aufruf verzögert wird, der eine Schleifenvariable erfasst.
9
Wie vermeidest du Race Conditions? Mutex oder Channels?
Antwort
Eine Data Race ist unsynchronisierter, gleichzeitiger Zugriff, bei dem mindestens eine Seite schreibt, erkennbar mit dem -race-Flag. sync.Mutex nutzt du, wenn Goroutinen sich einen Zustand teilen und du den Zugriff darauf einfach serialisieren musst. Channels nutzt du, wenn die Eigentümerschaft an Daten zwischen Goroutinen übertragen wird — die Maxime lautet, Speicher durch Kommunikation zu teilen, statt durch geteilten Speicher zu kommunizieren.
10
Was ist ein nil-Interface, und warum kann der Vergleich mit nil überraschen?
Antwort
Ein Interface-Wert besteht aus zwei Feldern: einem Typ und einem Wert. Er ist nur dann gleich nil, wenn beide nil sind. Weist du einem Interface einen nil-Pointer eines konkreten Typs zu, ist das Typ-Feld gesetzt, sodass das Interface nicht-nil ist, obwohl der zugrunde liegende Pointer nil ist. Das ist der klassische Bug, bei dem eine Funktion ein nil *MyError als error zurückgibt und die err != nil-Prüfung des Aufrufers unerwartet durchgeht.
🦎
Antworten lesen reicht nicht
Im echten Interview sprichst du unter Druck. Cam stellt genau diese Fragen, bewertet jede Antwort und zeigt dir genau, was du verbessern musst.
Go-Backend-Interview üben →Kostenlos · 3 Interviews im Monat