Valmistautuminen Go-haastatteluun

Go-backend-kehittäjän haastattelukysymykset

Valikoituja Go-haastattelukysymyksiä backend-kehittäjille aihepiireittäin ryhmiteltynä. Kysymykset on poimittu samasta tietokannasta, joka toimii EngineerSpeak-harjoittelun ytimenä.

Aloita tekoälypohjainen Go-backend-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

4Selitä, miten `nil` toimii eri tavoin osoittimien, `slice`- ja `map`-tyyppien, kanavien, funktioiden ja rajapintojen kohdalla Go-kielessä.

Go-kielessä `nil` on nolla-arvo osoittimille, `slice`- ja `map`-tyypeille, kanaville, funktioille ja rajapinnoille, mutta näillä `nil`-arvoilla tehtävät operaatiot vaihtelevat tyypin mukaan. `nil`-osoitinta voidaan verrata `nil`-arvoon, mutta sen purkaminen (dereferencing) aiheuttaa paniikin (`panic`). `nil`-arvoisen `slice`:n pituus ja kapasiteetti on 0, ja sen yli voidaan iteroida `range`-rakenteella ja siihen voidaan lisätä alkioita `append`-funktiolla. `nil`-arvoista `map`:ia voidaan lukea ja sen yli voidaan iteroida, mutta siihen sijoittaminen aiheuttaa paniikin. `nil`-kanavalle lähettäminen tai sieltä vastaanottaminen estää suorituksen ikuisesti, ja `nil`-kanavan sulkeminen aiheuttaa paniikin. `nil`-funktion kutsuminen aiheuttaa paniikin. Rajapinta on `nil` vain silloin, kun sillä ei ole dynaamista tyyppiä eikä dynaamista arvoa; tyypitetyn `nil`-arvon, kuten `nil`-osoittimen, sisältävä rajapinta ei itse ole `nil`.

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

5Mitä ovat vertailtavat tyypit (comparable types) Gossa, ja miten vertailtavuussäännöt vaikuttavat map-rakenteiden avaimiin, yhtäsuuruuteen ja generics-rajoitteisiin?

Vertailtavat tyypit Gossa ovat tyyppejä, joiden arvoja voidaan vertailla operaattoreilla `==` ja `!=`. Perustyypit, osoittimet, kanavat (channels), rajapinnat (interfaces) sekä sellaiset `struct`- ja taulukkotyypit, joiden kentät tai alkiot ovat vertailtavia, ovat vertailtavia tyyppejä. `slice`-, `map`- ja funktiotyypit eivät ole vertailtavia, paitsi arvoon `nil`. `map`-rakenteen avainten on oltava vertailtavia. Yhtäsuuruus noudattaa tyypin omia vertailusääntöjä, ja rajapintojen vertailu riippuu dynaamisista konkreettisista arvoista; jos vertailtava rajapinta sisältää dynaamisen arvon, joka ei ole vertailtava, vertailu aiheuttaa paniikin (panic). Generics-tyyppijärjestelmässä ennalta määritelty `comparable`-rajoite sallii tyyppiparametrien vertailun `==`/`!=` -operaattoreilla ja niiden käytön `map`-avaimina.

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

6Miten Go esittää tavuja, rune-merkkejä ja UTF-8-koodattua tekstiä, ja miksi len(s) voi poiketa käyttäjälle näkyvien merkkien määrästä?

Go-kielessä `byte` on alias `uint8`-tyypille ja se edustaa yhtä raakatavua, kun taas `rune` on alias `int32`-tyypille ja edustaa yhtä Unicode-koodipistettä. `string` on vain luku -tyyppinen tavujono, joka on yleensä UTF-8-koodattua tekstiä, mutta voi sisältää mielivaltaisia tavuja. `len(s)` palauttaa tavujen määrän, ei `rune`-merkkien tai käyttäjälle näkyvien merkkien määrää. Merkkijonon indeksointi palauttaa tavun; `range`-rakenteen käyttö merkkijonon yli purkaa UTF-8-koodauksen ja tuottaa tavuindeksien lisäksi `rune`-merkkejä. `len(s)` voi poiketa näkyvien merkkien määrästä, koska UTF-8 saattaa käyttää useita tavuja koodipistettä kohden, ja koska yksi käyttäjälle näkyvä merkki voi koostua useista koodipisteistä, kuten yhdistelmämerkeistä (combining marks) tai emojisarjoista.

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

9Kuvaile, kuinka `slice`-tyyppien viipalointi uudelleen ja sijoittaminen voivat johtaa siihen, että useat `slice`-arvot jakavat saman taustataulukon, ja millaisia bugeja tämä voi aiheuttaa.

`slice`-arvo on otsikkorakenne (header), joka osoittaa taustalla olevaan taulukkoon. `slice`-arvon sijoittaminen tai välittäminen funktiolle kopioi vain tuon otsikkorakenteen, ei alkioita. Uudelleen viipalointi luo toisen otsikkorakenteen, joka osoittaa samaan taustataulukkoon tietylle välille. Tämän vuoksi useat `slice`-arvot voivat viitata samaan tallennustilaan: alkion muuttaminen yhden `slice`-arvon kautta voi näkyä toisessa, ja `append`-operaatio yhteen `slice`-arvoon voi ylikirjoittaa toiselle näkyvää dataa, jos siinä on vielä vapaata kapasiteettia. Tästä aiheutuvia bugeja ovat yllättävät muutokset, korruptoituneet tulokset, suurten taustataulukoiden tahaton muistissa pitäminen pienten `slice`-osien kautta sekä kilpailutilanteet (data race), kun aliaksia käytetään samanaikaisesti. Tahattoman jakamisen välttämiseksi tee puolustava kopiointi käyttämällä `copy`-funktiota tai lauseketta `append([]T(nil), s...)`, tai rajoita kapasiteettia koko kapasiteetin määrittävällä `slice`-lausekkeella ennen `append`-operaatiota.

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

10Selitä `slice`-rakenteen kasvaminen `append`-funktion aikana käsitteellisellä tasolla sekä toistuvien muistinvarauksien vaikutukset suorituskykyyn.

Kun `append` lisää elementtejä `slice`-rakenteeseen, se kirjoittaa ne olemassa olevaan taustataulukkoon (backing array), jos `slice`:lla on riittävästi kapasiteettia. Jos kapasiteetti ei riitä, Go varaa suuremman taustataulukon, kopioi olemassa olevat alkiot, kirjoittaa uudet alkiot ja palauttaa `slice`-otsikon, joka osoittaa uuteen muistialueeseen. Tarkka kasvatuskäytäntö riippuu toteutuksesta, mutta käsitteellisesti kapasiteetti kasvaa riittävästi, jotta toistuvat `append`-lisäykset ovat kuoletetusti tehokkaita (amortized efficient). Toistuvat uudelleenvaraukset kuluttavat silti suoritinaikaa kopiointiin, luovat uusia muistinvarauksia, lisäävät GC (Garbage Collector) -kuormitusta ja voivat rikkoa tilojen jaon (sharing) vanhojen `slice`-viittausten kanssa. Jos odotettu koko on tiedossa, varaa muisti ennakkoon komennolla `make([]T, 0, n)` rakentaessasi `append`-funktiolla, tai komennolla `make([]T, n)` täyttäessäsi taulukkoa indekseittäin, jotta vältyt turhilta muistinvarauksilta.

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

Kielen semantiikka

11Miten tyhjä tunniste (blank identifier) toimii Gossa käyttämättömien arvojen, tuontien ja käännösaikaisten rajapintatarkistusten osalta?

Tyhjä tunniste `_` on vain kirjoitusta tukeva paikkamerkki. Siihen sijoittaminen hylkää arvon eikä luo käyttökelpoista muuttujaa. Sitä käytetään tarpeettomien paluuarvojen tai silmukkamuuttujien ohittamiseen, paketin tuomiseen pelkkien sivuvaikutusten vuoksi muodossa `import _ "pkg"`, sekä käännösaikaisiin rajapintatoteutusten tarkistuksiin, kuten `var _ io.Reader = (*MyReader)(nil)`. Tyhjä tuonti suorittaa silti tuodun paketin alustuksen. Rajapintatarkistuksen sijoitus epäonnistuu kääntämisvaiheessa, jos konkreettisen tyypin metodijoukko ei täytä rajapinnan vaatimuksia.

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

Paketit

12Miten pakettien alustusjärjestys toimii Go:ssa ottaen huomioon `init`-funktiot ja tuodut riippuvuudet?

Go alustaa paketit riippuvuusjärjestyksessä. Paketin tuodut riippuvuudet alustetaan aina ennen ne tuovaa pakettia. Paketin sisällä pakettitason muuttujat alustetaan ennen mahdollisia `init`-funktioita, ja muuttujien alustus tapahtuu kielen määrittelemässä riippuvuus- ja esittelyjärjestyksessä. Tämän jälkeen paketin `init`-funktiot suoritetaan automaattisesti; yhdellä paketilla voi olla useita `init`-funktioita, eikä niitä ole mahdollista kutsua suoraan koodista. Jokainen paketti alustetaan vain kerran. Suoritettavan ohjelman tapauksessa tuontiverkko alustetaan ensin, sitten alustetaan `main`-paketti, ja lopuksi kutsutaan `main.main`-funktiota.

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

13Selitä Go-kielen pakettien näkyvyyssäännöt, mukaan lukien viedyt tunnisteet (exported identifiers) ja `internal/`-hakemistokäytäntö.

Go-kielessä pakettien näkyvyyttä hallitaan tunnisteiden nimeämisellä, ei näkyvyysavainsanoilla. Tunniste, jonka nimi alkaa isolla Unicode-kirjaimella, on viety (exported) ja siihen voidaan viitata muista paketeista; muut tunnisteet ovat sisäisiä (unexported), ja niitä voidaan käyttää vain saman paketin sisällä. Tämä koskee funktioita, tyyppejä, metodeja, muuttujia, vakioita ja `struct`-kenttiä. Paketit käyttävät vietyjä tunnisteita julkisen rajapintansa määrittelyyn ja pitävät toteutusyksityiskohdat sisäisinä. Erillisenä sääntönä `internal/`-hakemiston alla sijaitsevan paketin voi tuoda vain koodi, jonka tuontipolku kuuluu kyseisen `internal`-hakemiston ylähakemistopuuhun; tätä valvoo Go-työkaluketju.

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

Suorituksen ohjaus

14Miten `defer` toimii Go-kielessä, mukaan lukien suoritusjärjestys, argumenttien evaluointiaika ja vuorovaikutus paluuarvojen kanssa?

`defer` ajastaa funktiokutsun suoritettavaksi, kun ympäröivästä funktiosta poistutaan, riippumatta siitä, poistutaanko siitä normaalilla paluulla vai `panic`-tilan purkamisen kautta. Useat viivästetyt kutsut suoritetaan LIFO (Last-In, First-Out) -järjestyksessä. Viivästetyn funktion arvo ja sen argumentit evaluoidaan välittömästi, kun `defer`-lause suoritetaan, mutta itse kutsu ajetaan myöhemmin. Nimetyillä paluuarvoilla `return`-lause sijoittaa paluuarvot ensin, minkä jälkeen viivästetyt funktiot suoritetaan. Tämän ansiosta viivästetty sulkeuma voi havainnoida tai muokata nimettyjä tulosmuuttujia ennen kuin kutsuja vastaanottaa ne. Tämä tekee `defer`-lauseesta hyödyllisen siivoustoimenpiteissä, kuten tiedostojen sulkemisessa, muteksien lukituksen avaamisessa ja resurssien vapauttamisessa.

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

Virheiden käsittely

15Selitä Go-kielen virheidenkäsittelymalli ja tavanomaiset tavat, joilla virheitä luodaan, palautetaan ja tarkistetaan.

Go käsittelee virheitä tavallisina arvoina, ei poikkeuksina. Mikä tahansa tyyppi, jolla on `Error() string` -metodi, toteuttaa sisäänrakennetun `error`-rajapinnan. Funktiot palauttavat tavanomaisesti `error`-arvon viimeisenä tuloksenaan, jolloin `nil` tarkoittaa onnistumista ja siitä poikkeava virhe tarkoittaa, että kutsujan on käsiteltävä tai välitettävä virhe eteenpäin. Yksinkertaisia virheitä luodaan yleensä `errors.New`-funktiolla, muotoiltuja virheitä `fmt.Errorf`-funktiolla, ja kutsujat tarkistavat tyypillisesti virheet muodossa `if err != nil { ... }`.

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

16Miten `panic`-tilanteista toipumista tulisi käsitellä Go-taustapalveluissa, mukaan lukien mitä tapahtuu, kun `goroutine` panikoi, ja milloin prosessin tulisi toipua kaatumisen sijaan?

`panic` purkaa nykyisen `goroutine`:n kutsupinon ja suorittaa sen viivästetyt funktiot (`defer`). `recover` toimii vain, kun sitä kutsutaan saman `goroutine`:n viivästetystä funktiosta; yksi `goroutine` ei voi toipua toisen `goroutine`:n `panic`-tilanteesta. Jos `panic`-tilanteesta ei toivuta, koko prosessi kaatuu. Taustapalveluissa toipuminen tulisi yleensä sijoittaa eristysrajoille, kuten pyyntöjen käsittelijöihin, RPC (Remote Procedure Call) -välikerroksiin tai taustatehtäviä suorittavien `goroutine`-rutiinien aloituspisteisiin, jotta yksi epäonnistuva pyyntö tai työ ei kaada koko palvelua. Mutta jos on mahdollista, että `panic` on korruptoinut jaettua tilaa tai tehnyt prosessin eheydestä epäluotettavan, on turvallisempaa antaa prosessin kaatua ja käynnistyä uudelleen sen sijaan, että se toipuisi ja jatkaisi toimintaansa sokeasti.

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

Samanaikaisuus

17Mitä ovat `nil`-kanavat Go-kielessä, ja miten ne voivat vahingossa rikkoa koodia tai tarkoituksella poistaa `select`-rakenteen `case`-vaihtoehtoja käytöstä?

`nil`-kanava on kanavamuuttuja, jonka arvo on `nil`. Tämä johtuu usein siitä, että sitä ei ole alustettu `make`-funktiolla tai se on nimenomaisesti asetettu arvoon `nil`. `nil`-kanavalle lähettäminen tai siltä lukeminen estää suorituksen ikuisesti. `select`-rakenteessa `nil`-kanavaa käyttävä `case` ei ole koskaan valmis suoritettavaksi, joten kanavamuuttujan asettaminen arvoon `nil` voi tarkoituksella poistaa kyseisen `case`-vaihtoehdon käytöstä. `nil`-kanavan käyttö vahingossa voi aiheuttaa goroutine-säikeiden jumiutumisen tai estää `select`-logiikkaa käsittelemästä odotettuja tapahtumia.

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

18Miten `sync/atomic`-paketin atomiset operaatiot eroavat mutex-pohjaisesta synkronoinnista, ja milloin niiden käyttö on perusteltua?

`sync/atomic`-paketti tarjoaa yksittäisiin muistipaikkoihin kohdistuvia jakamattomia operaatioita, kuten lataus, tallennus, lisäys, vaihto sekä vertailu ja vaihto (compare-and-swap), taaten samalla synkronoinnin ja muistioperaatioiden järjestyksen (memory ordering). Mutex-lukko suojaa kriittistä aluetta, joten se voi suojata mielivaltaista koodia ja invariantteja, jotka koskevat useita luku- ja kirjoitusoperaatioita tai kenttiä. Atomiset operaatiot sopivat yksinkertaisiin, toisistaan riippumattomiin tiloihin, kuten laskureihin, tilalippuihin, järjestysnumeroihin tai huolellisesti suunniteltuihin lukottomiin (lock-free) tietorakenteisiin. Suosi mutex-lukkoa, kun operaatiot ovat moniosaisia, useiden arvojen on pysyttävä yhdenmukaisina tai kun koodin atomista versiota olisi vaikea hahmottaa tai todistaa oikeaksi.

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

19Kuinka kanavien omistajuus ja goroutine-rutiinien elinkaari tulisi suunnitella, jotta vältytään goroutine-vuodoilta?

Suunnittele `goroutine`-rutiineille selkeä omistaja, yksiselitteinen sammutussignaali ja taattu poistumisreitti. Tuottajapuoli vastaa yleensä kanavan sulkemisesta, erityisesti tulostekanavien osalta; vastaanottajat eivät saisi sulkea kanavaa silloin, kun lähettäjiä saattaa olla vielä aktiivisina. Jokaisen estävän lähetys- tai vastaanotto-operaation, silmukan, ajastimen tai ulkoisen kutsun on joko taatusti valmistuttava tai vapauduttava peruutuksen yhteydessä, yleensä `context.Context`-rajapinnan tai `done`-kanavan avulla. Käytä `WaitGroup`-, `errgroup`- tai vastaavia koordinaatiomekanismeja, jotta työntekijöiden valmistumista odotetaan ja kanavat suljetaan vasta sen jälkeen, kun lähettäjät ovat lopettaneet toimintansa.

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

20Mitkä ovat yleisimpiä syitä goroutine-vuotoihin Go-palveluissa, ja miten ne havaitaan ja korjataan tuotannossa?

Yleisimpiä syitä goroutine-vuotoihin Go-palveluissa ovat ikuisesti kanavien lähetys- tai vastaanotto-operaatioihin jumiutuneet goroutinet, muiden estävien operaatioiden odottaminen ilman peruutusta, jumiutunut I/O (Input/Output) ilman aikarajoja, taustasilmukat tai `ticker`-ajastimet, jotka eivät koskaan pysähdy, sekä pyyntökohtaiset goroutinet, jotka jatkavat suoritustaan alkuperäisen pyynnön päätyttyä. Tuotannossa etsitään jatkuvaa kasvua goroutine-määrissä ja siihen liittyviä oireita, minkä jälkeen tutkitaan goroutine-vedoksia tai `pprof`-työkalun goroutine-profiileja, jotta nähdään mihin goroutinet ovat jumiutuneet. Vuodon korjaaminen tarkoittaa koodin muuttamista siten, että kyseiset goroutinet pystyvät poistumaan: lisää peruutuksia ja aikarajoja, pysäytä `ticker`-ajastimet, sulje kanavat oikein, vältä irtaantuneita pyyntökohtaisia goroutineja ja rajoita rinnakkaisuutta tarvittaessa.

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