Valmistautuminen Junior Go -haastatteluun

Junior Go-backend-kehittäjän haastattelukysymykset

15 valikoitua Junior-tason Go-haastattelukysymystä backend-kehittäjille, joiden tulee osata selittää kielen perusteet selkeästi ja varmasti.

Aloita tekoälypohjainen Junior Go -haastatteluEi vaadi luottokorttia. 1 ilmainen sessio saatavilla.
Teknisten haastattelujen harjoittelu englanniksiTila, jossa ei-äidinkieliset puhujat voivat harjoitella teknisen haastattelun läpäisemistä.

Tyyppijärjestelmä

1Selitä, miten Go-kielen nolla-arvot toimivat sisäänrakennetuille ja viittauksen kaltaisille tyypeille, ja miksi niillä on merkitystä, kun muuttujia esitellään ilman eksplisiittistä alustusta.

Go-kielessä muuttuja, joka on esitelty ilman eksplisiittistä alustusta, alustetaan automaattisesti tyyppinsä nolla-arvoon. Numeerisista tyypeistä tulee `0`, `bool`-tyypistä `false`, `string`-tyypistä `""`, ja taulukot (arrays) tai tietueet (`struct`) nollataan alkio alkiolta tai kenttä kentältä. Osoittimien tai viittausten kaltaisilla tyypeillä, kuten osoittimilla, `slice`- ja `map`-tyypeillä, kanavilla, funktioilla ja rajapinnoilla, on `nil` nolla-arvonaan. Tällä on merkitystä, koska Go-muuttujat ja pois jätetyt `struct`-kentät aloittavat deterministisessä tilassa roskadatan sijaan. Lisäksi monet API-rajapinnat on suunniteltu siten, että nolla-arvo on hyödyllinen oletusarvo, vaikka jotkin `nil`-arvot vaativat yhä alustuksen ennen tiettyjä toimenpiteitä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

2Miten Go käsittelee struct-tyyppien yhtäsuuruutta, ja mitä tapahtuu, kun struct sisältää kenttiä, jotka eivät ole vertailtavia?

Gossa `struct`-arvoja voidaan vertailla operaattoreilla `==` ja `!=` vain silloin, kun rakenteen jokainen kenttä on vertailtava. Yhtäsuuruus vertailee vastaavia kenttiä käyttäen kunkin kentän omaa vertailusääntöä. Jos `struct` sisältää kentän, joka ei ole vertailtava (kuten `slice`, `map` tai funktio), koko `struct`-tyyppi ei ole vertailtava, ja kahden kyseisen tyyppisen arvon vertaileminen `==`-operaattorilla aiheuttaa käännösaikaisen virheen. Tällaisten `struct`-tyyppien kohdalla on käytettävä mukautettua vertailulogiikkaa tai sopivaa syvävertailun (deep equality) apufunktiota, erityisesti testeissä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

3Selitä merkkijonojen muuttumattomuus (string immutability) Go-kielessä ja suhde string-, []byte-, bytes.Buffer- ja strings.Builder-tyyppien välillä.

Go-kielen `string` on muuttumaton tavujono, joka on usein UTF-8-muotoista tekstiä, mutta sen ei ole pakko olla validia UTF-8:aa. Merkkijonoa ei voi muokata paikallaan; sen sisällön muuttamiseksi se tyypillisesti muunnetaan `[]byte`-tyypiksi tavutason muokkauksia varten tai `[]rune`-tyypiksi koodipisteiden (code point) muokkauksia varten, ja sen jälkeen takaisin merkkijonoksi. Normaalit muunnokset `string`- ja `[]byte`-tyyppien välillä kopioivat dataa ja saattavat varata muistia, joten toistuvat muunnokset tai toistuva merkkijonojen yhdistäminen silmukoissa voivat olla raskaita. `strings.Builder` on optimoitu merkkijonojen tehokkaaseen rakentamiseen, kun taas `bytes.Buffer` on muokattava tavupuskuri, joka on hyödyllinen tavupohjaiselle datalle sekä I/O-operaatioille, ja josta voi myös tuottaa merkkijonon.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

4Miten osoittimet toimivat Go-kielessä, ja mitkä operaatiot on tarkoituksellisesti kielletty verrattuna C-kieleen?

Go:n osoitin on tyypitetty arvo, joka sisältää toisen arvon osoitteen, esimerkiksi `*int`, joka osoittaa `int`-arvoon. Käytä merkintää `&x` arvon osoitteen hakemiseen ja `*p` osoittimen purkamiseen (dereference), kun haluat lukea tai kirjoittaa osoitettua arvoa. Osoittimen välittäminen tai tallentaminen mahdollistaa saman taustalla olevan arvon havainnoinnin tai muokkaamisen useasta paikasta käsin, ja osoitin voi olla `nil`. Toisin kuin C-kielessä, turvallinen Go kieltää tarkoituksellisesti osoitinaritmetiikan ja mielivaltaisen raakaosoitteiden käsittelyn; matalan tason poikkeustapaukset vaativat `unsafe`-paketin käyttöä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

5Selitä, miten vakiot ja `iota` toimivat Gossa, mukaan lukien tyypitetyt ja tyypittämättömät vakiot.

Gon vakiot ovat käännösaikaisia arvoja: totuusarvoja, merkkijonoja tai numeerisia arvoja. Tyypitetyllä vakiolla on tietty tyyppi. Tyypittämättömällä vakiolla ei ole kiinteää konkreettista tyyppiä ennen kuin sitä käytetään tyypitetyssä kontekstissa, ja numeeriset tyypittämättömät vakiot esitetään siihen asti täydellä tarkkuudella. Arvon on oltava esitettävissä valitussa tyypissä. `iota` on ennalta määritelty tunniste, jota käytetään `const`-määrittelyissä: se alkaa nollasta jokaisessa `const`-lohkossa ja kasvaa jokaisen vakiomäärittelyn myötä, mikä tekee siitä hyödyllisen enum-tyyppisille vakioille ja bittilipuille.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

6Miten eksplisiittiset tyyppimuunnokset toimivat Gossa, ja miksi kieli välttää monia implisiittisiä muunnoksia?

Go vaatii yleensä eksplisiittisiä muunnoksia käyttäen syntaksia `T(x)` arvon tyyppiä muutettaessa, esimerkiksi `int64(i)` tai `MyID(n)`. Tämä pitää tyyppimuutokset näkyvinä ja välttää yllättävät implisiittiset numeeriset, totuusarvojen tai merkkijonojen muunnokset. Muunnokset ovat sallittuja vain Gon muunnossääntöjen mukaisesti, ja ne voivat muuttaa arvoa esimerkiksi kokonaisluvun ylivuodon (overflow) tai katkaisemisen (truncation) takia, tai johtuen liukuluvun muuttamisesta kokonaisluvuksi nollaa kohti katkaisten tai tarkkuuden menetyksestä. Tyypittömät vakiot (untyped constants) ovat joustavampia: niitä voidaan käyttää tyypitetyssä kontekstissa, jos vakion arvo on esitettävissä kyseisessä tyypissä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Tietorakenteet

7Kuvaile eroa Go-kielen taulukoiden ja `slice`-tyyppien välillä, mukaan lukien miten pituus, kapasiteetti ja taustalla oleva tallennustila käyttäytyvät.

Go-kielen taulukoilla on kiinteä pituus, joka on osa sen tyyppiä, kuten `[3]int`. Se tallentaa alkionsa suoraan, ja taulukon sijoittaminen muuttujaan tai välittäminen funktiolle kopioi koko taulukon arvon. `slice`, kuten `[]int`, on pieni kuvaaja taustalla olevasta taulukosta: käsitteellisesti se sisältää osoittimen alkioihin, pituuden ja kapasiteetin. `slice`-tyypin pituus on näkyvillä olevien alkioiden määrä; sen kapasiteetti määrittää, kuinka monta alkiota voidaan käyttää `slice`-alkukohdasta taustataulukon loppuun mennessä. `slice`-tyypit ovat joustavia: arvon viipalointi uudelleen (reslicing) muuttaa kuvaajaa, ja `append` voi käyttää uudelleen samaa taustataulukkoa, jos kapasiteetti sallii, tai varata uuden, jos se ei riitä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

8Kuinka Go:n `map`-tyyppi käyttäytyy suhteessa avaintyyppeihin, puuttuviin avaimiin, `nil`-arvoisiin `map`-rakenteisiin ja iteroinnin järjestykseen?

Go:n `map`-rakenteen avaintyyppien on oltava vertailukelpoisia (comparable); `slice`-, `map`- ja funktiotyyppejä ei voi käyttää suoraan avaimina. Puuttuvan avaimen hakeminen palauttaa alkiotyypin nolla-arvon, joten puuttuminen ja olemassa oleva nolla-arvo erotetaan toisistaan `comma-ok`-idiomilla (`v, ok := m[k]`). `nil`-arvoista `map`-rakennetta voi lukea ja sen voi käydä läpi `range`-rakenteella, mutta siihen sijoittaminen aiheuttaa paniikin (`panic`); se on siis alustettava ennen kirjoittamista. `map`-rakenteen iterointijärjestystä ei ole määritelty, eikä koodin tulisi luottaa siihen.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Muistinhallinta

9Milloin Go:ssa `struct` tai muu arvo pitäisi välittää parametrina arvona (by value) ja milloin osoittimena (by pointer)?

Välitä parametrina arvona, kun arvo on pieni, se on vain luettavana kyseisessä kutsussa tai kun haluat itsenäisen kopion semantiikan. Välitä osoittimena, kun funktion täytyy muuttaa kutsujan arvoa, kopiointi olisi liian kallista suorituskyvyn kannalta, tyyppiä ei tulisi kopioida lainkaan tai jos nolla-arvo (`nil`) tai jaettu identiteetti on osa API-rajapintaa. Älä oleta automaattisesti, että osoittimet ovat aina nopeampia: ne voivat lisätä aliasointia, keosta varaamista (heap escapes), GC:n (Garbage Collector) työmäärää ja heikentää välimuistin käyttötehokkuutta. Suosi ensisijaisesti selkeää semantiikkaa ja tee suorituskykymittaus suorituskyvyn kannalta kriittisille valinnoille.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Kielen semantiikka

10Miten sijoitus ja kopiointi toimivat Go-kielen `struct`-, taulukko- (array), `map`- ja `slice`-tyypeillä?

Sijoitus Go-kielessä kopioi sijoitettavan arvon, mutta vaikutus riippuu tyypistä. `struct`-tyypin sijoitus kopioi tietueen kentät, ja taulukon sijoitus kopioi jokaisen alkion. `slice`-tyypin sijoitus kopioi otsikkotiedot – osoittimen, pituuden ja kapasiteetin – joten `slice`-arvot jakavat yleensä saman taustataulukon. `map`-tyypin sijoitus kopioi viittauksen kaltaisen kuvaajan, joten molemmat muuttujat viittaavat samaan `map`-dataan. Riippumatonta omistajuutta varten on tehtävä puolustava kopio, kuten käyttämällä `copy`-funktiota tai `slices.Clone`-funktiota `slice`-arvoille, ja luomalla uusi `map` sekä kopioimalla alkiot `map`-arvoille.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

11Miten sulkeumat sieppaavat muuttujia Go-kielessä, ja mitä bugeja voi ilmetä silmukoiden ja goroutinejen yhteydessä?

Sulkeuma on Go-kielessä funktioarvo, joka viittaa sitä ympäröivän leksikaalisen näkyvyysalueen muuttujiin. Se sieppaa itse muuttujat, joten sulkeuma pystyy havaitsemaan niihin tehdyt muutokset, ja siepatut muuttujat voivat elää kauemmin kuin ne luonut funktio, jos sulkeuma karkaa näkyvyysalueensa ulkopuolelle. Silmukoihin ja goroutineihin liittyviä bugeja ilmenee, kun sulkeumat jakavat tai tarkastelevat muuttujaa sen muuttumisen jälkeen. Tämä johtaa usein siihen, että goroutinet tai viivästetyt takaisinkutsut näkevät väärän arvon tai aiheuttavat datakilpailuja jaetussa tilassa. Yleinen korjaus on välittää haluttu arvo argumenttina sulkeumalle tai luoda uusi paikallinen kopio jokaisella iteraatiolla, sekä synkronoida samanaikainen pääsy tarvittaessa. Go 1.22 -versiosta lähtien `for`- ja `range`-silmukoiden esittelemät muuttujat ovat iteraatiokohtaisia. Tämä poistaa monia klassisia silmukkamuuttujien sieppaamiseen liittyviä bugeja, mutta silmukan ulkopuolella uudelleenkäytetyt muuttujat ja muu jaettu muuttuva tila voivat silti aiheuttaa ongelmia.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

12Kuvaile `range`-avainsanan semantiikka `array`-, `slice`-, `map`-, `string`- ja kanavatyyppien (channel) kanssa.

`range` käy läpi alkioita operandin tyypin mukaisesti. `array`- tai `slice`-tyypin kanssa se palauttaa indeksin ja alkion arvon. Alkion arvo on kopio, joten siihen sijoittaminen ei muokkaa alkuperäistä kokoelmaa. `array`-arvon läpikäynti kopioi taulukon iteraatiota varten, kun taas `slice`-arvon läpikäynti käyttää `slice`:n otsikkoa (slice header) ja indeksoi sen alla olevaa taulukkoa. `map`-tyypin kohdalla se palauttaa avaimen ja arvon määrittelemättömässä järjestyksessä, ja palautettu arvo on kopio. `string`-tyypin kanssa se palauttaa tavuindeksin sekä dekoodatun Unicode-koodipisteen (`rune`), ei `rune`-merkin sijainti-indeksiä. Kanavan kanssa se vastaanottaa arvoja, kunnes kanava suljetaan ja tyhjennetään. `nil`-kanavan läpikäynti jää odottamaan ikuisesti.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Metodit

13Mitä eroa on arvo- ja osoitinvastaanottajilla, ja miten vastaanottajan valinta vaikuttaa muutettavuuteen, kopiointiin ja rajapintojen toteuttamiseen?

Arvovastaanottajametodi saa kopion vastaanottajasta, joten se sopii vain luku -tyyppiseen toimintaan ja pienille, muuttumattomille tyypeille; muutokset vastaanottajan kopioon eivät näy kutsujalle. Osoitinvastaanottajametodi saa kopion vastaanottajaan osoittavasta osoittimesta, joten se voi muuttaa alkuperäistä arvoa ja välttää suurten tai sellaisten arvojen kopioinnin, joita ei pitäisi kopioida. Vastaanottajan valinta vaikuttaa metodijoukkoihin (`method set`): arvovastaanottajalliset metodit kuuluvat sekä `T`:n että `*T`:n metodijoukkoon, kun taas osoitinvastaanottajalliset metodit kuuluvat vain `*T`:n metodijoukkoon. Siksi rajapinnan, joka vaatii osoitinvastaanottajametodia, toteuttaa `*T`, ei `T`, vaikka osoitteen sisältävä `T`-arvo voikin kutsua metodia käyttämällä tavallista metodikutsun syntaksia.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Rajapinnat

14Kuvaile, miten rajapinnat toimivat Go-kielessä ja mitä tarkoittaa, että tyyppi toteuttaa rajapinnan implisiittisesti.

Go-kielen rajapinta määrittelee joukon vaadittuja metodeja. Konkreettinen tyyppi toteuttaa rajapinnan implisiittisesti, kun sen `method set` sisältää kyseiset metodit; erillistä toteutusmäärittelyä ei ole. Rajapintamuuttuja voi pitää sisällään konkreettisen dynaamisen arvon, jonka tyyppi toteuttaa rajapinnan, ja rajapinnan metodikutsut ohjataan kyseisen dynaamisen arvon toteutukseen. Tyhjä rajapinta, joka kirjoitetaan `interface{}` tai yleensä `any`, ei sisällä vaadittuja metodeja, joten kaikki tyypit toteuttavat sen. Se, toteuttaako tyyppi `T`, `*T` vai molemmat tietyn rajapinnan, riippuu niiden `method set` -määritelmistä ja vastaanottimen (receiver) valinnoista.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

15Miten tyyppiväitteet (type assertions) ja tyyppikytkimet (type switches) toimivat rajapinta-arvojen kanssa?

Tyyppiväite tarkistaa rajapintaan tallennetun dynaamisen arvon: `v := x.(T)` onnistuu, jos rajapinta-arvon dynaaminen tyyppi on `T`, tai jos `T` on rajapinta, jonka dynaaminen arvo toteuttaa. Yhden tuloksen muoto aiheuttaa paniikin (`panic`), jos se epäonnistuu. `comma-ok`-muoto, `v, ok := x.(T)`, ilmoittaa onnistumisesta panikoimatta. Tyyppikytkin, `switch v := x.(type)`, haarautuu rajapinta-arvon dynaamisen tyypin perusteella, jolloin `v` saa tyyppinsä täsmäävän tapauksen mukaan.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa