Comment réussir un entretien Go : les meilleures questions avec des exemples de code
Un entretien pour un poste de développeur Go ne consiste pas seulement à vérifier la syntaxe, mais aussi à comprendre en profondeur le modèle de concurrence, la gestion de la mémoire et le style idiomatique du langage. Dans cet article, nous avons analysé les 10 meilleures questions les plus fréquemment posées lors des entretiens Golang, avec des réponses détaillées et des exemples de code. Ce contenu convient aussi bien aux niveaux Junior qu'aux niveaux Middle.
1. Goroutines vs threads OS : quelle est la différence ?
C'est une question de base qui teste la compréhension du modèle d'exécution de Go. La réponse doit être claire :
- Taille de la pile : Une goroutine démarre avec une pile d'environ 2 Ko, qui croît dynamiquement. Un thread OS a une pile fixe (1-8 Mo).
- Changement de contexte : Les goroutines changent de contexte dans l'espace utilisateur (Go scheduler). Les threads le font via un appel système (plus coûteux).
- Coût de création : Une goroutine coûte environ 4 Ko de mémoire, un thread environ 1 Mo. On peut lancer des milliers de goroutines.
// Exemple : 10000 goroutinesfunc main() { for i := 0; i < 10000; i++ { go func(n int) { fmt.Println("Goroutine", n) }(i) } time.Sleep(time.Second)}2. Canaux : bufferisés et non bufferisés
Une question pour comprendre la synchronisation. Expliquez la différence avec un exemple :
- Canal non bufferisé : L'envoi est bloqué jusqu'à la réception. Garantit la synchronicité.
- Canal bufferisé : L'envoi n'est pas bloqué tant que le tampon n'est pas plein. Permet une transmission asynchrone.
// Canal non bufferiséch := make(chan int)go func() { ch <- 42 }()val := <-ch // Bloqué jusqu'à ce que la valeur arrive
// Canal bufferisébufCh := make(chan int, 2)bufCh <- 1 // Non bloquébufCh <- 2 // Non bloqué// bufCh <- 3 // Bloqué - le tampon est plein3. Select et gestion de plusieurs canaux
L'instruction select permet d'attendre plusieurs opérations sur des canaux. On demande souvent à propos de default et des timeouts :
func main() { ch1 := make(chan string) ch2 := make(chan string)
go func() { time.Sleep(2 * time.Second); ch1 <- "from ch1" }() go func() { time.Sleep(1 * time.Second); ch2 <- "from ch2" }()
select { case msg := <-ch1: fmt.Println(msg) case msg := <-ch2: fmt.Println(msg) case <-time.After(3 * time.Second): fmt.Println("Timeout") }}4. Interfaces : interface vide et type assertion
Teste la compréhension de interface{} (any) et du transtypage sécurisé :
func describe(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Entier : %d", v) case string: fmt.Printf("Chaîne : %s", v) default: fmt.Printf("Type inconnu : %T", v) }}
func main() { describe(42) describe("hello") describe(3.14)}5. Gestion de la mémoire : pile et tas, escape analysis
Une question pour comprendre comment Go décide où placer les variables. Le point clé est l'escape analysis :
- Si le compilateur voit qu'une variable peut être utilisée après le retour d'une fonction, elle « s'échappe » vers le tas.
- L'utilisation de pointeurs, d'interfaces et de fermetures provoque souvent une allocation dans le tas.
// La variable x "s'échappe" vers le tasfunc createPointer() *int { x := 42 return &x // x ne peut pas être sur la pile}6. Defer : ordre d'exécution et particularités
Defer s'exécute selon le principe LIFO (Last In, First Out). Les arguments sont évalués au moment de la déclaration, pas de l'exécution :
func main() { x := 10 defe