Forberedelse til Go-interview

Interviewspørgsmål til Go Backend-udviklere

Udvalgte Go-interviewspørgsmål til backend-udviklere, grupperet efter emne og hentet fra det samme spørgsmålskatalog, der driver EngineerSpeaks øvelser.

Start et AI-interview om Go BackendIntet 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 Gos nulværdier fungerer for indbyggede og reference-lignende typer, og hvorfor de er vigtige, når man erklærer variabler uden eksplicit initialisering.

I Go vil en variabel, der er erklæret uden en eksplicit initialiseringsværdi, automatisk blive initialiseret til nulværdien for dens type. Numeriske typer bliver `0`, `bool` bliver `false`, `string` bliver `""`, og arrays eller `struct`-typer nulstilles element for element eller felt for felt. Pointer-lignende eller reference-lignende typer såsom pointere, `slice`-værdier, `map`-typer, `channel`-typer, funktioner og interfaces har `nil` som deres nulværdi. Dette er vigtigt, fordi Go-variabler og udeladte `struct`-felter starter i en deterministisk tilstand i stedet for at indeholde vilkårlige hukommelsesdata, og mange API'er (Application Programming Interfaces) er designet, så nulværdien er en brugbar standardværdi, selvom visse `nil`-værdier stadig kræver initialisering før bestemte operationer.

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

2Hvordan håndterer Go lighed for `struct`-typer, og hvad sker der, når en `struct` indeholder felter, der ikke kan sammenlignes?

Værdier af en `struct` i Go kan kun sammenlignes med `==` og `!=`, når hvert eneste felt i den pågældende `struct` er sammenligneligt. Lighed sammenligner de tilsvarende felter ved hjælp af hvert felts egen regel for lighed. Hvis en `struct` indeholder et felt, der ikke er sammenligneligt, såsom en `slice`, `map` eller funktion, er selve `struct`-typen ikke sammenlignelig, og en sammenligning af to værdier af denne `struct`-type med `==` vil resultere i en kompilatorfejl. For sådanne `struct`-typer bør man bruge tilpasset sammenligningslogik eller en passende hjælpefunktion til dyb sammenligning (deep equality), især i test.

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

3Forklar uforanderlighed for strenge (string immutability) i Go samt relationen mellem `string`, `[]byte`, `bytes.Buffer` og `strings.Builder`.

En Go-`string` er en uforanderlig sekvens af bytes, ofte UTF-8-tekst, men den behøver ikke at være gyldig UTF-8. Du kan ikke ændre en streng på stedet (in place); for at ændre indholdet konverterer du typisk til `[]byte` for redigeringer på byteniveau, eller til `[]rune` for redigeringer på kodepunktsniveau (code points), hvorefter du konverterer tilbage. Almindelige konverteringer mellem `string` og `[]byte` kopierer data og kan allokere hukommelse, så gentagne konverteringer eller gentagen sammensætning af strenge i løkker kan være omkostningstungt. `strings.Builder` er optimeret til effektivt at opbygge strenge, mens `bytes.Buffer` er en foranderlig byte-buffer, der er velegnet til byteorienterede data og I/O, og som også kan producere en streng.

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

4Forklar, 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

5Hvad 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

6Hvordan 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

Datastrukturer

7Beskriv forskellen mellem arrays og slices i Go, herunder hvordan længde, kapacitet og den underliggende lagring fungerer.

Et array i Go har en fast længde, som er en del af dets type, f.eks. `[3]int`; det gemmer sine elementer direkte, og hvis man tildeler eller videregiver et array, kopieres hele array-værdien. En slice, f.eks. `[]int`, er en lille deskriptor over et underliggende array: konceptuelt indeholder den en pointer til elementer, en længde og en kapacitet. Længden på en slice er antallet af synlige elementer; dens kapacitet er, hvor mange elementer der kan bruges fra startpunktet af en slice, før slutningen af det bagvedliggende array nås. Slices er fleksible: reslicing ændrer deskriptoren, og `append` kan genbruge det samme underliggende array, hvis kapaciteten tillader det, eller allokere et nyt, hvis ikke.

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

8Hvordan opfører Gos `map`-type sig med hensyn til nøgletyper, manglende nøgler, `nil`-maps og iterationsrækkefølge?

Nøgletyper i et Go `map` skal være sammenlignelige; `slice`-værdier, `map`-typer og funktioner kan ikke bruges direkte som nøgler. Hvis man slår en nøgle op, der ikke er til stede, returneres elementtypens nulværdi, så comma-ok-formen (`v, ok := m[k]`) bruges til at skelne mellem manglende tilstedeværelse og en tilstedeværende nulværdi. Et `nil` `map` kan læses fra og itereres over med `range`, men tildeling til det forårsager en panic; det skal initialiseres før skrivning. Iterationsrækkefølgen i et `map` er uspecificeret, og koden må ikke afhænge af den.

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

9Beskriv, 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

10Forklar 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

Sprogsemantik

11Hvordan fungerer den blanke identifikator (the blank identifier) i Go til ubrugte værdier, imports og grænsefladetjek på kompileringstidspunktet?

Den blanke identifikator `_` er en pladsholder, der kun kan skrives til. Tildeling til den kasserer værdien og opretter ikke en brugbar variabel. Den bruges til at ignorere unødvendige returværdier eller løkkevariabler, til at importere en pakke udelukkende for dens bivirkninger med `import _ "pkg"`, og til at udføre tjek af grænsefladeimplementeringer på kompileringstidspunktet, såsom `var _ io.Reader = (*MyReader)(nil)`. En blank import kører stadig den importerede pakkes initialisering. En tildeling til grænsefladetjek vil fejle under kompilering, hvis den konkrete types metodesæt (method set) ikke opfylder grænsefladen.

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

Pakker

12Hvordan fungerer rækkefølgen for initialisering af pakker i Go, herunder `init`-funktioner og importerede afhængigheder?

Go initialiserer pakker i den rækkefølge, de er afhængige af hinanden. En pakkes importerede afhængigheder initialiseres før den pakke, der importerer dem. I en pakke initialiseres variabler på pakkeniveau før eventuelle `init`-funktioner, hvor variabelinitialiseringen er ordnet efter afhængigheds- og deklarationsrækkefølge som defineret af sproget. Derefter køres pakkens `init`-funktioner automatisk; en pakke kan have flere `init`-funktioner, og de kan ikke kaldes direkte. Hver pakke initialiseres kun én gang. For en eksekverbar fil initialiseres importgrafen først, hvorefter pakken `main` initialiseres, og til sidst kaldes `main.main`.

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

13Forklar reglerne for pakkers synlighed i Go, herunder eksporterede identifikatorer og konventionen for internal/-mapper.

I Go styres pakkesynlighed af identifikatorers navngivning, ikke af adgangsnøgleord (access modifiers). En identifikator, hvis navn starter med et stort Unicode-bogstav, er eksporteret og kan refereres til fra andre pakker; andre identifikatorer er ikke-eksporterede og kan kun bruges inden for samme pakke. Dette gælder for funktioner, typer, metoder, variabler, konstanter og struct-felter. Pakker bruger eksporterede identifikatorer til at definere deres offentlige API og holder implementeringsdetaljer skjulte (ikke-eksporterede). Separat gælder det, at en pakke placeret under en internal/-mappe kun kan importeres af kode, hvis importsti ligger inden for den overordnede træstruktur af denne internal-mappe; dette håndhæves af Gos toolchain.

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

Kontrolflow

14Hvordan fungerer `defer` i Go, herunder udførelsesrækkefølge, tidspunkt for evaluering af argumenter og interaktion med returværdier?

`defer` planlægger et funktionskald til at blive udført, når den omgivende funktion afsluttes, uanset om den afsluttes ved et normalt returkald eller ved afvikling af en panic (unwinding). Flere udskudte kald udføres i 'last-in, first-out'-rækkefølge (LIFO). Den udskudte funktionsværdi og dens argumenter evalueres med det samme, når `defer`-sætningen udføres, men selve kaldet kører senere. Ved navngivne returværdier tildeler en `return`-sætning først returværdierne, hvorefter de udskudte funktioner kører, så en udskudt closure kan observere eller ændre navngivne resultatvariabler, før kalderen modtager dem. Dette gør `defer` nyttig til oprydning, såsom at lukke filer, låse mutexer op og frigive ressourcer.

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

Fejlhåndtering

15Forklar Gos model for fejlhåndtering samt de konventionelle måder, hvorpå fejl oprettes, returneres og kontrolleres.

Go behandler fejl som almindelige værdier og ikke som undtagelser (exceptions). Det indbyggede `error`-interface opfyldes af enhver type, der har en `Error() string`-metode. Funktioner returnerer konventionelt en `error` som det sidste resultat, hvor `nil` betyder succes, og en fejl, der ikke er `nil`, betyder, at kalderen skal håndtere eller videresende fejlen. Simple fejl oprettes normalt med `errors.New`, formaterede fejl oprettes med `fmt.Errorf`, og kaldere kontrollerer typisk fejlen med `if err != nil { ... }`.

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

16Hvordan bør håndtering af `panic` gøres i Go-backend-tjenester, herunder hvad der sker, når en `goroutine` går i `panic`, og hvornår en proces bør gendannes (recover) frem for at krashe?

En `panic` afvikler den aktuelle `goroutine`, hvilket kører dens udskudte (deferred) funktioner. `recover` virker kun, når den kaldes fra en udskudt funktion i den samme `goroutine`; en `goroutine` kan ikke opfange en andens `panic`. Hvis en `panic` ikke opfanges, krasher processen. I backend-tjenester bør gendannelse (recovery) normalt placeres ved isolationsgrænser, såsom i forespørgselshåndterere, RPC-middleware (Remote Procedure Call) eller ved startpunkter for en worker-`goroutine`, så en enkelt fejlet forespørgsel eller opgave ikke får hele tjenesten til at gå ned. Men hvis en `panic` kan have korrumperet en delt tilstand eller gjort processens integritet utroværdig, er det mere sikkert at lade processen krashe og genstarte, frem for at opfange fejlen og fortsætte i blinde.

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

Samtidighed

17Hvad er nil-kanaler i Go, og hvordan kan de ved et uheld ødelægge kode eller bevidst deaktivere select-cases?

En nil-kanal er en kanalvariabel, hvis værdi er nil, ofte fordi den ikke blev initialiseret med make eller eksplicit blev sat til nil. At sende til eller modtage fra en nil-kanal blokerer for evigt. I en select er en case, der involverer en nil-kanal, aldrig klar, så at tildele en kanalvariabel til nil kan bevidst deaktivere den pågældende case. Utilsigtet brug af en nil-kanal kan få goroutines til at hænge eller få select-logik til at stoppe med at håndtere forventede hændelser.

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

18Hvordan adskiller atomare operationer i sync/atomic sig fra mutex-baseret synkronisering, og hvornår er de passende at bruge?

sync/atomic tilbyder udelelige operationer på enkelte hukommelseslokationer, såsom load, store, add, swap og compare-and-swap, med garantier for synkronisering og hukommelsesordning. En mutex beskytter en kritisk sektion, så den kan vogte over vilkårlig kode og invarianter, der involverer flere læsninger, skrivninger eller felter. Atomare operationer er velegnede til en simpel, uafhængig tilstand såsom tællere, flag, sekvensnumre eller nøje designede låsefri datastrukturer. Vælg en mutex, når operationer er sammensatte, flere værdier skal forblive konsistente, eller når den atomare version ville være svær at gennemskue eller bevise er korrekt.

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

19Hvordan bør ejerskab af kanaler (channels) og levetiden for en `goroutine` designes for at undgå lækager af `goroutine`-instanser?

Design hver `goroutine` med en eksplicit ejer, et tydeligt nedlukningssignal og en garanteret afslutningsvej. Producentsiden ejer generelt lukningen af en kanal, især en outputkanal; modtagere bør ikke lukke en kanal, mens afsendere muligvis stadig er aktive. Enhver blokerende afsendelse, modtagelse, løkke, timer eller eksternt kald skal enten være garanteret at fuldføre eller være i stand til at ophæve blokeringen ved annullering, oftest gennem `context.Context` eller en done-kanal. Brug `WaitGroup`, `errgroup` eller lignende koordinering, så der ventes på workerne, og kanaler først lukkes, efter at afsendere er afsluttet.

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

20Hvad er de typiske årsager til goroutine-lækager i Go-tjenester, og hvordan opdager og løser du dem i produktion?

Typiske goroutine-lækager i Go-tjenester opstår, når goroutines blokeres permanent ved afsendelse eller modtagelse på en `channel`, venter på andre blokerende operationer uden mulighed for annullering (cancellation), sidder fast i I/O (Input/Output) uden deadlines, kører baggrundsløkker eller `ticker`-objekter, der aldrig stopper, eller når request-afgrænsede goroutines lever længere end selve forespørgslen. I produktion bør man holde øje med en vedvarende stigning i antallet af goroutines og relaterede symptomer, og derefter undersøge goroutine-dumps eller `pprof`-goroutine-profiler for at se, hvor goroutines sidder fast. At løse lækagen indebærer at ændre koden, så disse goroutines kan afsluttes: Tilføj annullering og deadlines, stop `ticker`-objekter, luk channels korrekt, undgå fritstående request-goroutines og begræns samtidigheden, hvor det er nødvendigt.

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