Préparation entretien Node.js backend

Questions et réponses d'entretien Node.js 2026

Une sélection ciblée de questions Node.js pour les entretiens backend, structurée pour la pratique orale.

Commencer un mock interview Node.js en français Pratique d'entretien technique en anglaisUn mode où les non-natifs peuvent s'entraîner à passer des entretiens techniques.

Architecture et Boucle d'Événements

1Qu'est-ce que Node.js et à quoi sert-il ?

Node.js est un environnement d'exécution JavaScript en dehors du navigateur, construit sur V8 et étendu avec des API pour le réseau, les fichiers, les processus, les flux (streams) et l'accès au système d'exploitation. Il est couramment utilisé pour les API REST, les services GraphQL, les serveurs WebSocket temps réel, les microservices, les outils CLI, les proxys et les passerelles API (API gateways). Sa principale force réside dans les opérations I/O non bloquantes : un seul processus peut gérer de nombreuses connexions réseau efficacement. Le compromis est que les calculs longs et gourmands en CPU peuvent bloquer la boucle d'événements, à moins qu'ils ne soient déplacés vers des workers, des processus ou un autre service.

2Comment Node.js fonctionne-t-il en interne ?

JavaScript s'exécute dans V8, tandis que les API de Node.js délèguent le travail asynchrone au système d'exploitation ou au pool de threads de libuv. Le thread JavaScript principal n'attend pas qu'une opération I/O soit terminée ; il continue d'exécuter d'autres codes. Lorsque l'opération est terminée, son callback ou sa continuation Promise devient prêt pour la boucle d'événements. C'est ainsi que Node.js atteint une haute concurrence même si le JavaScript utilisateur s'exécute généralement sur un thread principal.

3Qu'est-ce que V8, libuv et la boucle d'événements ?

V8 compile et exécute JavaScript et gère les objets et la mémoire JavaScript. libuv fournit la boucle d'événements multiplateforme, l'intégration I/O asynchrone et un pool de threads workers pour des opérations telles que certaines tâches de système de fichiers, DNS et crypto. La boucle d'événements décide quand les rappels des opérations terminées doivent s'exécuter. Node.js lui-même connecte les API JavaScript aux capacités natives ; V8 seul ne fournit pas de serveur HTTP, d'accès au système de fichiers ou d'API de processus.

4Pourquoi Node.js est souvent appelé mono-threadé ?

Un seul processus Node.js exécute généralement le JavaScript utilisateur sur un thread principal, de sorte que deux gestionnaires de requêtes normaux n'exécutent pas de JavaScript au même moment dans ce thread. Cependant, la plateforme n'est pas littéralement un seul thread : libuv dispose d'un pool de threads, le système d'exploitation effectue des I/O asynchrones, V8 a du travail de ramasse-miettes (garbage-collection), et Node.js prend également en charge worker_threads et les processus enfants. Cette expression décrit principalement le modèle d'exécution JavaScript par défaut.

5Comment Node.js peut-il gérer de nombreuses requêtes en même temps ?

Node.js lance les opérations I/O, puis revient à la boucle d'événements au lieu de bloquer un thread par requête. Par exemple, 1 000 appels à la base de données peuvent être en cours pendant que le thread principal continue d'accepter des connexions et de traiter les rappels prêts. Cela fonctionne bien pour les charges de travail liées aux E/S (I/O-bound) telles que les appels HTTP, les requêtes de base de données, les fichiers et les sockets. Il fonctionne mal lorsque les gestionnaires de requêtes effectuent un lourd travail CPU synchrone, car cela bloque la boucle d'événements et retarde toutes les autres requêtes.

6Quelles sont les principales phases de la boucle d'événements de Node.js ?

La boucle d'événements libuv comprend des phases telles que timers, pending callbacks, idle/prepare, poll, check et close callbacks. Les timers exécutent les callbacks setTimeout et setInterval ; poll gère de nombreux événements I/O ; check exécute les callbacks setImmediate ; close gère les événements de fermeture tels que la fermeture des sockets. Node.js dispose également de files de microtâches, y compris process.nextTick et les microtâches Promise, qui s'exécutent entre les callbacks. Trop de microtâches peuvent affamer l'I/O et faire paraître le service bloqué.

7Quelle est la différence entre les opérations synchrones, asynchrones et non bloquantes ?

Une opération synchrone retourne avant que la ligne suivante ne s'exécute et bloque le thread principal, comme `fs.readFileSync` dans un gestionnaire de requête. Une opération asynchrone retourne son résultat plus tard via un callback ou une Promise. Non-bloquant signifie que le thread peut continuer à effectuer d'autres tâches pendant que l'opération est en attente. Les termes sont liés mais pas identiques : 'async' décrit comment le résultat est livré, tandis que 'non-blocking' décrit si le thread attend. Les gestionnaires de requêtes backend devraient généralement préférer les API asynchrones et non bloquantes.

import fs from 'node:fs';

const syncData = fs.readFileSync('file.txt', 'utf8');

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);

8Qu'est-ce que la famine de la boucle d'événements (event loop starvation) ?

La famine de la boucle d'événements se produit lorsque le travail JavaScript ou les microtâches maintiennent la boucle d'événements occupée, l'empêchant ainsi de traiter rapidement les opérations I/O, les minuteurs ou les nouvelles requêtes. Les causes courantes comprennent des calculs synchrones lourds, un traitement JSON massif, des chaînes Promise non bornées et une utilisation excessive de process.nextTick. Les solutions incluent la division du travail en morceaux (chunks), la limitation des tailles d'entrée, le profilage du CPU, le déplacement du travail lié au CPU vers worker_threads ou un autre service, et l'évitement des API synchrones dans les chemins de requête critiques.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});

Programmation asynchrone et erreurs

9Qu'est-ce qu'un callback et qu'est-ce que le 'callback hell' ?

Un callback est une fonction passée pour être exécutée plus tard, souvent après la complétion d'une opération asynchrone. Les callbacks Node.js classiques suivent généralement le style 'error-first' : callback(erreur, résultat). Le 'callback hell' se produit lorsque les opérations dépendantes sont profondément imbriquées, ce qui rend difficile la gestion des erreurs et le flux de contrôle. La solution est de décomposer en fonctions nommées, d'utiliser Promises ou async/await, et d'exécuter les opérations indépendantes avec Promise.all au lieu de les imbriquer.

import fs from 'node:fs';

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(data);
});

10Qu'est-ce qu'une Promise et comment fonctionne async/await ?

Une Promise représente le résultat futur d'une opération asynchrone et peut être en attente (pending), remplie (fulfilled) ou rejetée (rejected). Une fonction async retourne toujours une Promise. await ne suspend que la fonction async actuelle jusqu'à ce que la Promise se stabilise ; cela ne bloque pas tout le processus Node.js. Une bonne réponse devrait mentionner la gestion des erreurs avec try/catch, éviter les Promises flottantes et utiliser Promise.all pour un travail indépendant afin que les opérations ne s'exécutent pas inutilement de manière séquentielle.

async function loadUser(id) {
  try {
    const user = await repository.findById(id);
    return user;
  } catch (error) {
    console.error(error);
    throw error;
  }
}

const [user, settings] = await Promise.all([
  getUser(),
  getSettings()
]);

11Quelle est la différence entre process.nextTick, queueMicrotask, setImmediate et setTimeout ?

process.nextTick s'exécute après l'opération actuelle et avant que la boucle d'événements ne passe à autre chose. queueMicrotask planifie une microtâche JavaScript standard, similaire aux continuations Promise. setImmediate s'exécute dans la phase 'check'. setTimeout(fn, 0) s'exécute dans la phase des minuteurs après un délai minimum. Habituellement, nextTick s'exécute avant les microtâches Promise, et l'ordre de setTimeout(0) et setImmediate dépend du contexte ; après les rappels I/O, setImmediate s'exécute souvent en premier.

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));

12Comment les erreurs doivent-elles être gérées dans Node.js ?

Le code synchrone utilise try/catch. Le code basé sur des Promises devrait utiliser await à l'intérieur de try/catch ou attacher .catch. Les callbacks 'error-first' devraient vérifier l'argument d'erreur avant d'utiliser le résultat. Dans les applications serveur, un middleware d'erreur centralisé, la journalisation du contexte de requête, des messages clients sécurisés et un arrêt gracieux sont importants. uncaughtException ne devrait pas être utilisé pour maintenir en fonctionnement un processus corrompu ; c'est généralement un signal pour journaliser, nettoyer et redémarrer.

try {
  JSON.parse(input);
} catch (error) {
  handleError(error);
}

try {
  await operation();
} catch (error) {
  handleError(error);
}

operation((error, result) => {
  if (error) return handleError(error);
  return result;
});

Modules et gestion de projet

13Qu'est-ce qu'un module dans Node.js ?

Un module est une unité de code isolée avec une interface exportée explicite. Node.js prend en charge des modules intégrés tels que node:fs et node:http, les modules locaux du projet, les paquets npm, les modules CommonJS et les modules ECMAScript. Les modules clarifient les frontières de responsabilité et permettent la réutilisation. Le compromis en production est le couplage : si les modules s'importent trop profondément ou masquent des effets secondaires au moment de l'importation, il devient plus difficile d'analyser les comportements de test et de démarrage.

// math.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add } from './math.js';

14Quelle est la différence entre `require` de CommonJS et l'importation des modules ES ?

CommonJS utilise `require` et `module.exports`, tandis que les Modules ES utilisent `import` et `export`. Les Modules ES prennent en charge l'analyse statique et le `top-level await`, et ils sont sélectionnés via `package.json` avec `type: module` ou des fichiers `.mjs`. CommonJS est souvent chargé de manière synchrone et peut être marqué avec `.cjs`. Dans les projets Node.js modernes, les deux systèmes existent, donc une réponse complète évite la simplification excessive et explique que l'extension du fichier, le `package.json` et le contexte du projet déterminent le mode module.

// CommonJS
const fs = require('node:fs');
module.exports = { calculate };

// ES Modules
import fs from 'node:fs';
export { calculate };

15Qu'est-ce que npm, package.json et package-lock.json ?

npm est le gestionnaire de paquets et CLI. package.json décrit les scripts, les métadonnées, les dépendances et les devDependencies. package-lock.json enregistre l'arbre de dépendance exact afin que les installations soient reproductibles. Dans un environnement CI, npm ci est généralement préféré car il installe strictement à partir du fichier lock et échoue si package.json et le lock ne concordent pas. Cela protège les builds contre la dérive accidentelle des dépendances.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}

16Comment les variables d'environnement et la configuration doivent-elles être gérées ?

Node.js lit les variables d'environnement via process.env, mais les valeurs sont des chaînes de caractères et doivent être validées et converties au démarrage. Les secrets ne doivent pas être stockés dans le dépôt ; .env doit être ignoré, et les secrets de production doivent provenir de la plateforme ou d'un gestionnaire de secrets. Un bon service échoue rapidement lorsque la configuration requise est manquante. NODE_ENV est une convention, non une frontière de sécurité, donc le code ne devrait pas en dépendre comme mécanisme protégé.

const port = Number(process.env.PORT ?? 3000);
const databaseUrl = process.env.DATABASE_URL;

if (!databaseUrl) {
  throw new Error('DATABASE_URL is required');
}

Événements, Tampons et Flux

17Qu'est-ce qu'EventEmitter ?

EventEmitter implémente un modèle de publication/abonnement (publish-subscribe) à l'intérieur d'un processus Node.js. `on` enregistre un écouteur, `once` enregistre un écouteur pour un événement unique, `off` supprime un écouteur et `emit` appelle les écouteurs de manière synchrone dans l'ordre d'enregistrement. La nature synchrone est importante : un écouteur lent bloque l'émetteur (emitter). L'événement d'erreur est spécial car un événement d'erreur non géré peut faire planter le processus. Les systèmes à longue durée de vie doivent également supprimer les écouteurs inutilisés pour éviter les fuites de mémoire.

import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

emitter.on('orderCreated', order => {
  console.log(`Order ${order.id} created`);
});

emitter.emit('orderCreated', { id: 42 });

18À quoi sert Buffer ?

Buffer représente une séquence de bytes de taille fixe. Il est utilisé pour les fichiers, les sockets TCP, les corps HTTP, les images, la cryptographie et les protocoles binaires. Buffer.from crée un tampon à partir de données existantes, Buffer.alloc crée une mémoire remplie de zéros, et Buffer.allocUnsafe est plus rapide mais peut contenir d'anciennes données jusqu'à ce qu'elles soient écrasées. Cela ne rend allocUnsafe approprié que lorsque le code remplit immédiatement le tampon et n'expose jamais de données périmées.

const buffer = Buffer.from('Hello', 'utf8');

console.log(buffer);
console.log(buffer.toString('utf8'));

const safe = Buffer.alloc(1024);
const fast = Buffer.allocUnsafe(1024);

19Qu'est-ce que les flux (streams) et quels types existent ?

Les flux traitent les données par morceaux au lieu de tout charger en mémoire. Les principaux types sont Readable, Writable, Duplex et Transform. Des exemples courants incluent les fichiers, les objets de requête et de réponse HTTP, les sockets TCP et les flux gzip. Les flux sont précieux pour les gros fichiers et le trafic réseau car ils réduisent l'utilisation de la mémoire et la latence. Une réponse complète doit mentionner que les erreurs de flux doivent être gérées correctement sur toute la chaîne.

import fs from 'node:fs';

const input = fs.createReadStream('large.log');
const output = fs.createWriteStream('copy.log');

input.pipe(output);

20Qu'est-ce que la backpressure et pourquoi le pipeline est-il utile ?

La backpressure se produit lorsqu'une source de données produit des morceaux plus rapidement que la destination ne peut les consommer. Sans backpressure, la mémoire peut croître jusqu'à ce que le processus ralentisse ou plante. Les flux (Streams) et les tuyaux (pipe) peuvent coordonner le débit, mais pipeline provenant de node:stream/promises est plus sûr pour les chaînes multi-étapes car il propage les erreurs, ferme les flux associés et renvoie une Promise. C'est un bon choix par défaut pour la compression de fichiers, les téléversements, les téléchargements et les transformations de flux.

import fs from 'node:fs';
import zlib from 'node:zlib';
import { pipeline } from 'node:stream/promises';

await pipeline(
  fs.createReadStream('input.txt'),
  zlib.createGzip(),
  fs.createWriteStream('input.txt.gz')
);