Forberedelse til Middle Go-interview

Interviewspørgsmål til Middle Go Backend-udviklere

15 udvalgte Middle Go-interviewspørgsmål til backend-udviklere, der har brug for at forklare praktiske afvejninger, samtidighed og tjenesteadfærd.

Start et AI-interview om Middle GoIntet kreditkort kræves. 1 gratis session er tilgængelig.
Øv tekniske jobsamtaler på engelskEn tilstand, hvor ikke-modersmålstalende kan øve sig i at bestå tekniske interviews.

Typesystem

1Forklar, hvordan `nil` fungerer forskelligt for pointere, `slice`-værdier, `map`-typer, `channel`-typer, funktioner og interfaces i Go.

I Go er `nil` nulværdien for pointere, `slice`-værdier, `map`-typer, `channel`-typer, funktioner og interfaces, men operationer på disse `nil`-værdier varierer afhængigt af typen. En `nil`-pointer kan sammenlignes med `nil`, men hvis man dereferencerer den, forårsager det et `panic`. En `nil`-slice har længde og kapacitet på 0 og kan bruges med `range` samt tilføjes til med `append`. Et `nil`-map kan læses fra og bruges med `range`, men tildeling af værdier til det forårsager `panic`. At sende til eller modtage fra en `nil`-channel blokerer for evigt, og lukning af en `nil`-channel forårsager `panic`. At kalde en `nil`-funktion forårsager `panic`. Et interface er kun `nil`, når det hverken har en dynamisk type eller en dynamisk værdi; et interface, der indeholder en typestærk `nil`-værdi, såsom en `nil`-pointer, er ikke i sig selv `nil`.

Prøv at besvare dette spørgsmål med en AI-coach

2Hvad er sammenlignelige (comparable) typer i Go, og hvordan påvirker reglerne for sammenlignelighed `map`-nøgler, lighed og begrænsninger (constraints) i generics?

Sammenlignelige typer i Go er typer, hvis værdier kan sammenlignes med `==` og `!=`. Grundlæggende typer, pointere, channels, interfaces og `struct`-typer eller arrays, hvis felter eller elementer er sammenlignelige, er sammenlignelige; `slice`-værdier, `map`-typer og funktioner er ikke sammenlignelige, undtagen med `nil`. Nøgler i en `map` skal være sammenlignelige. Lighed følger typens sammenligningsregler, og sammenligning af interfaces afhænger af de dynamiske konkrete værdier; hvis et sammenlignet interface indeholder en dynamisk værdi, der ikke er sammenlignelig, vil sammenligningen forårsage en `panic`. I generics tillader den forhåndsdeklarerede `comparable`-begrænsning, at typeparametre sammenlignes med `==`/`!=` og bruges som `map`-nøgler.

Prøv at besvare dette spørgsmål med en AI-coach

3Hvordan repræsenterer Go bytes, runer og UTF-8-kodet tekst, og hvorfor kan `len(s)` afvige fra antallet af brugersynlige tegn?

I Go er `byte` et alias for `uint8` og repræsenterer én rå byte, mens `rune` er et alias for `int32` og repræsenterer et Unicode-kodepunkt (code point). En `string` er en skrivebeskyttet (read-only) sekvens af bytes, typisk UTF-8-kodet tekst, men den er i stand til at indeholde vilkårlige bytes. `len(s)` returnerer antallet af bytes, ikke runer eller brugersynlige tegn. Indeksering af en streng returnerer en byte; iteration over en streng med `range` afkoder UTF-8 og returnerer byteindekser samt runer. `len(s)` kan afvige fra antallet af synlige tegn, fordi UTF-8 kan bruge flere bytes pr. kodepunkt, og fordi et brugersynligt tegn kan bestå af flere kodepunkter, såsom kombinerende diakritiske tegn (combining marks) eller emoji-sekvenser.

Prøv at besvare dette spørgsmål med en AI-coach

4Hvordan adskiller type-aliasser sig fra definerede typer, og hvornår vil du bruge hver af dem?

En defineret type (defined type), som for eksempel `type UserID int64`, opretter en ny, særskilt type med `int64` som dens underliggende type. Den kan ikke frit tildeles til `int64` uden konvertering og kan have sine egne metoder. Et type-alias, som for eksempel `type UserID = int64`, er blot et andet navn for den samme type, så typeidentitet og tildelbarhed bevares. Brug definerede typer til domænemodellering, typesikkerhed og metoder; brug primært aliasser til refaktorering, migrering eller kompatibilitet uden at introducere en ny type.

Prøv at besvare dette spørgsmål med en AI-coach

5Hvordan håndterer Go specielle floating-point-værdier (kommatal), og hvilke faldgruber i forhold til lighed er vigtige i backend-systemer?

Go's `float32` og `float64` bruger IEEE-754-lignende adfærd, hvilket inkluderer specielle værdier som positiv/negativ uendelighed og NaN (Not a Number). For `float64` findes der hjælpefunktioner som `math.Inf`, `math.IsInf`, `math.NaN` og `math.IsNaN`. NaN er ikke lig med noget, ikke engang sig selv, så `x == x` er falsk, når `x` er NaN. Eksakt lighed mellem beregnede kommatal er også risikabelt, fordi afrunding og præcision kan få matematisk ens værdier til at afvige; brug domænetilpassede tolerancer eller undgå kommatal til eksakte forretningsværdier som penge. Kommatal er tilladte som nøgler i en `map`, men NaN-nøgler er problematiske, fordi opslag i en `map` afhænger af lighed, og NaN kan ikke sammenlignes som lig med noget, end ikke sig selv.

Prøv at besvare dette spørgsmål med en AI-coach

6Beskriv indlejring af `struct`-typer (struct embedding) i Go, og hvordan promoverede felter og metoder fungerer.

Indlejring af en `struct` i Go betyder, at man erklærer et felt ved hjælp af dets type uden et eksplicit feltnavn, for eksempel `type User struct { Person }`. Den indlejrede værdi er stadig et reelt felt, som kan tilgås som `u.Person`, men dens eksporterede/tilgængelige felter og metoder kan blive promoveret, så man kan skrive selektorer som `u.Name` eller `u.Greet()` som en genvej frem for at gå gennem det indlejrede felt. Indlejring er komposition, ikke klassisk nedarvning: den ydre type bliver ikke automatisk en undertype af den indlejrede type. Hvis promoverede selektorer er i konflikt, gætter Go ikke; tvetydige navne skal kvalificeres, ellers kan de ikke vælges via den ydre værdi.

Prøv at besvare dette spørgsmål med en AI-coach

Datastrukturer

7Beskriv, hvordan reslicing og tildeling af slices kan få flere slices til at dele det samme underliggende array, og hvilke fejl dette kan skabe.

En slice-værdi er en header, der peger ind i et underliggende array. Tildeling af en slice eller overdragelse af den til en funktion kopierer kun denne header, ikke elementerne. Reslicing skaber en anden header, der peger på et interval i det samme bagvedliggende array. Derfor kan flere slices fungere som aliaser for den samme lagring: ændring af et element via én slice kan være synlig gennem en anden, og en `append` til én slice kan overskrive data, der er synlige for en anden, hvis den stadig har ledig kapacitet. Fejl inkluderer overraskende mutationer, beskadigede resultater, tilbageholdelse af store bagvedliggende arrays via små under-slices og data-races, når aliaser bruges samtidigt. For at undgå utilsigtet deling kan man lave en defensiv kopi med `copy` eller `append([]T(nil), s...)`, eller begrænse kapaciteten med et fuld-slice-udtryk før `append`.

Prøv at besvare dette spørgsmål med en AI-coach

8Forklar på et konceptuelt niveau, hvordan en `slice` vokser under `append`, og hvilke konsekvenser gentagne genallokeringer har for ydeevnen.

Når `append` tilføjer elementer til en `slice`, skriver den til det eksisterende bagvedliggende array ("backing array"), hvis den har tilstrækkelig kapacitet. Hvis kapaciteten er utilstrækkelig, allokerer Go et større bagvedliggende array, kopierer de eksisterende elementer, skriver de nye elementer og returnerer en `slice`-header, der peger på det nye lager. Den præcise vækstpolitik afhænger af implementeringen, men konceptuelt vokser kapaciteten nok til at gøre gentagne kald til `append` amortiseret effektive. Gentagne genallokeringer koster dog stadig CPU-tid til kopiering, skaber nye allokeringer, øger presset på vores GC (Garbage Collector) og kan bryde delingen med gamle `slice`-aliasser. Hvis du kender den forventede størrelse, bør du forhåndsallokere med `make([]T, 0, n)`, når der opbygges med `append`, eller `make([]T, n)`, når der udfyldes via indeks, for at reducere antallet af genallokeringer.

Prøv at besvare dette spørgsmål med en AI-coach

Pakker

9Hvad er init-time registreringsmønstre i Go, og hvilke risici er der ved blank import-bivirkninger og globale registre?

Et init-time registreringsmønster er, når en pakke registrerer en implementering i et delt register fra en `init`-funktion. En blank import som fx `_ "example.com/driver"` bruges ofte til at importere en pakke udelukkende for dens bivirkninger, hvilket får dens `init`-funktion til at køre, selvom der ikke refereres til nogen eksporterede navne. Dette er almindeligt for udvidelsespunkter til drivere, codecs, plugins, metrikker eller serialiseringsværktøjer. Risiciene omfatter skjulte afhængigheder og opstartsbivirkninger, foranderlig global tilstand, duplikeret eller rækkefølgefølsom registrering, sværere testisolering og mindre eksplicit afhængighedshåndtering. Det bør bruges bevidst, dokumenteres tydeligt og kan ofte afhjælpes med eksplicit registrering, idempotente og trådsikre registre, eller injicerbare og nulstilbare registre til tests.

Prøv at besvare dette spørgsmål med en AI-coach

Fejlhåndtering

10Hvordan interagerer `defer`, `panic` og navngivne returværdier ved implementering af oprydning, der kan ændre returnerede fejl?

Funktioner, der er udskudt med `defer`, kører, efter at returværdierne er blevet tildelt, men før funktionen returnerer til kalderen. Af den grund kan en udskudt closure læse eller ændre navngivne returværdier, såsom en navngiven `err`. Dette bruges ofte til at tilføje oprydningsfejl fra `Close`, `Commit` eller lignende operationer til den fejl, der i forvejen returneres, ideelt set så den primære fejl bevares i stedet for at blive overskrevet. Under udrulning af stakken efter en `panic` (panic unwinding) kører `defer`-funktioner stadig; en udskudt funktion kan foretage et `recover` og sætte en navngiven returværdi, men dette bør begrænses til bevidste `panic`-grænser. Vær forsigtig med ikke at skygge (shadow) for en navngiven returvariabel som f.eks. `err`, da `defer`-kaldet i så fald kan ende med at observere eller ændre en helt anden variabel end tilsigtet.

Prøv at besvare dette spørgsmål med en AI-coach

11Hvordan fungerer `errors.Is`, `errors.As` og `%w` i Go's kæder af indpakkede fejl (error wrapping chains)?

`fmt.Errorf` med `%w` opretter en ny fejl, der indpakker en underliggende fejl, samtidig med at der tilføjes kontekst. Indpakninger (wrappers) eksponerer underliggende fejl gennem `Unwrap`, hvilket danner en kæde eller et træ, som standardbiblioteket kan inspicere. `errors.Is(err, target)` tjekker, om `err` eller noget den indpakker, matcher en målfejl. `errors.As(err, &target)` tjekker, om `err` eller noget den indpakker, kan tildeles til måltypen, og gemmer den matchede værdi i den medfølgende pointer.

Prøv at besvare dette spørgsmål med en AI-coach

12Hvad er sentinel-fejl (sentinel errors), og hvad er afvejningerne sammenlignet med tilpassede typiserede fejl eller mere omfattende domænefejlmodeller?

En sentinel-fejl er en navngiven fejlværdi, ofte en variabel på pakkeniveau som `var ErrNotFound = errors.New("not found")`, der bruges til at repræsentere en specifik tilstand, som kaldere kan teste for, typisk med `errors.Is`, når fejlindpakning (error wrapping) er mulig. Sentinel-fejl er enkle og nyttige til brede, stabile kategorier, men eksporterede sentinel-fejl bliver en del af dit API og kan binde kaldere tæt til bestemte værdier. Tilpassede typiserede fejl kan indeholde strukturerede felter og findes via `errors.As`. Mere omfattende domænefejlmodeller klassificerer fejl efter type/kode og kan indeholde sikre meddelelser eller metadata, hvilket er nyttigt, når kaldere har brug for en stabil adfærd, der går ud over én fast fejlværdi.

Prøv at besvare dette spørgsmål med en AI-coach

13Hvordan bør en Go-backend oversætte interne fejl til brugbare klientsvar, samtidig med at systemoperatører får diagnosticerbare signaler?

En Go-backend bør oversætte interne fejl ved en applikations- eller netværksgrænse til stabile, klientsikre kategorier og svar. Disse kategorier bør afbildes til passende HTTP-statuskoder (Hypertext Transfer Protocol) eller tilsvarende netværksstatusser med sikre meddelelser og maskinlæsbare koder frem for rå interne fejl. Systemoperatører bør stadig modtage diagnostisk information gennem strukturerede logfiler, sporing (traces), metrikker, korrelations-/forespørgsels-id'er og bevarede underliggende årsager. Logning udføres normalt bedst én gang ved en grænse, der har kontekst for forespørgslen, for at undgå både oversete fejl og støj fra duplikerede logfiler.

Prøv at besvare dette spørgsmål med en AI-coach

14Hvordan implementerer man tilpassede fejltyper korrekt i Go, og hvordan påvirker errors.Join inspektion og fejlhåndtering i oprydningsfaser?

En tilpasset Go-fejltype opfylder `error`-grænsefladen ved at implementere `Error() string`. Den kan også indeholde strukturerede felter og implementere `Unwrap() error` for at afsløre en underliggende årsag. Det har betydning, om der anvendes en pointer receiver eller en value receiver: en pointer receiver betyder, at kun `*T` opfylder `error`, mens en value receiver normalt betyder, at både `T` og `*T` gør det. Dette påvirker kopiering, og hvilken type kaldere bør bruge med `errors.As`. `errors.Join` kombinerer flere fejl til én fejl; `errors.Is` og `errors.As` inspicerer de sammensluttede underfejl. Dette er nyttigt, når både en primær handlingsfejl og en oprydnings- eller udskudt fejl skal returneres, uden at man mister information om nogen af fejlene.

Prøv at besvare dette spørgsmål med en AI-coach

Samtidighed

15Hvordan fungerer `select` med kanaler, herunder valg af klar-tilfælde (ready-cases), standardtilfælde (default cases) og annullerbare operationer?

`select` venter på flere kanaloperationer og kører det tilfælde (case), hvor der kan sendes eller modtages. Hvis intet kanaltilfælde er klar, blokerer det, medmindre der er en `default`-case; `default` køres øjeblikkeligt, når ingen kanaloperationer kan fortsætte, hvilket er nyttigt til ikke-blokerende forsøg på at sende/modtage. Hvis flere cases er klar, vælger Go en pseudotilfældigt frem for efter kildekodens rækkefølge. Annullerbare kanaloperationer tilføjer typisk en case, der modtager fra `ctx.Done()`, så gorutinen kan stoppe med at vente, når konteksten (context) bliver annulleret eller tidsudløber.

Prøv at besvare dette spørgsmål med en AI-coach