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

Entretien JavaScript Senior — Event Loop et Prototypes

Javascript Senior Quiz-Javascript-Senior Event-Loop Microtasks Javascript-Prototypes Heritage-Js Async-Await Questions-Entretien-Javascript-Senior Qcm-Javascript-Avance Patterns-Es6 Closures-Avance Front-End Javascript-Avance
Sénior 🔀 Mixte 20 questions ⏱ 15 min
📝

Entretien JavaScript Senior — Event Loop et Prototypes

20 questions avancées JavaScript : event loop, microtasks, async/await, prototypes, closures complexes, patterns ES6+ et optimisations. Idéal pour réussir un entretien développeur Senior.

20 questions ⏱ ~15 min Niveau Sénior

Partager

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

Voici l'intégralité des 40 questions de « Entretien JavaScript Senior — Event Loop et Prototypes », 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.

  • var a une portée fonction/globale et est hoistée ; let/const ont une portée bloc (bonne réponse)
  • const ne peut jamais être modifié, même ses propriétés d'objet
  • let et var sont interchangeables dans tous les cas
  • var est réservé aux modules ES6
Explication : var est hoistée (déclaration remontée) et a une portée fonction. let et const ont une portée bloc ({}). const empêche la réassignation mais permet la mutation des propriétés d'un objet.
Illustre avec un exemple de boucle : for (var i…) vs for (let i…) pour montrer la différence concrète.

  • "object" (bonne réponse)
  • "null"
  • "undefined"
  • null
Explication : typeof null retourne "object" — c'est un bug historique de JavaScript datant de 1995 qui n'a jamais été corrigé pour maintenir la rétrocompatibilité.
Ce piège est classique en entretien. Précise que null === null est la bonne façon de tester un null.

  • Une fonction qui se ferme immédiatement après exécution
  • Une fonction qui garde accès aux variables de son scope parent, même après que ce scope ait terminé (bonne réponse)
  • Un bloc try/catch autour d'une fonction
  • Une fonction sans paramètres
Explication : Une closure permet à une fonction interne de capturer les variables de son scope lexical parent. Même si la fonction externe a retourné, la fonction interne conserve une référence vivante à ces variables.
Donne l'exemple du compteur : function counter() { let n = 0; return () => ++n; } — chaque appel à counter() crée un scope indépendant.

  • "" (bonne réponse)
  • []
  • 0
  • undefined
Explication : [] + [] convertit les deux tableaux en chaînes ("" + "") et retourne une chaîne vide "". La coercition de type en JS est source de nombreux pièges.
Enchaîne avec [] + {}"[object Object]" et {} + [] → dépend du contexte. Bonne démonstration de maîtrise.

  • Chaque objet a une propriété interne [[Prototype]] pointant vers un objet parent ; la recherche de propriétés remonte cette chaîne jusqu'à null (bonne réponse)
  • Les classes ES6 remplacent complètement le prototype chain
  • Le prototype chain ne s'applique qu'aux classes, pas aux objets littéraux
  • Chaque objet copie les propriétés de son prototype lors de sa création
Explication : En JS, chaque objet possède une référence interne [[Prototype]] (accessible via Object.getPrototypeOf()). Quand on accède à une propriété, JS remonte la chaîne des prototypes jusqu'à trouver la propriété ou atteindre null.
Distingue bien prototype chain (héritage délégué) des classes ES6 qui sont du sucre syntaxique par-dessus ce même mécanisme.

  • A, D, C, B (bonne réponse)
  • A, B, C, D
  • A, C, D, B
  • A, D, B, C
Explication : La call stack exécute d'abord le code synchrone (A, D). Les microtasks (Promise) passent avant les macrotasks (setTimeout) : C s'affiche avant B. Ordre final : A → D → C → B.
L'event loop est un sujet phare en entretien senior. Mentionne les notions de microtask queue (Promise, queueMicrotask) vs macrotask queue (setTimeout, setInterval, I/O).

  • === compare la valeur ET le type sans coercition ; == effectue une coercition de type avant la comparaison (bonne réponse)
  • Ils sont identiques dans un contexte strict ("use strict")
  • == compare uniquement les types
  • === est réservé aux comparaisons de chaînes
Explication : == (égalité lâche) applique la coercition de type : "5" == 5 est true. === (égalité stricte) compare valeur ET type sans conversion : "5" === 5 est false.
Recommande toujours === en production. Les exceptions légitimes à == sont rares — cite null == undefined comme exemple.

  • Aplatit récursivement un tableau imbriqué à n'importe quelle profondeur (bonne réponse)
  • Supprime les valeurs dupliquées d'un tableau
  • Trie un tableau de façon descendante
  • Convertit un tableau en Set
Explication : .flat(depth) aplatit un tableau jusqu'à la profondeur spécifiée. flat(Infinity) aplatit complètement [1, [2, [3, [4]]]][1, 2, 3, 4], quelle que soit la profondeur d'imbrication.

  • Appeler removeEventListener avec la référence exacte de la fonction quand l'élément est retiré du DOM (bonne réponse)
  • Utiliser exclusivement des fonctions fléchées dans addEventListener
  • Les event listeners sont automatiquement nettoyés par le garbage collector
  • Limiter le nombre d'event listeners à 10 par page
Explication : Un event listener conserve une référence à son callback ET à l'élément DOM. Si l'élément est retiré du DOM sans que le listener soit supprimé, il reste en mémoire. Il faut removeEventListener(type, refFonction) — d'où l'importance d'utiliser des références nommées, pas des lambdas anonymes.
Cite aussi l'AbortController (ES2021) comme alternative moderne pour supprimer plusieurs listeners en une fois.

  • 1 (bonne réponse)
  • 99
  • undefined
  • Lance une TypeError
Explication : Object.freeze() rend un objet immuable (lecture seule). La tentative de modification est silencieusement ignorée en mode normal. En mode strict ("use strict"), elle lève une TypeError.

  • Transformer une fonction à N arguments en une chaîne de fonctions prenant chacune 1 argument — utile pour la composition et la réutilisation partielle (bonne réponse)
  • Appeler une fonction avec un contexte this fixé
  • Mémoriser le résultat d'une fonction coûteuse
  • Enchaîner des Promises pour éviter le callback hell
Explication : Le currying transforme f(a, b, c) en f(a)(b)(c). Utile pour l'application partielle : const add5 = add(5) crée une nouvelle fonction à partir d'une fonction générique. Facilite la composition fonctionnelle.
Donne un exemple concret : const multiply = a => b => a * b; const double = multiply(2);

  • const result = { ...obj1, ...obj2 } (bonne réponse)
  • Object.assign(obj1, obj2)
  • obj1.merge(obj2)
  • JSON.merge(obj1, obj2)
Explication : Le spread operator { ...obj1, ...obj2 } crée un nouvel objet sans modifier les originaux (shallow copy). Object.assign(obj1, obj2) mute obj1 directement.

  • undefined = valeur non assignée par le moteur ; null = absence intentionnelle de valeur assignée par le développeur (bonne réponse)
  • Ce sont des synonymes en JavaScript moderne
  • null est retourné par les fonctions sans return
  • undefined s'utilise uniquement dans les comparaisons strictes
Explication : undefined signifie qu'une variable a été déclarée mais pas encore initialisée. null est une valeur explicite qui représente "aucune valeur" — c'est un choix du développeur. typeof undefined === "undefined" et typeof null === "object".
Mentionne null == undefined est true (coercition) mais null === undefined est false (strict).

  • Retourne l'opérande droite uniquement si la gauche est null ou undefined (bonne réponse)
  • Retourne l'opérande droite si la gauche est falsy (0, "", false, null, undefined)
  • Vérifie si une valeur est définie dans l'objet
  • Effectue une comparaison non stricte avec null
Explication : ?? ne déclenche que pour null et undefined, contrairement à || qui déclenche pour toute valeur falsy. Ainsi : 0 ?? "défaut" retourne 0, mais 0 || "défaut" retourne "défaut".

  • Attacher un seul listener sur un élément parent pour gérer les événements de tous ses enfants via la propagation — améliore les performances et gère les éléments dynamiques (bonne réponse)
  • Déléguer le traitement des événements à un Web Worker pour ne pas bloquer le thread principal
  • Empêcher la propagation des événements avec stopPropagation()
  • Utiliser addEventListener une seule fois pour l'ensemble du document
Explication : L'event delegation exploite le bubbling : un événement sur un enfant remonte jusqu'au parent. En attachant un listener unique sur le conteneur et en vérifiant event.target, on évite des listeners multiples et on gère les éléments ajoutés dynamiquement.
Exemple : une liste de 1000 items — un seul listener sur <ul>, pas 1000 listeners sur chaque <li>.

  • WeakMap accepte uniquement des clés objet et ne prévient pas le garbage collection ; Map accepte n'importe quel type de clé et est itérable (bonne réponse)
  • WeakMap est plus performant pour les lookup de chaînes
  • Map est réservé aux clés primitives ; WeakMap aux clés complexes
  • Il n'y a pas de différence pratique entre les deux
Explication : WeakMap : clés objets uniquement, références faibles (l'objet peut être garbage collecté si aucune autre référence n'existe), non itérable. Map : toutes clés, références fortes, itérable. Ideal pour stocker des métadonnées privées associées à des objets.

  • async/await est du sucre syntaxique sur les Promises ; une fonction async retourne toujours une Promise, et await suspend l'exécution jusqu'à la résolution (bonne réponse)
  • async/await crée un nouveau thread pour les opérations asynchrones
  • await bloque le thread principal JavaScript
  • async transforme une fonction synchrone en fonction synchrone plus lisible
Explication : Toute fonction async retourne une Promise implicitement. await "suspend" la fonction courante (sans bloquer le thread) jusqu'à la résolution de la Promise. Le code reprend dans la microtask queue. C'est une abstraction sur .then()/.catch().
Mentionne que le gestion d'erreurs se fait avec try/catch avec async/await, vs .catch() avec les Promises brutes.

  • Number.isNaN retourne false ; isNaN retourne true (bonne réponse)
  • Les deux retournent true
  • Number.isNaN retourne true ; isNaN retourne false
  • Les deux retournent false
Explication : Number.isNaN() vérifie si la valeur est exactement NaN (sans coercition). 'hello' n'est pas NaNfalse. La fonction globale isNaN() convertit d'abord en nombre : Number('hello') donne NaNtrue.

  • Ne pas modifier les données existantes mais créer de nouvelles versions — facilite le debug, prévient les effets de bord et permet la comparaison par référence (performance en frameworks réactifs) (bonne réponse)
  • Rendre toutes les variables const pour éviter les erreurs de réassignation
  • Utiliser Object.freeze() sur tous les objets de l'application
  • Désactiver le mode strict pour éviter les mutations accidentelles
Explication : L'immutabilité signifie qu'on ne modifie pas les données en place mais qu'on crée de nouvelles valeurs. Avantages : comparaison par référence (prev !== next dans React/Redux), historique des états, prévention des effets de bord. Outils : spread, Object.assign, Array.map/filter, Immer.js.
Lien direct avec la performance de React : si la référence n'a pas changé, pas de re-render. Montre ta connaissance des frameworks.

  • Retourne undefined si l'objet ou la propriété intermédiaire est null/undefined, au lieu de lancer une TypeError (bonne réponse)
  • Crée une propriété de façon optionnelle si elle n'existe pas
  • Effectue une comparaison optionnelle sans lever d'exception
  • Permet d'appeler une méthode uniquement si elle est définie dans le prototype
Explication : user?.address?.city est équivalent à user && user.address && user.address.city. Si une étape est null ou undefined, l'expression court-circuite et retourne undefined sans erreur. Fonctionne aussi pour les appels de méthodes : obj.method?.().
Complète souvent avec ?? pour fournir une valeur par défaut : user?.name ?? "Anonyme".

  • 0, 1, 2
  • 3, 3, 3 (bonne réponse)
  • undefined, undefined, undefined
  • 1, 2, 3
Explication : Avec var, la variable i a une portée fonction (ou globale). Au moment où les callbacks setTimeout s'exécutent (après la boucle), i vaut déjà 3. D'où trois fois 3. Avec let, chaque itération capture une nouvelle liaison → 0, 1, 2.
Solution classique : IIFE (function(i) { setTimeout(...) })(i) ou utiliser let.

  • La période entre l'entrée dans un bloc et la déclaration d'une variable let/const où celle-ci existe mais n'est pas accessible (bonne réponse)
  • Une zone mémoire réservée aux variables globales
  • Le temps d'exécution d'une fonction asynchrone avant sa résolution
  • L'intervalle entre deux cycles de l'event loop
Explication : La TDZ est la période où une variable déclarée avec let ou const existe (hoistée) mais n'est pas encore initialisée. Y accéder avant la déclaration lève une ReferenceError. var n'a pas de TDZ car initialisée à undefined.
Exemple : { console.log(x); let x = 1; } → ReferenceError.

  • seal empêche l'ajout/suppression de propriétés mais permet leur modification ; freeze rend tout immuable (ajout/suppression/modification) (bonne réponse)
  • Les deux sont identiques en mode strict
  • freeze fonctionne uniquement sur les tableaux, seal sur les objets
  • seal est plus restrictif que freeze
Explication : Object.seal() : empêche d'ajouter/supprimer des propriétés, mais on peut modifier les valeurs existantes. Object.freeze() : tout est bloqué (ajout/suppression/modification). En mode strict, les tentatives de modification lèvent une TypeError.

  • Utilise l'algorithme mark-and-sweep : marque les objets accessibles depuis les racines, puis supprime les non-marqués (bonne réponse)
  • Libère immédiatement la mémoire dès qu'une variable sort de portée
  • Nécessite des appels manuels à gc() comme en C++
  • Ne gère que les types primitifs, pas les objets
Explication : JS utilise un GC à marquage et balayage (mark-and-sweep). Les racines sont les variables globales, les références sur la pile, etc. L'algorithme parcourt récursivement les références, marque les objets atteignables, puis supprime ceux qui ne le sont pas.
Les cycles de références ne sont plus un problème avec ce mécanisme. Mentionne les WeakMap/WeakSet pour éviter de bloquer le GC.

  • ["h", "e", "l", "l", "o"] (bonne réponse)
  • ["hello"]
  • ["h","e","l","l","o"] mais avec une erreur silencieuse
  • SyntaxError
Explication : Le spread operator [...] fonctionne sur les itérables. Les chaînes sont itérables (charactère par charactère). ...'hello' donne "h","e","l","l","o" dans un tableau. C'est une alternative à String.split('').

  • Un objet avec 3 états (pending, fulfilled, rejected) ; .then() retourne une nouvelle Promise, ce qui permet le chaînage (bonne réponse)
  • Un objet qui bloque le thread jusqu'à résolution
  • Une fonction qui s'exécute toujours de façon synchrone
  • Un remplacement complet des callbacks sans possibilité d'erreur
Explication : Une Promise est un proxy pour une valeur future. États : pending (initial), fulfilled (succès), rejected (erreur). Chaque .then() ou .catch() retourne une nouvelle Promise, ce qui évite le callback hell et permet le chaînage fluide.
Mentionne que Promise.all(), Promise.race(), Promise.allSettled() sont des outils avancés pour orchestrer plusieurs Promises.

  • Crée un objet vide sans prototype (pas même Object.prototype) (bonne réponse)
  • Crée une copie superficielle de l'objet null
  • Retourne une erreur car null n'est pas un prototype valide
  • Crée un objet avec null comme unique propriété
Explication : Object.create(null) crée un objet sans aucune propriété héritée (pas de toString, hasOwnProperty, etc.). Utile pour créer un dictionnaire "propre" ou une map sans collisions de clés héritées.
Contrairement à {} qui hérite de Object.prototype.

  • Chaque module a son propre scope ; les exports sont des liaisons vivantes (live bindings) ; les imports sont hoistés (bonne réponse)
  • Les modules sont chargés de façon synchrone et bloquent le rendu
  • Les modules ES6 sont identiques aux modules CommonJS (require)
  • import/export ne fonctionne qu'en Node.js, pas dans le navigateur
Explication : Les modules ES6 sont statically analyzable (imports/export déterminés à la compilation). Les exports sont des live bindings : si la valeur change dans le module source, l'import reflète le changement. Par défaut, les modules sont en mode strict automatiquement.
Distinction avec CommonJS (require) : les exports sont des copies, et require peut être appelé dynamiquement.

  • Debouncing : attendre un temps d'inactivité pour exécuter ; Throttling : exécuter au plus une fois par intervalle (scroll, resize) (bonne réponse)
  • Throttling : ignorer tous les appels ; Debouncing : exécuter immédiatement
  • Les deux sont des synonymes pour limiter le taux d'appels
  • Ce sont des méthodes de débogage pour les fonctions asynchrones
Explication : Debouncing : exécute la fonction après un délai d'inactivité (idéal pour autocomplete). Throttling : garantit qu'une fonction ne s'exécute pas plus d'une fois par intervalle (idéal pour scroll, resize).
Donne des exemples : debounce pour un champ de recherche, throttle pour un écouteur de scroll.

  • true (bonne réponse)
  • false
  • SyntaxError
  • undefined
Explication : Les opérateurs de comparaison s'évaluent de gauche à droite. 1 < 2true, puis true < 31 < 3true. Donc résultat true. Piège : 3 > 2 > 1true > 11 > 1false.
Pour vérifier qu'un nombre est entre deux bornes : 2 < x && x < 5.

  • Le event loop coordonne l'exécution : d'abord le call stack, puis les microtasks (Promises), puis les macrotasks (setTimeout) (bonne réponse)
  • Le event loop exécute toutes les tâches dans l'ordre d'arrivée sans priorité
  • Les macrotasks sont toujours exécutées avant les microtasks
  • Le event loop ne s'applique qu'aux navigateurs, pas à Node.js
Explication : L'event loop vide d'abord la call stack. Ensuite, elle exécute toutes les microtasks (Promise callbacks, queueMicrotask). Puis elle prend une macrotask (setTimeout, setInterval, I/O). Cycle infini. Priorité : synchrone > microtasks > macrotasks.
Exemple important : setTimeout(fn, 0) n'est pas garanti à 0ms, mais minimum après la microtask queue.

  • Retourne un Set contenant {1, 2, 3} (valeurs uniques) (bonne réponse)
  • Retourne un tableau [1, 2, 3]
  • Retourne une erreur car Set n'accepte pas de tableau
  • Retourne un Set avec les doublons {1, 2, 2, 3}
Explication : Un Set stocke des valeurs uniques. L'initialisation avec un tableau itérable supprime automatiquement les doublons. Le résultat est un objet Set (itérable) contenant 1, 2, 3.
Technique courante pour dédoublonner un tableau : [...new Set(tableau)].

  • Les classes ES6 sont du sucre syntaxique par-dessus l'héritage prototypal ; extends utilise Object.create() et les prototypes internes (bonne réponse)
  • Les classes ES6 remplacent complètement les prototypes par un modèle de classes classique (comme Java)
  • Les classes ES6 ne supportent pas l'héritage multiple
  • Les méthodes de classe sont stockées sur chaque instance, pas sur le prototype
Explication : Les classes ES6 sont principalement du sucre syntaxique. class A extends B équivaut à A.prototype = Object.create(B.prototype). Le mécanisme sous-jacent reste la chaîne de prototypes. Le constructeur parent s'appelle avec super().
Mentionne que les méthodes de classe sont définies sur le prototype (non énumérables), contrairement aux propriétés d'instance.

  • "function" (bonne réponse)
  • "object"
  • "undefined"
  • "class"
Explication : typeof sur une fonction retourne "function". C'est un type spécial en JavaScript. Bien que les fonctions soient des objets (elles ont des propriétés et méthodes), typeof fait une exception pour elles.
Pour différencier une fonction d'un objet ordinaire, on utilise typeof fn === "function".

  • Technique de cache des résultats de fonctions coûteuses basée sur les arguments ; utile pour les calculs récursifs (Fibonacci) ou les appels API idempotents (bonne réponse)
  • Une méthode pour optimiser la mémoire en supprimant les variables inutilisées
  • Un pattern pour chaîner des fonctions asynchrones
  • Une technique de débogage pour tracer l'exécution
Explication : La mémoïsation stocke les résultats d'appels précédents dans un cache. Si la même entrée est fournie, on retourne le résultat mis en cache sans recalculer. Exemple : Fibonacci récursif passe de O(2^n) à O(n).
Implémentation simple : function memoize(fn) { const cache = {}; return (...args) => cache[args] ??= fn(...args); }.

  • some() retourne true si au moins un élément satisfait le prédicat ; every() retourne true seulement si tous les éléments le satisfont (bonne réponse)
  • some() modifie le tableau original, every() non
  • every() est plus rapide que some() sur les grands tableaux
  • Les deux méthodes retournent un nouveau tableau filtré
Explication : some() : OR logique (existe-t-il au moins un ?). every() : AND logique (tous sont-ils ?). Les deux s'arrêtent tôt (early exit) dès que la condition est déterminée.
Exemple : array.some(v => v > 10) ; array.every(v => v > 0).

  • Proxy permet d'intercepter et redéfinir les opérations fondamentales sur un objet (get, set, deleteProperty, apply, etc.) (bonne réponse)
  • Proxy est un design pattern pour créer des singletons
  • Proxy est un objet qui duplique un autre objet avec ses méthodes
  • Proxy est une alternative à Object.defineProperty() mais moins performante
Explication : Un Proxy enveloppe un objet cible et définit des traps (pièges) pour intercepter des opérations comme lecture de propriété (get), assignation (set), appel de fonction (apply), etc. Utilisé pour la validation, le logging, l'observabilité (Vue 3).
Exemple : new Proxy(obj, { get(target, prop) { return prop in target ? target[prop] : "default"; } }).

  • true
  • false (bonne réponse)
  • undefined
  • TypeError
Explication : Chaque appel à Symbol() crée un symbole unique et immuable, même si la description est identique. Deux symboles ne sont jamais égaux (=== ou ==). Utilisé pour créer des clés de propriétés uniques, non énumérables.
Pour obtenir le même symbole à partir d'une clé : Symbol.for("id") (symboles globaux partagés).

  • Une fonction qui s'exécute immédiatement après sa définition ; utilisée pour créer un scope privé et éviter la pollution de l'espace global (pré-ES6) (bonne réponse)
  • Une fonction qui s'invoque automatiquement à chaque tick de l'event loop
  • Une syntaxe pour déclarer des fonctions asynchrones auto-exécutables
  • Une fonction qui ne peut être appelée qu'une seule fois
Explication : Syntaxe : (function() { ... })(). L'IIFE crée un scope lexical isolé. Avant let/const, c'était la seule façon d'avoir des variables privées et d'éviter les conflits globaux. Aujourd'hui, les modules ES6 et les blocs avec let/const sont préférés.
Aujourd'hui utile dans certains patterns d'initialisation : const app = (function() { return { init() {} }; })();.

  • Crée un tableau [0, 1, 2, 3, 4] (bonne réponse)
  • Crée un tableau [undefined, undefined, undefined, undefined, undefined]
  • Crée un tableau [1, 2, 3, 4, 5]
  • Retourne une erreur car le premier argument n'est pas itérable
Explication : Array.from() crée un tableau à partir d'un objet itérable ou array-like. Un objet { length: 5 } est array-like. La fonction de mapping transforme chaque index en sa valeur. Résultat : [0, 1, 2, 3, 4].
Technique courante pour initialiser un tableau de taille n : Array.from({ length: n }, (_, i) => i).