Préparation Junior Go

Questions d'entretien Junior pour développeur backend Go

15 questions sélectionnées pour les développeurs Junior Go qui doivent expliquer clairement les fondamentaux et la pratique backend.

Commencer un entretien IA Junior GoAucune carte bancaire requise. 1 session gratuite disponible.
Pratique d'entretien technique en anglaisUn mode où les non-natifs peuvent s'entraîner à passer des entretiens techniques.

Système de types

1Expliquez comment fonctionnent les valeurs zéro (zero values) de Go pour les types intégrés et les types de type référence, et pourquoi elles sont importantes lorsqu'on déclare des variables sans initialisation explicite.

En Go, une variable déclarée sans initialiseur explicite est automatiquement initialisée à la valeur zéro de son type. Les types numériques deviennent 0, bool devient false, string devient "", et les tableaux ou structs sont mis à zéro élément par élément ou champ par champ. Les types de type pointeur ou référence comme les pointeurs, slices, maps, channels, fonctions et interfaces ont nil comme valeur zéro. Cela compte car les variables Go et les champs de struct omis démarrent dans un état déterministe plutôt que de contenir des données indéterminées, et de nombreuses API sont conçues pour que la valeur zéro soit un défaut utile, même si certaines valeurs nil nécessitent encore une initialisation avant certaines opérations.

Essayer de répondre à cette question avec un coach IA

2Comment Go gère-t-il l'égalité des structs et que se passe-t-il lorsqu'une struct contient des champs non comparables ?

Les valeurs de struct en Go peuvent être comparées avec `==` et `!=` uniquement lorsque chaque champ de la struct est comparable. L'égalité compare les champs correspondants selon la règle d'égalité propre à chaque champ. Si une struct contient un champ non comparable tel qu'une slice, une map ou une fonction, le type struct n'est pas comparable, et comparer deux valeurs de ce type struct avec `==` est une erreur à la compilation. Pour de telles structs, utilisez une logique de comparaison personnalisée ou un helper d'égalité en profondeur approprié, surtout dans les tests.

Essayer de répondre à cette question avec un coach IA

3Expliquez l'immuabilité des strings en Go et la relation entre string, []byte, bytes.Buffer et strings.Builder.

Une `string` Go est une séquence immuable d'octets, souvent du texte UTF-8 mais pas nécessairement de l'UTF-8 valide. On ne peut pas modifier une string en place ; pour changer le contenu, on convertit généralement en `[]byte` pour des éditions au niveau octet ou en `[]rune` pour des éditions au niveau point de code, puis on reconvertit. Les conversions normales entre `string` et `[]byte` copient les données et peuvent allouer, donc des conversions répétées ou des concatenations répétées dans des boucles peuvent être coûteuses. `strings.Builder` est optimisé pour construire efficacement des strings, tandis que `bytes.Buffer` est un buffer d'octets mutable utile pour les données orientées octets et les E/S, et peut aussi produire une string.

Essayer de répondre à cette question avec un coach IA

4Comment fonctionnent les pointeurs en Go, et quelles opérations sont volontairement interdites par rapport au C ?

Un pointeur Go est une valeur typée qui contient l'adresse d'une autre valeur, par exemple *int pointe vers un int. Utilisez &x pour prendre l'adresse d'une valeur et *p pour déréférencer un pointeur afin de lire ou d'écrire la valeur pointée. Passer ou stocker un pointeur permet à plusieurs endroits d'observer ou de muter la même valeur sous-jacente, et un pointeur peut être nil. Contrairement au C, le Go sûr interdit volontairement l'arithmétique de pointeurs et la manipulation arbitraire d'adresses brutes ; les exceptions de bas niveau nécessitent le package unsafe.

Essayer de répondre à cette question avec un coach IA

5Expliquez comment fonctionnent les constantes et iota en Go, y compris les constantes typées versus non typées.

Les constantes Go sont des valeurs à la compilation : booléennes, string ou numériques. Une constante typée a un type spécifique. Une constante non typée n’a pas de type concret fixe tant qu’elle n’est pas utilisée dans un contexte typé, et les constantes numériques non typées sont représentées de façon exacte/à haute précision jusqu’alors ; la valeur doit être représentable dans le type choisi. `iota` est un identifiant prédéclaré utilisé dans les déclarations `const` : il commence à 0 dans chaque bloc const et s’incrémente pour chaque spécification de constante, ce qui le rend utile pour des constantes de type enum et des bit flags.

Essayer de répondre à cette question avec un coach IA

6Comment fonctionnent les conversions explicites en Go, et pourquoi le langage évite-t-il de nombreuses conversions implicites ?

Go exige en général des conversions explicites via `T(x)` lors d’un changement de type d’une valeur, comme `int64(i)` ou `MyID(n)`. Cela rend les changements de type visibles et évite des conversions numériques, booléennes ou string implicites surprenantes. Les conversions ne sont autorisées que selon les règles de conversion de Go et peuvent changer la valeur, par exemple par overflow/troncature d’entiers, troncature float-vers-int vers zéro, ou perte de précision. Les constantes non typées sont plus flexibles : elles peuvent être utilisées dans un contexte typé si la valeur de la constante est représentable dans ce type.

Essayer de répondre à cette question avec un coach IA

Structures de données

7Décrivez la différence entre les tableaux (arrays) et les slices en Go, y compris le comportement de la longueur, de la capacité et du stockage sous-jacent.

Un tableau en Go a une longueur fixe qui fait partie de son type, comme [3]int ; il stocke ses éléments directement, et assigner ou passer un tableau copie toute la valeur du tableau. Une slice, comme []int, est un petit descripteur sur un tableau sous-jacent : conceptuellement elle contient un pointeur vers les éléments, une longueur et une capacité. La longueur d'une slice est le nombre d'éléments visibles ; sa capacité est le nombre d'éléments utilisables depuis le début de la slice jusqu'à la fin du tableau de support. Les slices sont flexibles : le reslicing modifie le descripteur, et append peut réutiliser le même tableau sous-jacent si la capacité le permet ou en allouer un nouveau sinon.

Essayer de répondre à cette question avec un coach IA

8Comment se comporte le type map de Go concernant les types de clés, les clés absentes, les maps nil et l'ordre d'itération ?

Les types de clés d'une map Go doivent être comparables ; les slices, maps et fonctions ne peuvent pas être utilisées directement comme clés. La recherche d'une clé absente renvoie la valeur zéro du type des éléments, c'est pourquoi la forme comma-ok (`v, ok := m[k]`) sert à distinguer l'absence d'une valeur zéro présente. Une map nil peut être lue et parcourue avec range, mais une affectation provoque un panic ; il faut l'initialiser avant d'écrire. L'ordre d'itération d'une map n'est pas spécifié et le code ne doit pas en dépendre.

Essayer de répondre à cette question avec un coach IA

Gestion mémoire

9Quand faut-il passer une struct ou une autre valeur par valeur plutôt que par pointeur en Go ?

Passez par valeur lorsque la valeur est petite, en lecture seule pour l'appel, ou lorsque vous voulez une sémantique de copie indépendante. Passez par pointeur lorsque la fonction doit muter la valeur de l'appelant, que la copie serait coûteuse, que le type ne doit pas être copié, ou que nil/l'identité partagée fait partie de l'API. Ne supposez pas que les pointeurs sont toujours plus rapides : ils peuvent ajouter de l'aliasing, des escapes vers le tas, du travail pour le GC, et un accès moins favorable au cache. Privilégiez d'abord une sémantique claire et mesurez les choix sensibles aux performances.

Essayer de répondre à cette question avec un coach IA

Sémantique du langage

10Comment se comportent l'affectation et la copie pour les structs, tableaux, maps et slices en Go ?

L'affectation en Go copie la valeur affectée, mais l'effet dépend du type. L'affectation d'une struct copie les champs de la struct, et l'affectation d'un tableau copie chaque élément. L'affectation d'une slice copie l'en-tête de slice — pointeur, longueur et capacité — donc les slices partagent généralement le même tableau sous-jacent. L'affectation d'une map copie un descripteur de map de type référence, donc les deux variables se réfèrent aux mêmes données de map. Pour une propriété indépendante, faites une copie défensive, par exemple avec copy ou slices.Clone pour les slices, et en créant une nouvelle map et en copiant les entrées pour les maps.

Essayer de répondre à cette question avec un coach IA

11Comment les closures capturent-elles les variables en Go, et quels bugs peuvent survenir autour des boucles et des goroutines ?

Une closure en Go est une valeur de fonction qui fait référence à des variables de sa portée lexicale environnante. Elle capture les variables elles-mêmes, donc les mutations peuvent être observées par la closure, et les variables capturées peuvent vivre plus longtemps que la fonction créatrice si la closure s'échappe. Les bugs autour des boucles et des goroutines surviennent lorsque des closures partagent ou observent une variable après qu'elle a changé, ce qui conduit souvent les goroutines ou les callbacks différés à voir une valeur non voulue ou à provoquer des data races sur un état partagé. La correction habituelle consiste à passer la valeur voulue en argument à la closure ou à créer une nouvelle copie locale par itération, et à synchroniser l'accès concurrent si nécessaire. Depuis Go 1.22, les variables de boucle déclarées par `for`/`range` sont par itération, ce qui élimine de nombreux bugs classiques de capture de variables de boucle, mais les variables réutilisées hors de la boucle et tout autre état mutable partagé peuvent encore poser problème.

Essayer de répondre à cette question avec un coach IA

12Décrivez la sémantique de range sur les arrays, slices, maps, strings et channels.

`range` itère selon le type de l'opérande. Sur un array ou une slice, il produit un index et une valeur d'élément ; la valeur d'élément est une copie, donc y assigner ne modifie pas la collection. Ranger sur une valeur d'array copie l'array pour l'itération, tandis que ranger sur une slice utilise le header de slice et indexe l'array sous-jacent. Sur une map, il produit clé et valeur dans un ordre non spécifié, et la valeur est une copie. Sur une string, il produit l'index d'octet et le point de code Unicode décodé (`rune`), pas un index de position de rune. Sur un channel, il reçoit des valeurs jusqu'à ce que le channel soit fermé et vidé ; un range sur un channel nil bloque indéfiniment.

Essayer de répondre à cette question avec un coach IA

Méthodes

13Quelle est la différence entre les value receivers et les pointer receivers, et comment les choix de receiver affectent-ils la mutabilité, la copie et la satisfaction d'interface ?

Une méthode à value receiver reçoit une copie du receiver, elle convient donc au comportement en lecture seule et aux petits types de style immuable ; les changements sur la copie du receiver ne sont pas visibles pour l'appelant. Une méthode à pointer receiver reçoit une copie d'un pointeur vers le receiver, elle peut donc muter l'original et éviter de copier des valeurs grandes ou qui ne doivent pas être copiées. Le choix du receiver affecte les method sets : les méthodes à value receivers sont dans le method set de T et de *T, tandis que les méthodes à pointer receivers ne sont que dans le method set de *T. Par conséquent, une interface exigeant une méthode à pointer receiver est satisfaite par *T, pas par T, même si une valeur T adressable peut appeler la méthode avec la syntaxe d'appel de méthode ordinaire.

Essayer de répondre à cette question avec un coach IA

Interfaces

14Décrivez comment fonctionnent les interfaces en Go et ce que signifie qu'un type satisfait une interface de façon implicite.

Une interface Go définit un ensemble de méthodes requises. Un type concret satisfait une interface de façon implicite lorsque son method set contient ces méthodes ; il n'y a pas de déclaration implements. Une variable d'interface peut contenir une valeur dynamique concrète dont le type satisfait l'interface, et les appels de méthodes d'interface se font vers l'implémentation de cette valeur dynamique. L'interface vide, écrite interface{} ou habituellement any, n'a aucune méthode requise, donc tout type la satisfait. Que T, *T, ou les deux satisfassent une interface dépend de leurs method sets et des choix de receivers.

Essayer de répondre à cette question avec un coach IA

15Comment fonctionnent les type assertions et les type switches avec les valeurs d’interface ?

Une type assertion vérifie la valeur dynamique stockée dans une interface : `v := x.(T)` réussit si le type dynamique de la valeur d’interface est `T`, ou si `T` est une interface implémentée par la valeur dynamique. La forme à un seul résultat panique en cas d’échec. La forme comma-ok, `v, ok := x.(T)`, signale le succès sans paniquer. Un type switch, `switch v := x.(type)`, branche selon le type dynamique de la valeur d’interface, avec `v` typé selon le cas correspondant.

Essayer de répondre à cette question avec un coach IA