Préparation d'entretien 100% Gratuit angularforall.com

Entretien Node.js Senior — Performance, Streams et Scaling

Node-Js Senior Quiz-Nodejs-Senior Nodejs-Streams Worker-Threads Nodejs-Performance Scaling Nodejs-Securite Questions-Entretien-Nodejs-Senior Qcm-Nodejs-Avance Nodejs-Architecture Event-Loop-Avance Back-End Javascript-Avance
Sénior 🔀 Mixte 20 questions ⏱ 15 min
📝

Entretien Node.js Senior — Performance, Streams et Scaling

20 questions avancées Node.js : architecture backend, event loop, streams, worker threads, sécurité, scaling horizontal et patterns professionnels. Idéal pour réussir un entretien Node.js Senior.

20 questions ⏱ ~15 min Niveau Sénior

Partager

Banque de révision : les 25 questions corrigées

Voici l'intégralité des 25 questions de « Entretien Node.js Senior — Performance, Streams et Scaling », chacune avec sa bonne réponse et son explication. Le quiz interactif ci-dessus en tire une sélection aléatoire à chaque tentative — utilisez cette liste complète pour réviser.

  • timers → pending callbacks → idle/prepare → poll → check → close callbacks ; chaque phase a une file d'attente spécifique (bonne réponse)
  • macrotasks → microtasks → render
  • setTimeout → setImmediate → process.nextTick → I/O
  • Une seule phase qui traite tous les événements
Explication : L'Event Loop de Node.js a 6 phases principales : timers (setTimeout/setInterval), pending callbacks (I/O), idle/prepare (interne), poll (récupère nouveaux événements I/O), check (setImmediate), close callbacks (socket.on("close")). Chaque phase a sa propre file d'attente de callbacks.
Entre chaque phase, Node.js exécute les microtasks (process.nextTick, Promises résolues). nextTick a priorité sur les Promises.

  • nextTick s'exécute avant les Promises et avant la phase check ; setImmediate s'exécute dans la phase check, après les I/O (bonne réponse)
  • nextTick s'exécute après setImmediate
  • Ils sont équivalents
  • setImmediate s'exécute avant nextTick
Explication : process.nextTick() s'exécute immédiatement après la fin de l'opération en cours, avant même les Promises (microtasks), et avant la phase check. setImmediate() s'exécute dans la phase "check" de l'Event Loop, après les I/O du poll. setImmediate() est plus lent que nextTick().
Utilisez process.nextTick() pour des callbacks très prioritaires, mais attention à ne pas bloquer l'Event Loop avec des nextTick récursifs.

  • Le module cluster crée plusieurs processus enfants (workers) partageant le même port, permettant d'utiliser tous les cœurs CPU ; les workers sont indépendants, sans partage mémoire (bonne réponse)
  • Le cluster crée des threads partagés avec mémoire commune
  • Le cluster permet de multiplier les performances I/O mais pas CPU
  • Le cluster est automatique sans configuration
Explication : Le module cluster permet de créer plusieurs processus enfants (workers) qui partagent le même port. Chaque worker a sa propre instance de l'Event Loop, utilisant un cœur CPU différent. Les workers ne partagent pas la mémoire (communication via IPC). Limites : gestion d'état partagé (sessions, cache) nécessite Redis ou similaire.
Utilisez PM2 pour gérer facilement le clustering : pm2 start app.js -i max. Attention aux fuites mémoire qui tuent un worker à la fois.

  • Libuv est la bibliothèque C qui implémente l'Event Loop, les threads pool, les opérations asynchrones (fichiers, DNS, réseau) et la gestion des signaux (bonne réponse)
  • Libuv est un compilateur JavaScript
  • Libuv est un framework de test
  • Libuv est un gestionnaire de paquets
Explication : Libuv est la bibliothèque multiplateforme qui constitue le cœur de Node.js. Elle fournit l'Event Loop, le thread pool (4 threads par défaut pour les opérations bloquantes comme fs.readFile), la gestion des sockets, les timers, les signaux, et l'abstraction des différences système.
La taille du thread pool se configure avec UV_THREADPOOL_SIZE. Utile pour les applications très I/O intensives.

  • Utiliser heap snapshots avec Chrome DevTools via --inspect, analyser les growths avec des outils comme clinic.js ou memwatch-next, vérifier les closures et les event listeners non nettoyés (bonne réponse)
  • Redémarrer l'application régulièrement
  • Augmenter la mémoire avec --max-old-space-size
  • Désactiver le garbage collector
Explication : Pour diagnostiquer une fuite mémoire : 1) utiliser node --inspect et Chrome DevTools pour prendre des heap snapshots, 2) outils comme clinic.js (clinic heap), 3) memwatch-next pour détecter les leaks, 4) vérifier les closures qui retiennent des objets, les event listeners non supprimés, les caches sans éviction, les variables globales accidentelles.
Utilisez process.memoryUsage() pour surveiller la mémoire. Les fuites fréquentes : les tableaux/caches qui grandissent sans limite, les callbacks dans des observables, les timers non nettoyés.

  • Readable : lecture ; Writable : écriture ; Duplex : lecture ET écriture ; Transform : duplex qui modifie les données (comme un filtre) (bonne réponse)
  • Readable et Writable sont identiques
  • Duplex est juste un alias de Transform
  • Transform ne peut que lire
Explication : Streams : Readable (ex: fs.createReadStream), Writable (fs.createWriteStream), Duplex (net.Socket - lecture+écriture indépendantes), Transform (zlib.createGzip - modifie les données en transit). Les streams sont essentiels pour traiter de gros volumes de données sans charger tout en mémoire.
Utilisez stream.pipeline() (ou .pipe()) pour chaîner des streams. Préférez pipeline pour une meilleure gestion d'erreurs.

  • Le backpressure se produit quand un stream writable ne peut pas traiter les données aussi vite que le stream readable les fournit. Le readable pause automatiquement (highWaterMark) (bonne réponse)
  • Le backpressure est ignoré par Node.js
  • Il faut augmenter la mémoire pour le résoudre
  • Le backpressure est une erreur fatale
Explication : Le backpressure (contre-pression) est le mécanisme automatique des streams pour éviter de surcharger la mémoire. Chaque stream a un highWaterMark. Si le buffer interne dépasse, readable.pause() est appelé. Le writable.write() retourne false pour indiquer qu'il faut attendre l'événement drain.
Lorsque vous utilisez .pipe(), le backpressure est géré automatiquement. Avec des streams personnalisés, implémentez _write() et _read() correctement.

  • Cluster : plusieurs processus (IPC) ; Worker Threads : threads légers dans le même processus (partage mémoire via SharedArrayBuffer) (bonne réponse)
  • Worker Threads sont plus lourds que cluster
  • Cluster permet le partage mémoire, pas Worker Threads
  • Ils sont identiques
Explication : Cluster : multi-processus, chaque worker a sa propre mémoire, communication via IPC, idéal pour utiliser tous les cœurs CPU pour les requêtes HTTP. Worker Threads : threads légers dans le même processus, partagent la mémoire (avec SharedArrayBuffer, Atomics), plus efficaces pour les calculs CPU intensifs.
Utilisez Worker Threads pour le calcul lourd (image processing, crypto) ; utilisez Cluster pour les serveurs HTTP haute concurrence.

  • V8 utilise un GC générationnel (Scavenger + Mark-Compact) ; on peut optimiser avec --max-old-space-size, --expose-gc, et en réduisant les allocations dans les boucles chaudes (bonne réponse)
  • Le GC est automatique et parfait, sans réglage
  • Node.js n'a pas de GC
  • Le GC bloque toujours l'exécution pendant plusieurs secondes
Explication : V8 a un GC générationnel : New Space (Scavenger - rapide) pour les objets jeunes, Old Space (Mark-Compact - plus lent) pour les objets anciens. Flags utiles : --max-old-space-size (limite mémoire), --expose-gc (pour appeler global.gc()), --trace-gc (logs). Optimisations : réutiliser les objets plutôt que d'en créer, éviter les closures qui retiennent des objets, réduire les allocations dans les boucles.
Surveillez la mémoire avec --trace-gc et --trace-gc-verbose. Pour les applications temps réel, réduisez la taille du New Space pour des GC plus fréquents mais plus courts.

  • async_hooks permet de tracer les ressources asynchrones (timers, promises, I/O) pour corréler les logs, le tracing distribué, ou la gestion automatique de contextes (CLS) (bonne réponse)
  • async_hooks est un remplacement de Promise
  • async_hooks sert à rendre le code synchrone
  • async_hooks est déprécié
Explication : Le module async_hooks permet de suivre le cycle de vie des ressources asynchrones. Utile pour : le tracing distribué (corréler des requêtes à travers les callbacks), les logs contextuels (propager un requestId), les ORM avec contexte automatique, la gestion de sessions sans passer l'objet partout.
Attention : async_hooks a un coût en performance (désactivé par défaut pour cette raison). Utilisez-le avec parcimonie ou en développement.

  • Helmet.js pour les en-têtes, validation des entrées (Joi/validator), rate limiting (express-rate-limit), CSRF tokens (csurf), sanitization, et ORM/paramètres préparés pour SQL (bonne réponse)
  • Désactiver toutes les vérifications
  • Un seul mot de passe suffit
  • Node.js est sécurisé par défaut
Explication : Sécurisation : Helmet (en-têtes de sécurité), validation/sanitization des entrées (Joi, express-validator), rate limiting (express-rate-limit, slow down), CSRF (csurf), XSS (sanitize-html, échappement), injection SQL (utiliser ORM ou paramètres préparés), authentification forte (bcrypt, JWT sécurisé), logging des tentatives.
Activez aussi : app.disable("x-powered-by"), utilisez HTTPS, gérez les CORS restrictifs, et faites des audits réguliers avec npm audit.

  • node --inspect ouvre un websocket pour Chrome DevTools ; permet profiling CPU, heap snapshots, debugging asynchrone, coverage, et post-mortem debugging (bonne réponse)
  • node --debug (déprécié)
  • Le debugging se fait uniquement avec console.log
  • Il n'y a pas d'outil de profiling
Explication : Le module inspector (--inspect) permet d'utiliser Chrome DevTools sur une application Node.js. Fonctionnalités : breakpoints (y compris async), profiler CPU (flame graphs), heap snapshots (memory leaks), coverage (code non exécuté), post-mortem avec --inspect-brk, profiling de performance.
Pour une analyse en production : node --inspect app.js et connectez-vous à chrome://inspect. Pour les conteneurs, exposez le port 9229.

  • perf_hooks donne accès à l'API Performance Timing (high-resolution timers) et permet de créer des marqueurs (performance.mark/measure) pour mesurer les temps d'exécution précis (bonne réponse)
  • perf_hooks est un module déprécié
  • perf_hooks sert à optimiser automatiquement le code
  • perf_hooks est pour le front-end uniquement
Explication : Le module perf_hooks expose l'API Performance Web. performance.now() donne des timers haute résolution (microsecondes). performance.mark() et performance.measure() permettent de mesurer les temps entre deux marqueurs. perf_hooks.performance donne aussi accès aux métriques d'Event Loop.
Utilisez perf_hooks pour mesurer des opérations critiques (temps de requête DB, temps de traitement). Combine avec async_hooks pour le tracing distribué.

  • Centraliser la gestion des erreurs (process.on uncaughtException/unhandledRejection), utiliser des domaines ou des middlewares d'erreur, logger structuré, redémarrer le processus (PM2), et fail fast (bonne réponse)
  • Ignorer les erreurs
  • Tout mettre dans des try/catch
  • L'application doit continuer à tout prix
Explication : Bonnes pratiques : 1) process.on("uncaughtException") et unhandledRejection pour capturer les erreurs fatales, logger, puis redémarrer (PM2). 2) Middleware d'erreur Express (4 paramètres). 3) Logs structurés (Winston, Pino) avec niveau, trace. 4) Fail fast : l'application ne doit pas continuer après une erreur fatale. 5) Utiliser des retry pour les opérations temporaires.
Ne jamais faire : process.on("uncaughtException", () => {}) sans redémarrer. L'état de l'application est alors indéterminé.

  • spawn (streams, long-running), exec (buffer, petite sortie), execFile (exécute fichier), fork (spawn spécial pour Node.js avec IPC) (bonne réponse)
  • Tous sont identiques
  • spawn est le plus lent
  • fork est déprécié
Explication : spawn : flux (streams), idéal pour les processus longs (sortie volumineuse). exec : buffer en mémoire, pour les commandes avec petite sortie. execFile : comme exec mais sans shell (plus sûr). fork : spawn spécial pour les scripts Node.js, crée un canal IPC pour communiquer.
Utilisez spawn pour les commandes avec gros volume de données, exec pour les commandes simples. Préférez execFile pour éviter les injections shell.

  • Quand une opération CPU-intensive bloque l'Event Loop, empêchant les I/O. Solutions : déléguer à Worker Threads, découper avec setImmediate/setTimeout, utiliser des batchs (bonne réponse)
  • C'est une erreur normale
  • Augmenter la RAM
  • Désactiver l'Event Loop
Explication : L'Event Loop starvation se produit quand une opération synchrone longue bloque la boucle, empêchant les requêtes entrantes de répondre. Solutions : 1) déléguer le calcul CPU intensif aux Worker Threads, 2) découper en petits blocs avec setImmediate() (yield), 3) utiliser des batchs avec setTimeout(,0) pour laisser la place aux I/O.
Exemple de découpage : function processBatch(items, index, callback) { ... if (index < items.length) setImmediate(() => processBatch(items, index+10, callback)); }

  • Node.js met en cache les modules après le premier require(). Le cache est partagé entre tous les require() du même module. Pour des instances isolées, utiliser des factories ou des fonctions qui retournent des nouvelles instances (bonne réponse)
  • Les modules ne sont jamais mis en cache
  • Chaque require() crée une nouvelle instance
  • Le cache est vidé à chaque requête HTTP
Explication : Quand on require() un module, Node.js exécute le code une fois et met en cache l'export. Les require() suivants retournent la même instance. Cela partage l'état. Pour des instances isolées, on peut exporter une fonction factory qui crée de nouvelles instances (module.exports = () => new MonModule()) ou utiliser des classes.
Pour invalider le cache (dangereux) : delete require.cache[require.resolve("./module")]. Utile pour le hot reload en développement uniquement.

  • Node.js retourne un export partiel (incomplet) pendant le cycle ; la référence sera complétée plus tard, mais peut causer des erreurs "undefined" si mal géré (bonne réponse)
  • Les dépendances circulaires sont interdites
  • Node.js les résout automatiquement sans problème
  • Le compilateur les ignore
Explication : Quand il y a un cycle, Node.js retourne un objet partiel (ce qui a déjà été exporté) puis continue. Exemple : module A require B, module B require A → A reçoit un export partiel de B. Cela peut causer des undefined si on accède à des propriétés qui ne sont pas encore définies. Solution : restructurer ou utiliser des imports paresseux (dans les fonctions).
Les imports ES avec import sont analysés statiquement et détectent les cycles à la compilation, mais peuvent aussi avoir des problèmes.

  • Créer un serveur HTTPS avec https.createServer({ key, cert }, app). Générer des certificats avec OpenSSL ou Let's Encrypt (bonne réponse)
  • http.createServer() supporte HTTPS par défaut
  • Node.js ne supporte pas HTTPS
  • Utiliser FTP à la place
Explication : Pour HTTPS : lire les fichiers key et cert (ou un fichier pfx). Exemple : const https = require("https"); const options = { key: fs.readFileSync("key.pem"), cert: fs.readFileSync("cert.pem") }; https.createServer(options, app).listen(443).
En production, utilisez un reverse proxy comme Nginx pour gérer TLS, ou Let's Encrypt avec certbot. Pour le développement, utilisez mkcert pour générer des certificats locaux valides.

  • vm crée des contextes isolés pour exécuter du code JavaScript dans un sandbox ; attention, ce n'est pas une sécurité absolue (possibilité d'échappement) (bonne réponse)
  • vm est un émulateur de machine virtuelle
  • vm permet d'exécuter du code Python
  • vm est déprécié
Explication : Le module vm permet d'exécuter du code JavaScript dans un contexte isolé (sandbox) avec des variables globales restreintes. Cas d'usage : exécution de plugins utilisateur, templates dynamiques, calculs sécurisés. Attention : vm n'est pas un sandbox parfait (des échappements sont possibles).
Pour un vrai sandbox, utilisez des conteneurs (Docker) ou des worker threads. Ne faites jamais confiance à vm seul pour exécuter du code non fiable.

  • Le pipeline gère automatiquement le backpressure. Si l'envoi HTTP est plus lent que la compression, le flux compressé met en pause la lecture du fichier (bonne réponse)
  • Le backpressure doit être géré manuellement
  • Le pipeline ignore le backpressure
  • Il faut augmenter la mémoire tampon
Explication : Les streams gèrent automatiquement le backpressure via le mécanisme de highWaterMark. Si le writable (HTTP) retourne false, le stream précédent (compression) arrête d'écrire, ce qui remonte jusqu'à la source (fichier) qui pause sa lecture. stream.pipeline() gère tout cela et aussi les erreurs.
Exemple : stream.pipeline(fs.createReadStream("big.txt"), zlib.createGzip(), res, (err) => { if (err) console.error(err); });

  • Un système de publication/subscription pour instrumenter le code sans le modifier (AOP) ; permet aux outils de monitoring de s'abonner à des événements internes (bonne réponse)
  • Un module de diagnostic médical
  • Un remplacement de console.log
  • Un débogueur graphique
Explication : Le module diagnostics_channel permet de créer des canaux nommés. Les bibliothèques peuvent publier des événements (channel.publish(data)) et les outils de monitoring peuvent s'y abonner (channel.subscribe(callback)) sans modifier le code source. Idéal pour l'instrumentation et le tracing.
Moins intrusif que async_hooks pour le tracing. Exemple : const channel = diagnostics_channel.channel("http.request"); channel.subscribe((data) => { console.log(data.url); });

  • Réduire la taille du bundle (tree shaking, minification), provisionner des lambdas "warmer" (ping toutes les X minutes), utiliser AWS Lambda SnapStart, optimiser les dépendances (dependencies vs devDependencies) (bonne réponse)
  • Augmenter la mémoire
  • Désactiver les logs
  • C'est impossible à optimiser
Explication : Stratégies : 1) réduire le bundle (esbuild, Webpack), 2) déplacer le code d'initialisation hors de la handler (outside handler = une fois par conteneur), 3) provisioned concurrency (AWS) pour garder des lambdas "chaudes", 4) Lambda SnapStart (AWS) pour sauvegarder/restaurer l'état mémoire, 5) minimiser les dépendances, 6) utiliser des langages plus rapides pour le cold start (Rust, Go).
Utilisez des outils comme @aws-lambda-powertools pour logger et tracer, et mesurez le cold start avec des métriques CloudWatch.

  • node --inspect, ouvrir chrome://inspect, prendre heap snapshot, comparer plusieurs snapshots pour voir les objets qui persistent (comparison view), analyser les retenues (retainers) (bonne réponse)
  • Utiliser console.log
  • Redémarrer l'application
  • Les heap snapshots ne fonctionnent pas avec Node.js
Explication : Processus : 1) lancer node --inspect app.js. 2) Ouvrir chrome://inspect. 3) Prendre un premier snapshot (baseline). 4) Faire des actions qui devraient libérer de la mémoire. 5) Prendre un second snapshot. 6) Mode "Comparison" pour voir les objets ajoutés ou non libérés. 7) Identifier les "retainers" (pourquoi l'objet est retenu).
Pour les applications en production, utilisez node --inspect app.js (attention à la performance). Pour les environnements conteneurisés, exposez le port 9229.

  • module.createRequire(filename) crée une fonction require personnalisée pour un chemin donné, permettant de charger des JSON ou modules ES depuis n'importe où (bonne réponse)
  • require fonctionne déjà pour tout
  • createRequire est déprécié
  • Les modules ES ne sont pas supportés
Explication : module.createRequire(import.meta.url) permet de créer une fonction require dans un module ES. Utile pour : charger des fichiers JSON (qui n'ont pas d'import standard), importer des modules CommonJS depuis ESM, ou charger des fichiers depuis des chemins dynamiques.
Exemple : import { createRequire } from "module"; const require = createRequire(import.meta.url); const data = require("./data.json");