Questions d'entretien Confirmé Ingénieur Backend
Sur quoi porte un entretien Confirmé de Ingénieur Backend, les questions que vous rencontrerez et comment vous entraîner avec un retour IA instantané.
Ce qui est attendu au niveau Confirmé
On attend de la concurrence, du cache, de la conception de schémas et une prise en charge autonome d'un service.
Exemples de questions d'entretien pour Ingénieur Backend
- TechniqueQuand choisiriez-vous une base de données SQL plutôt qu'un stockage NoSQL, et pourquoi ?Ce qu'une bonne réponse couvre
- ACID compliance
- structured relationships
- vertical vs horizontal scaling
- eventual consistency tradeoffs
- query complexity
Voir un exemple de réponse
Choisir une base de données SQL quand l'intégrité des données et les transactions ACID sont critiques, comme dans les systèmes financiers ou les commandes clients. Les bases SQL excellent pour les données fortement structurées avec des relations complexes (jointures) et des schémas fixes. Leur modèle relationnel permet d'éviter la redondance via la normalisation. En revanche, NoSQL convient mieux pour des schémas flexibles, des volumes massifs de données non structurées, ou un besoin de scalabilité horizontale immédiate (distribution natale). Un compromis classique : SQL offre une cohérence forte mais passe mal à l'échelle horizontale (sharding complexe), tandis que NoSQL sacrifie souvent la cohérence immédiate pour la disponibilité et la partition (théorème CAP). Pour une API devant gérer des milliards de requêtes simples avec des lectures majoritaires, NoSQL peut être préférable ; pour un système de réservation avec des contraintes d'intégrité, SQL reste indispensable.
- TechniqueComment fonctionnent les index de base de données, et quels sont leurs compromis ?Ce qu'une bonne réponse couvre
- B-tree vs hash index
- read vs write performance
- indexing overhead
- covering index
- composite index order
Voir un exemple de réponse
Un index de base de données est une structure auxiliaire qui accélère les recherches en échange d'un coût en écriture et en stockage. Les index B-tree (par défaut dans MySQL/PostgreSQL) permettent des recherches, tris et plages en O(log n), tandis que les index hash offrent un accès direct O(1) mais ne supportent pas les requêtes de plage. L'overhead en écriture vient du fait que chaque INSERT, UPDATE ou DELETE doit maintenir l'index, ce qui peut ralentir les opérations d'écriture de 50% ou plus. Un covering index (qui contient toutes les colonnes nécessaires à une requête) évite les accès à la table, améliorant les lectures. L'ordre des colonnes dans un index composite est crucial : il faut placer en premier les colonnes avec le plus de sélectivité. Un piège courant est d'ajouter trop d'index, ce qui dégrade les performances d'écriture et gonfle la taille de la base. En pratique, il faut mesurer l'utilisation via les logs de requêtes lentes et le plan d'exécution.
- TechniqueExpliquez comment vous rendriez idempotent un endpoint de paiement.Ce qu'une bonne réponse couvre
- idempotency key
- exactly-once semantics
- check-process-commit pattern
- idempotency key storage
- timeout and expiry
Voir un exemple de réponse
Pour rendre un endpoint de paiement idempotent, on utilise une clé d'idempotence fournie par le client (souvent un UUID dans l'en-tête de la requête). Le serveur vérifie d'abord si une opération avec cette clé a déjà été traitée, en consultant un stockage persistant (base de données ou cache distribué). Si la clé existe, on retourne la réponse précédente sans exécuter la transaction ; sinon, on traite le paiement de manière atomique (vérification du solde, débit, mise à jour). Il faut garantir que la capture de la clé et l'exécution du paiement soient dans une même transaction ou protégées par un verrouillage (pessimiste ou optimiste). Un suivi des clés avec une date d'expiration évite de saturer le stockage. Le piège principal est de ne pas couvrir les cas où la transaction est validée côté banque mais pas retournée au client (timeout) : dans ce cas, le client renvoie la même clé, et le serveur doit lire l'état réel (via une API de rapprochement) ou appliquer une compensation. Cette conception assure une exécution exactement une fois malgré les retries.
- ProgrammationImplémentez un cache LRU avec get et put en O(1).Ce qu'une bonne réponse couvre
- doubly linked list
- hash map for O(1) access
- LRU eviction policy
- time vs space tradeoff
- thread safety
Voir un exemple de réponse
Voici une implémentation d'un cache LRU en Python utilisant une combinaison de dictionnaire (hash map) et une liste chaînée double pour un accès et une mise à jour en O(1). Le dictionnaire stocke les références aux nœuds, et la liste chaînée maintient l'ordre d'utilisation (le nœud le plus récemment utilisé en tête). Pour chaque get, on déplace le nœud en tête ; pour put, on ajoute en tête et on supprime la queue si la capacité est dépassée. La complexité temporelle est O(1) pour get et put, et la complexité spatiale est O(capacité). Voici le code :
Solution de référencepython class Node: def __init__(self, key=None, value=None): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.cache = {} # mapping clé -> nœud # Têtes et queue fictives pour éviter les cas limites self.head = Node() self.tail = Node() self.head.next = self.tail self.tail.prev = self.head def _remove_node(self, node: Node): # Supprime un nœud de la liste chaînée prev_node = node.prev next_node = node.next prev_node.next = next_node next_node.prev = prev_node def _add_to_head(self, node: Node): # Ajoute un nœud juste après la tête node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def get(self, key: int) -> int: if key in self.cache: node = self.cache[key] # Déplace le nœud en tête (marque comme récemment utilisé) self._remove_node(node) self._add_to_head(node) return node.value return -1 def put(self, key: int, value: int) -> None: if key in self.cache: # Mise à jour de la valeur existante node = self.cache[key] node.value = value self._remove_node(node) self._add_to_head(node) else: # Ajout d'une nouvelle clé if len(self.cache) >= self.capacity: # Supprime le nœud le moins récemment utilisé (queue) lru_node = self.tail.prev self._remove_node(lru_node) del self.cache[lru_node.key] nouveau_noeud = Node(key, value) self.cache[key] = nouveau_noeud self._add_to_head(nouveau_noeud) - ProgrammationÀ partir d'un flux d'événements, concevez un limiteur de débit pour une API.Ce qu'une bonne réponse couvre
- token bucket algorithm
- sliding window log
- distributed counters
- atomic increments (Redis)
- burst vs steady state
Voir un exemple de réponse
Pour concevoir un limiteur de débit à partir d'un flux d'événements, on peut utiliser l'algorithme du token bucket, qui permet de lisser les rafales tout en respectant un débit moyen. Chaque utilisateur ou IP a un bucket avec un nombre maximal de tokens (burst). Les tokens sont ajoutés à un taux fixe (par seconde). Quand une requête arrive, un token est consommé ; si aucun token n'est disponible, la requête est rejetée (HTTP 429). L'implémentation peut être centralisée via Redis avec des clés et des commandes atomiques (INCR, EXPIRE) pour éviter les conditions de course. Une alternative est le sliding window log, qui enregistre les timestamps des requêtes récentes et vérifie si le nombre dans la fenêtre dépasse le seuil. Le choix dépend des exigences de précision et de la charge. Voici une implémentation simple en Python avec token bucket :
Solution de référencepython import time class TokenBucket: def __init__(self, rate: float, burst: int): self.rate = rate # tokens par seconde self.burst = burst # capacité maximale self.tokens = burst self.last_refill = time.time() def allow(self, tokens_needed: int = 1) -> bool: now = time.time() # Ajouter des tokens en fonction du temps écoulé ecoule = now - self.last_refill self.tokens = min(self.burst, self.tokens + ecoule * self.rate) self.last_refill = now if self.tokens >= tokens_needed: self.tokens -= tokens_needed return True return False # Utilisation : limiteur = TokenBucket(rate=10, burst=20) # 10 req/s, burst 20 if limiteur.allow(): print("Requête acceptée") else: print("429 Too Many Requests") - Conception de systèmesConcevez un raccourcisseur d'URL capable de gérer des milliards de redirections.Ce qu'une bonne réponse couvre
- hash-based key generation
- base62 encoding
- distributed ID generation (Snowflake)
- caching layer (Redis)
- database sharding
Voir un exemple de réponse
Un raccourcisseur d'URL doit gérer des milliards de redirections. On commence par un service de génération de clés uniques : on peut utiliser un compteur global (via une base de données ou un service comme Snowflake) puis coder en base62 (a-z, A-Z, 0-9) pour obtenir des chaînes courtes (7 caractères donnent 62^7 ~ 3500 milliards de combinaisons). Le flux : l'utilisateur soumet une longue URL → le service génère une clé unique, la stocke dans une base de données NoSQL (Cassandra ou DynamoDB) partitionnée par clé, et écrit également dans un cache Redis (avec TTL) pour les lectures chaudes. Lors d'une redirection, le serveur reçoit la clé, vérifie d'abord le cache (hit >99%), et si absent, interroge la base. Pour passer à l'échelle, on utilise un load balancer devant les serveurs Web, et on sharde la base sur la clé. La latence de redirection doit être inférieure à 10 ms ; le cache Redis aide énormément. Un piège est la gestion des collisions de clés : on doit garantir l'unicité, soit en utilisant un système distribué de génération d'IDs, soit en réessayant avec une nouvelle clé. On peut aussi pré-générer des lots de clés pour réduire la contention.
- ComportementalDécrivez une panne que vous avez aidé à résoudre et ce que vous avez changé ensuite.Ce qu'une bonne réponse couvre
- STAR method
- root cause analysis
- monitoring and alerting
- rollback strategy
- postmortem blameless
Voir un exemple de réponse
Dans mon précédent poste, nous avons eu une panne où notre API de paiement renvoyait des 500 pour 20% des requêtes. J'ai utilisé la méthode STAR : Situation : lancement d'une nouvelle fonctionnalité de vérification de solde. Tâche : résoudre l'instabilité. Action : j'ai d'abord analysé les logs et les métriques (APM) ; j'ai découvert qu'une nouvelle requête SQL vers une table indexée sur une colonne non sélective provoquait des verrous sur les lignes les plus chaudes. J'ai immédiatement appliqué un rollback de la fonctionnalité (feature flag) pour rétablir le service en 2 minutes. Résultat : le taux d'erreur est revenu à 0%. Ensuite, j'ai amélioré l'index en ajoutant une couverture et un partitionnement par date. Nous avons aussi mis en place des alertes sur le temps de requête et le nombre de verrous. Cette expérience m'a appris l'importance de tester les requêtes sur un volume de production et d'avoir des mécanismes de rollback rapides.
- ComportementalParlez-moi d'une fois où vous avez dû faire un compromis difficile sur la cohérence des données.Ce qu'une bonne réponse couvre
- strong vs eventual consistency
- tradeoff between availability and correctness
- compensating transactions
- saga pattern
- business impact
Voir un exemple de réponse
Lors de la conception d'un système de réservation de billets, nous avons dû choisir entre cohérence forte (garantie qu'un siège ne soit jamais surréservé) et disponibilité (éviter de bloquer des clients). La solution initiale utilisait des verrous pessimistes sur la base de données, mais provoquait des timeouts lors des pics de charge. J'ai proposé un compromis : utiliser une approche de réservation temporaire avec un TTL de 15 minutes. Quand un client initie une réservation, un siège est marqué comme réservé (avec une entrée dans Redis), et on vérifie en base avec un verrou optimiste (numéro de version). Si le paiement n'aboutit pas dans le délai, la réservation expire et le siège est libéré. Ce compromis accepte un faible risque de surréservation (en cas de dépassement du TTL et de deux achats simultanés) en échange d'une bien meilleure expérience utilisateur (pas de blocage). Nous avons ensuite mis en place un processus de compensation : si une surréservation se produit, un email automatique propose un autre siège ou un remboursement. Ce tradeoff a été validé par le métier car le taux d'incidents était inférieur à 0.01%.
Ce que les recruteurs évaluent
Modélisation des données
Relationnel vs. NoSQL, indexation, normalisation et transactions.
Conception d'API
REST/GraphQL/gRPC, idempotence, pagination et versionnage.
Concurrence
Verrous, conditions de course, files d'attente et cohérence à terme.
Conception de systèmes
Cache, sharding, réplication et gestion des pannes à l'échelle.
Algorithmes
Analyse de complexité et choix pratiques de structures de données.
Comment se préparer
- Énoncez vos hypothèses et contraintes avant de concevoir — les recruteurs récompensent le cadrage.
- Analysez toujours la complexité temporelle et spatiale dans les réponses de code.
- En conception de systèmes, menez la conversation : besoins, API, modèle de données, échelle, modes de défaillance.
Questions fréquentes
Quelles questions de conception de systèmes reviennent en entretien backend ?
Les sujets courants incluent la conception d'un raccourcisseur d'URL, d'un limiteur de débit, d'un fil d'actualité ou d'un système de chat, avec discussion du cache, du sharding et de la cohérence.
Quel niveau d'algorithmique demandent les entretiens backend ?
La plupart des entreprises incluent encore une ou deux épreuves de code axées sur les structures de données et la complexité, même si le quotidien relève davantage des systèmes.
Comment m'entraîner efficacement aux entretiens backend ?
Combinez des exercices de code avec de la pratique orale de conception de systèmes, et faites des entretiens blancs pour défendre vos compromis à voix haute.
Entraînez-vous aux questions de Ingénieur Backend avec un retour instantané de l'IA
Offersly mène un entretien blanc adapté à votre CV et au poste visé, puis note chaque réponse sur la pertinence, la profondeur, la clarté et l'exactitude.