Voorbereiding voor Junior Go

Interviewvragen voor junior Go-backenddevelopers

15 geselecteerde Junior Go-interviewvragen voor backenddevelopers die basisconcepten helder en met vertrouwen moeten kunnen uitleggen.

Start een junior Go-interview met AIGeen creditcard nodig. 1 gratis sessie beschikbaar.
Oefen technische interviews in het EngelsEen modus waarin anderstaligen kunnen oefenen met het afleggen van technische interviews.

Typesysteem

1Leg uit hoe de nulwaarden (zero values) van Go werken voor ingebouwde en referentieachtige typen, en waarom ze belangrijk zijn bij het declareren van variabelen zonder expliciete initialisatie.

In Go wordt een variabele die zonder expliciete initialisator is gedeclareerd, automatisch geïnitialiseerd op de nulwaarde van het bijbehorende type. Numerieke typen worden 0, `bool` wordt `false`, `string` wordt `""`, en arrays of `struct`-waarden worden element voor element of veld voor veld op nul gezet. Pointerachtige of referentieachtige typen zoals pointers, `slice`-waarden, `map`-typen, channels, functies en interfaces hebben `nil` als nulwaarde. Dit is belangrijk omdat Go-variabelen en weggelaten `struct`-velden in een deterministische toestand beginnen in plaats van willekeurige geheugenwaarden te bevatten. Bovendien zijn veel API's zo ontworpen dat de nulwaarde een bruikbare standaardwaarde is, hoewel sommige `nil`-waarden nog steeds initialisatie vereisen voor bepaalde bewerkingen.

Probeer deze vraag te beantwoorden met een AI-begeleider

2Hoe gaat Go om met gelijkheid voor structs en wat gebeurt er als een struct onvergelijkbare velden bevat?

`struct`-waarden in Go kunnen alleen met `==` en `!=` worden vergeleken als elk veld in de struct vergelijkbaar is. Gelijkheid vergelijkt de overeenkomstige velden met behulp van de eigen gelijkheidsregel van elk veld. Als een struct een onvergelijkbaar veld bevat, zoals een slice, map of functie, dan is het `struct`-type niet vergelijkbaar en is het vergelijken van twee waarden van dat `struct`-type met `==` een fout tijdens het compileren. Gebruik voor dergelijke structs aangepaste vergelijkingslogica of een geschikte hulpfunctie voor diepe gelijkheid, vooral in tests.

Probeer deze vraag te beantwoorden met een AI-begeleider

3Leg de onveranderlijkheid van strings in Go uit en de relatie tussen string, []byte, bytes.Buffer en strings.Builder.

Een `string` in Go is een onveranderlijke reeks bytes, vaak UTF-8-tekst, maar het is niet vereist dat het geldige UTF-8 is. Je kunt een string niet ter plekke wijzigen; om de inhoud te veranderen converteer je deze doorgaans naar `[]byte` voor bewerkingen op byteniveau of naar `[]rune` voor bewerkingen op codepoint-niveau, om het daarna weer terug te converteren. Normale conversies tussen `string` en `[]byte` kopiëren de gegevens en kunnen geheugen toewijzen (alloceren), waardoor herhaalde conversies of herhaaldelijk aaneenschakelen in lussen kostbaar kunnen zijn. `strings.Builder` is geoptimaliseerd voor het efficiënt opbouwen van strings, terwijl `bytes.Buffer` een veranderlijke bytebuffer is die nuttig is voor byte-georiënteerde data en I/O, en ook een string kan produceren.

Probeer deze vraag te beantwoorden met een AI-begeleider

4Hoe werken pointers in Go, en welke operaties zijn opzettelijk niet toegestaan vergeleken met C?

Een pointer in Go is een getypeerde waarde die het adres van een andere waarde bevat, zoals `*int` die naar een `int` wijst. Gebruik `&x` om het adres van een waarde op te halen en `*p` om een pointer te dereferencen, zodat de waarde waarnaar gewezen wordt kan worden gelezen of geschreven. Door een pointer door te geven of op te slaan, kunnen meerdere plaatsen in de code dezelfde onderliggende waarde waarnemen of wijzigen, en een pointer kan `nil` zijn. In tegenstelling tot C, staat veilig Go opzettelijk geen rekenen met pointers en willekeurige manipulatie van ruwe adressen toe; voor uitzonderingen op laag niveau is de `unsafe`-package vereist.

Probeer deze vraag te beantwoorden met een AI-begeleider

5Leg uit hoe constanten en iota werken in Go, inclusief het verschil tussen getypeerde en ongetypeerde constanten.

Go-constanten zijn waarden die tijdens het compileren worden vastgesteld: booleans, strings of numerieke waarden. Een getypeerde constante heeft een specifiek type. Een ongetypeerde constante heeft geen vast concreet type totdat deze in een getypeerde context wordt gebruikt, en numerieke ongetypeerde constanten worden tot dat moment exact en met hoge precisie bewaard; de waarde moet kunnen worden gerepresenteerd in het gekozen type. iota is een vooraf gedeclareerde identifier die wordt gebruikt in const-declaraties: deze begint op 0 in elk const-blok en wordt voor elke constantenspecificatie verhoogd, wat het nuttig maakt voor enum-achtige constanten en bitflags.

Probeer deze vraag te beantwoorden met een AI-begeleider

6Hoe werken expliciete conversies in Go, en waarom vermijdt de taal veel impliciete conversies?

Go vereist doorgaans expliciete conversies met behulp van `T(x)` wanneer het type van een waarde verandert, zoals `int64(i)` of `MyID(n)`. Dit houdt typewijzigingen zichtbaar en voorkomt verrassende impliciete numerieke, boolean- of stringconversies. Conversies zijn alleen toegestaan volgens de conversieregels van Go en kunnen de waarde veranderen, bijvoorbeeld door integer-overflow, afkapping, afkapping van float naar int richting nul, of verlies van precisie. Ongetypeerde constanten zijn flexibeler: ze kunnen worden gebruikt in een getypeerde context zolang de constante waarde in dat type kan worden weergegeven.

Probeer deze vraag te beantwoorden met een AI-begeleider

Datastructuren

7Beschrijf het verschil tussen arrays en slices in Go, inclusief hoe de lengte, capaciteit en onderliggende opslag zich gedragen.

Een array in Go heeft een vaste lengte die onderdeel is van het type, zoals `[3]int`; de elementen worden direct opgeslagen, en bij het toewijzen of doorgeven van een array wordt de volledige arraywaarde gekopieerd. Een slice, zoals `[]int`, is een kleine descriptor bovenop een onderliggende array: conceptueel bevat het een pointer naar de elementen, een lengte en een capaciteit. De lengte van een slice is het aantal zichtbare elementen; de capaciteit geeft aan hoeveel elementen er vanaf het begin van de slice kunnen worden gebruikt voordat het einde van de onderliggende array wordt bereikt. Slices zijn flexibel: bij het aanpassen van de grenzen verandert de descriptor, en `append` kan dezelfde onderliggende array hergebruiken als de capaciteit dit toelaat, of een nieuwe toewijzen als dat niet het geval is.

Probeer deze vraag te beantwoorden met een AI-begeleider

8Hoe gedraagt het `map`-type in Go zich met betrekking tot sleuteltypen, ontbrekende sleutels, `nil`-maps en de volgorde van iteratie?

Sleuteltypen in een Go-`map` moeten vergelijkbaar zijn; `slice`-, `map`- en functietypen kunnen niet direct als sleutels worden gebruikt. Het opzoeken van een sleutel die niet aanwezig is, retourneert de nulwaarde van het elementtype, dus wordt de 'comma-ok'-notatie (`v, ok := m[k]`) gebruikt om afwezigheid te onderscheiden van een aanwezige nulwaarde. Uit een `nil`-map kan worden gelezen en er kan overheen worden geïtereerd, maar toewijzen eraan leidt tot een `panic`; initialiseer deze voor het schrijven. De iteratievolgorde van een `map` is niet gespecificeerd en code mag er niet van afhankelijk zijn.

Probeer deze vraag te beantwoorden met een AI-begeleider

Geheugenbeheer

9Wanneer moet je in Go een `struct` of andere waarde als waarde of als pointer doorgeven?

Geef door als waarde wanneer de waarde klein is, alleen-lezen is voor de aanroep, of wanneer je onafhankelijke kopieersemantiek wilt. Geef door als pointer wanneer de functie de waarde van de aanroeper moet muteren, kopiëren duur zou zijn, het type niet gekopieerd mag worden, of wanneer `nil` of gedeelde identiteit deel uitmaakt van de API. Ga er niet vanuit dat pointers altijd sneller zijn: ze kunnen aliasing, heap-escapes, extra werk voor de GC (Garbage Collector) en minder cachevriendelijke toegang tot gevolg hebben. Kies op de eerste plaats voor duidelijke semantiek en meet de prestaties van prestatiekritieke keuzes.

Probeer deze vraag te beantwoorden met een AI-begeleider

Taalsemantiek

10Hoe gedragen toewijzing en kopiëren zich voor `struct`-typen, arrays, `map`-typen en `slice`-waarden in Go?

Toewijzing in Go kopieert de waarde die wordt toegewezen, maar het effect hangt af van het type. Toewijzing van een `struct` kopieert de velden van de `struct`, en toewijzing van een array kopieert elk element. Toewijzing van een `slice` kopieert de header van de `slice` — pointer, lengte en capaciteit — waardoor slices doorgaans dezelfde onderliggende array delen. Toewijzing van een `map` kopieert een referentie-achtige mapdescriptor, zodat beide variabelen naar dezelfde mapgegevens verwijzen. Voor onafhankelijk eigenaarschap moet je een defensieve kopie maken, bijvoorbeeld door `copy` of `slices.Clone` te gebruiken voor slices, en door een nieuwe `map` aan te maken en de waarden te kopiëren voor maps.

Probeer deze vraag te beantwoorden met een AI-begeleider

11Hoe leggen closures variabelen vast in Go, en welke bugs kunnen ontstaan rond lussen en goroutines?

Een closure in Go is een functiewaarde die verwijst naar variabelen uit zijn omliggende lexicale bereik. Het legt de variabelen zelf vast, zodat mutaties door de closure kunnen worden waargenomen. Ook kunnen vastgelegde variabelen langer overleven dan de aanroepende functie als de closure ontsnapt. Bugs rond lussen en goroutines treden op wanneer closures een variabele delen of waarnemen nadat deze is veranderd. Dit leidt er vaak toe dat goroutines of uitgestelde callbackfuncties een onbedoelde waarde zien, of veroorzaakt data races op een gedeelde toestand. De gebruikelijke oplossing is om de beoogde waarde als argument aan de closure door te geven, of per iteratie een nieuwe lokale kopie te maken, en gelijktijdige toegang te synchroniseren waar dat nodig is. Sinds Go 1.22 zijn lusvariabelen die gedeclareerd zijn met `for`/`range` uniek per iteratie. Dit lost veel klassieke bugs op rond het vastleggen van lusvariabelen, maar hergebruikte variabelen buiten de lus en andere gedeelde veranderlijke toestanden kunnen nog steeds problemen veroorzaken.

Probeer deze vraag te beantwoorden met een AI-begeleider

12Beschrijf de semantiek van `range` bij het itereren over arrays, `slice`-waarden, `map`-typen, strings en channels.

`range` itereert afhankelijk van het type van de operand. Bij een array of `slice` levert het een index en de waarde van een element op; de waarde van dit element is een kopie, dus een toewijzing hieraan wijzigt de collectie niet. Bij het gebruik van `range` over een arraywaarde wordt de array gekopieerd voor de iteratie, terwijl een `range` over een `slice` de slice-header gebruikt en indexeert op de onderliggende array. Over een `map` levert het de sleutel en waarde op in een ongespecificeerde volgorde, waarbij de waarde wederom een kopie is. Bij een string levert het de byte-index en het gedecodeerde Unicode-codepoint (`rune`) op, niet de index van de positie van de rune. Over een channel ontvangt het waarden totdat het channel wordt gesloten en leeggemaakt; een `range` over een `nil`-channel zal voor altijd blokkeren.

Probeer deze vraag te beantwoorden met een AI-begeleider

Methoden

13Wat is het verschil tussen value-receivers en pointer-receivers, en hoe beïnvloedt de keuze van een receiver de veranderlijkheid, het kopiëren en het voldoen aan interfaces?

Een methode met een value-receiver ontvangt een kopie van de receiver, wat deze geschikt maakt voor alleen-lezen gedrag en kleine onveranderlijke typen; wijzigingen aan de kopie van de receiver zijn niet zichtbaar voor de aanroeper. Een methode met een pointer-receiver ontvangt een kopie van een pointer naar de receiver, zodat deze het origineel kan muteren en het kopiëren van grote waarden of waarden die niet gekopieerd mogen worden, vermijdt. De keuze van een receiver beïnvloedt methodesets: methoden met value-receivers bevinden zich in de methodeset van zowel T als *T, terwijl methoden met pointer-receivers zich alleen in de methodeset van *T bevinden. Daarom wordt aan een interface die een methode met een pointer-receiver vereist, voldaan door *T en niet door T, hoewel een adresseerbare T-waarde de methode toch kan aanroepen met de reguliere syntaxis voor methode-aanroepen.

Probeer deze vraag te beantwoorden met een AI-begeleider

Interfaces

14Beschrijf hoe interfaces werken in Go en wat het betekent als een type impliciet aan een interface voldoet.

Een interface in Go definieert een set vereiste methoden. Een concreet type voldoet impliciet aan een interface wanneer zijn methodenset deze methoden bevat; er is geen expliciete `implements`-declaratie nodig. Een interface-variabele kan een concrete dynamische waarde bevatten waarvan het type aan de interface voldoet, en het aanroepen van een interfacemethode wordt doorgestuurd naar de implementatie van die dynamische waarde. De lege interface, geschreven als `interface{}` of tegenwoordig meestal `any`, heeft geen vereiste methoden, waardoor elk type eraan voldoet. Of `T`, `*T` of beide aan een interface voldoen, hangt af van hun methodensets en keuzes voor de receiver.

Probeer deze vraag te beantwoorden met een AI-begeleider

15Hoe werken type-assertions en type-switches met interfacewaarden?

Een type-assertion controleert de dynamische waarde die is opgeslagen in een interface: `v := x.(T)` slaagt als het dynamische type van de interfacewaarde `T` is, of als `T` een interface is die wordt geïmplementeerd door de dynamische waarde. De vorm met één resultaat veroorzaakt een panic als deze faalt. De comma-ok-vorm, `v, ok := x.(T)`, meldt succes zonder een panic te veroorzaken. Een type-switch, `switch v := x.(type)`, vertakt op basis van het dynamische type van de interfacewaarde, waarbij `v` is getypeerd volgens de overeenkomende case.

Probeer deze vraag te beantwoorden met een AI-begeleider