Questions d'entretien Confirmé Ingénieur Frontend
Sur quoi porte un entretien Confirmé de Ingénieur Frontend, 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 profondeur sur les frameworks, la gestion d'état, des notions de performance et une livraison autonome de fonctionnalités.
Exemples de questions d'entretien pour Ingénieur Frontend
- TechniqueExpliquez l'event loop du navigateur et en quoi les microtâches diffèrent des macrotâches.Ce qu'une bonne réponse couvre
- Boucle d'événements du navigateur (event loop)
- Différence microtâches vs macrotâches
- Ordre d'exécution : macrotâche → microtâches → rendu
- Exemples : setTimeout (macrotâche), Promise.then (microtâche)
- Impact sur le rendu et la réactivité
Voir un exemple de réponse
L'event loop du navigateur est un mécanisme qui permet d'exécuter du JavaScript de manière asynchrone et non bloquante. Elle traite les tâches depuis une file d'attente. Les macrotâches incluent des événements comme les clics, les requêtes réseau, ou setTimeout. Les microtâches sont des tâches plus prioritaires comme les callbacks de Promise, MutationObserver ou queueMicrotask. L'event loop exécute d'abord une macrotâche, puis traite toutes les microtâches en attente avant de passer au rendu éventuel. Cela signifie que les microtâches sont exécutées juste après la macrotâche courante, avant le prochain rendu, ce qui peut bloquer le rendu si elles sont trop nombreuses. Une erreur courante est de placer du code synchrone lourd dans une microtâche, ce qui retarde le rendu et dégrade la réactivité. Comprendre cette différence est crucial pour éviter les freezes et optimiser les performances.
- TechniqueQu'est-ce qui déclenche un nouveau rendu dans React, et comment éviter ceux qui sont inutiles ?Ce qu'une bonne réponse couvre
- Modification de state/props via setState ou useState
- Re-rendu du parent entraînant celui des enfants
- React.memo et useMemo pour éviter les re-rendus inutiles
- useCallback pour stabiliser les références de fonctions
- Key prop stable pour les listes
Voir un exemple de réponse
Dans React, un nouveau rendu est déclenché lorsque l'état (state) ou les propriétés (props) d'un composant changent. Cela peut aussi venir d'un parent qui se re-rend, forçant ses enfants à se re-rendre même si leurs props n'ont pas changé. Pour éviter les re-rendus inutiles, on peut utiliser React.memo sur les composants fonctionnels pour ne les re-rendre que si leurs props changent (shallow comparison). useMemo permet de mémoriser des valeurs calculées, et useCallback de mémoriser des fonctions pour éviter de créer de nouvelles références à chaque rendu. Il est aussi important d'utiliser des clés (key) stables dans les listes pour que React identifie correctement les éléments. Une optimisation excessive peut toutefois complexifier le code ; il faut mesurer d'abord avec React DevTools pour identifier les vrais goulots d'étranglement.
- TechniqueDécrivez comment la cascade CSS résout les règles en conflit.Ce qu'une bonne réponse couvre
- Spécificité : sélecteurs inline > ID > classe > élément
- Ordre de déclaration : la dernière règle l'emporte à spécificité égale
- !important outrepasse la spécificité normale
- Héritage : certaines propriétés se transmettent aux enfants
- Règles @media et styles external/inline
Voir un exemple de réponse
La cascade CSS résout les conflits de règles en combinant trois critères : la spécificité, l'importance (via !important) et l'ordre de déclaration. La spécificité se calcule en comptant les sélecteurs : les sélecteurs d'ID (p. ex. #header) priment sur les classes (.menu), qui priment sur les éléments (div). Pour des règles de même spécificité, la dernière déclaration dans le code source l'emporte. L'usage de !important annule cette logique en donnant une priorité maximale, mais il est déconseillé car il rend le débogage difficile. L'héritage permet aux enfants de recevoir certaines propriétés (comme color) si elles ne sont pas définies explicitement. Enfin, les styles inline ont une spécificité très élevée, mais peuvent être contournés par !important. Comprendre la cascade évite les confusions et permet de structurer les feuilles de style de manière prévisible.
- ProgrammationImplémentez une fonction debounce et expliquez quand vous l'utiliseriez.Ce qu'une bonne réponse couvre
- Fonction debounce avec closure et timer
- Paramètres : délai et option leading/trailing
- Utilisation : input utilisateur, resize, scroll
- Complexité temporelle O(1) par appel, spatiale O(1)
- Implémentation avec clearTimeout et appel différé
Voir un exemple de réponse
Une fonction debounce permet de limiter la fréquence d'exécution d'une fonction en différant son appel jusqu'à ce qu'un délai se soit écoulé depuis le dernier déclenchement. On l'utilise typiquement pour éviter des appels réseau excessifs lors de la saisie dans un champ de recherche, ou pour réduire les calculs lors d'un redimensionnement. L'implémentation repose sur un timer stocké dans une closure. À chaque appel, on annule le timer précédent et on en crée un nouveau. On peut aussi gérer un déclenchement immédiat (leading) et/ou en fin de délai (trailing). La complexité en temps est O(1) par appel (annulation et création de timer), et en espace O(1). Une erreur courante est d'oublier de gérer this et les arguments dans la fonction retournée ; c'est pourquoi on utilise function ou une arrow function selon le contexte.
Solution de référencejavascript function debounce(func, delay, { leading = false, trailing = true } = {}) { let timer = null; let lastArgs = null; let isLeadingCalled = false; return function(...args) { const context = this; lastArgs = args; if (leading && !isLeadingCalled) { func.apply(context, args); isLeadingCalled = true; } clearTimeout(timer); timer = setTimeout(() => { if (trailing && lastArgs && !leading) { func.apply(context, lastArgs); } isLeadingCalled = false; lastArgs = null; }, delay); }; } // Exemple d'utilisation : const handleSearch = debounce((query) => { // Appel API console.log('Recherche :', query); }, 300); document.getElementById('searchInput').addEventListener('input', handleSearch); - ProgrammationConstruisez un composant d'autocomplétion accessible avec prise en charge du clavier.Ce qu'une bonne réponse couvre
- Composant contrôlé avec input et liste de suggestions
- ARIA : combobox, listbox, option, role=textbox
- Navigation clavier : flèches haut/bas, Enter, Escape
- Debounce sur la saisie pour éviter trop d'appels
- Gestion du focus et de l'état actif/sélectionné
Voir un exemple de réponse
Le composant d'autocomplétion accessible doit inclure un champ de texte avec un attribut role="combobox" et aria-expanded, une liste de suggestions avec role="listbox" et chaque option avec role="option". La navigation clavier doit permettre de se déplacer dans la liste avec les flèches, de sélectionner avec Enter, et de fermer avec Escape. On utilise un état pour stocker la valeur saisie, les suggestions filtrées et l'index de l'élément actif. Un debounce sur l'input évite de filtrer à chaque frappe. Il faut aussi gérer le focus : lorsque la liste s'ouvre, le focus reste sur l'input, mais l'utilisateur peut passer à la liste avec les flèches. Une erreur courante est d'oublier de mettre à jour aria-activedescendant pour indiquer l'option en surbrillance. L'accessibilité est améliorée en fournissant des instructions pour les lecteurs d'écran.
Solution de référencejavascript import React, { useState, useCallback, useRef, useEffect } from 'react'; const Autocomplete = ({ items, onSelect }) => { const [inputValue, setInputValue] = useState(''); const [filteredItems, setFilteredItems] = useState([]); const [activeIndex, setActiveIndex] = useState(-1); const [isOpen, setIsOpen] = useState(false); const inputRef = useRef(null); const listRef = useRef(null); // Filtrage avec debounce (implémentation simplifiée, utiliser lodash.debounce) useEffect(() => { const delay = setTimeout(() => { const filtered = items.filter(item => item.toLowerCase().includes(inputValue.toLowerCase()) ); setFilteredItems(filtered); setIsOpen(filtered.length > 0 && inputValue.length > 0); setActiveIndex(-1); }, 300); return () => clearTimeout(delay); }, [inputValue, items]); const handleKeyDown = useCallback((e) => { if (e.key === 'ArrowDown') { e.preventDefault(); setActiveIndex(prev => Math.min(prev + 1, filteredItems.length - 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setActiveIndex(prev => Math.max(prev - 1, -1)); } else if (e.key === 'Enter' && activeIndex >= 0) { e.preventDefault(); const selected = filteredItems[activeIndex]; setInputValue(selected); onSelect(selected); setIsOpen(false); } else if (e.key === 'Escape') { setIsOpen(false); inputRef.current.blur(); } }, [filteredItems, activeIndex, onSelect]); const handleChange = (e) => { setInputValue(e.target.value); }; const handleOptionClick = (item, index) => { setInputValue(item); onSelect(item); setIsOpen(false); setActiveIndex(index); }; return ( <div className="autocomplete"> <input ref={inputRef} type="text" role="combobox" aria-expanded={isOpen} aria-controls="autocomplete-list" aria-activedescendant={activeIndex >= 0 ? `option-${activeIndex}` : undefined} value={inputValue} onChange={handleChange} onKeyDown={handleKeyDown} placeholder="Rechercher..." /> {isOpen && ( <ul ref={listRef} id="autocomplete-list" role="listbox" className="suggestions"> {filteredItems.map((item, index) => ( <li key={item} id={`option-${index}`} role="option" aria-selected={index === activeIndex} className={index === activeIndex ? 'active' : ''} onClick={() => handleOptionClick(item, index)} > {item} </li> ))} </ul> )} </div> ); }; export default Autocomplete; - Conception de systèmesConcevez l'architecture frontend d'un éditeur de documents collaboratif en temps réel.Ce qu'une bonne réponse couvre
- Architecture client-serveur avec WebSocket pour temps réel
- Opérations transformées (OT) ou CRDT pour résoudre les conflits
- Structure de données : tableau de caractères avec opérations
- Gestion d'état local : proxy ou modèle immutable
- Optimisation du rendu : virtualisation, batching des opérations
Voir un exemple de réponse
Un éditeur collaboratif en temps réel nécessite une architecture client-serveur où chaque client envoie ses opérations (insertion, suppression) via WebSocket. Le serveur utilise un algorithme de résolution de conflits comme Operational Transformation (OT) ou CRDT (Conflict-free Replicated Data Types). OT est courant avec des systèmes comme ShareJS. Le client maintient un état local (par exemple un tableau de caractères) et applique les opérations reçues après transformation. Pour garantir la fluidité, on peut opter pour un modèle basé sur des opérations avec des identifiants uniques. Au niveau frontend, il faut virtuellement rendre le document (par exemple avec une liste d'éléments) et utiliser des techniques de virtualisation si le document est long. Les opérations doivent être batchées et envoyées périodiquement. Une erreur courante est de ne pas gérer correctement l'ordre des opérations ou d'ignorer les cas de concurrence. Des bibliothèques comme Quill ou Slate.js facilitent l'intégration.
- ComportementalParlez-moi d'une fois où vous avez amélioré la performance d'une page lente.Ce qu'une bonne réponse couvre
- STAR : Situation, Tâche, Action, Résultat
- Exemple concret de page lente identifiée via profiling
- Actions : lazy loading, code splitting, optimisation d'images, mise en cache
- Métriques : temps de chargement, First Contentful Paint, score Lighthouse
- Résultat : amélioration mesurée de X%
Voir un exemple de réponse
Dans mon précédent poste, nous avions une page produit qui mettait plus de 5 secondes à charger. J'ai utilisé Lighthouse et Chrome DevTools pour identifier les goulots : un bundle JavaScript de 2 Mo, des images non optimisées et des appels API redondants. J'ai mis en place du code splitting avec React.lazy et Suspense pour charger les composants lourds uniquement quand nécessaire, et ajouté un lazy loading pour les images via Intersection Observer. J'ai aussi mis en cache les réponses API avec un Service Worker. Résultat : le temps de chargement est passé à 1,8 seconde et le score Lighthouse de 45 à 92. J'ai aussi formé l'équipe à ces bonnes pratiques pour les projets futurs. L'important est de toujours mesurer avant et après pour valider l'impact.
- ComportementalComment gérez-vous les désaccords avec les designers sur les compromis UX ?Ce qu'une bonne réponse couvre
- STAR : Situation conflictuelle avec un designer
- Approche : écouter, proposer des données A/B, compromis
- Exemple : animation trop lourde vs performance
- Action : prototyper deux versions, tester utilisateurs
- Résultat : solution satisfaisante pour les deux parties
Voir un exemple de réponse
Lors d'un projet, un designer voulait une animation complexe avec des déplacements d'éléments en continu, mais cela dégradait les performances sur mobile (60 fps → 15 fps). J'ai expliqué les contraintes techniques avec des données de profiling. Nous avons convenu de tester deux versions : l'une avec l'animation complète, l'autre avec une version simplifiée utilisant CSS transforms. Après un test utilisateur, la version simplifiée offrait une meilleure expérience sur mobile sans perte notable d'impact visuel sur desktop. Nous avons donc adopté cette version. Mon rôle a été de rester à l'écoute, de proposer des compromis basés sur des preuves, et de travailler en collaboration pour trouver une solution qui respecte à la fois l'UX et la performance. L'essentiel est de cadrer le désaccord comme un problème à résoudre ensemble, pas comme une opposition.
Ce que les recruteurs évaluent
Fondamentaux JavaScript
Closures, event loop, promesses/async, prototypes et liaison de `this`.
Profondeur des frameworks
Rendu, réconciliation, hooks et gestion d'état avec React (ou Vue/Angular).
CSS et mise en page
Flexbox/grid, la cascade, les contextes d'empilement et le design responsive.
Performance
Chemin de rendu critique, taille du bundle, chargement différé et Core Web Vitals.
Accessibilité
HTML sémantique, ARIA, navigation au clavier et prise en charge des lecteurs d'écran.
Comment se préparer
- Décrivez le chemin de rendu à voix haute — les recruteurs notent votre raisonnement, pas seulement la réponse.
- Mentionnez toujours l'accessibilité et la performance, même sans qu'on vous le demande explicitement.
- Entraînez-vous à construire un composant de bout en bout sans l'autocomplétion de l'IDE.
Questions fréquentes
Quelles questions de programmation sont fréquentes en entretien frontend ?
Attendez-vous à de la manipulation du DOM, à des fonctions utilitaires comme debounce/throttle et à la construction d'un petit composant interactif tel qu'une autocomplétion ou une modale.
Les entretiens frontend incluent-ils de la conception de systèmes ?
Aux niveaux confirmé et senior, oui — généralement une conception orientée frontend comme un fil d'actualité, un design system ou un éditeur collaboratif, plutôt que de l'infrastructure backend.
Comment me préparer rapidement à un entretien frontend ?
Travaillez les fondamentaux de JavaScript, construisez quelques composants de zéro et faites des entretiens blancs chronométrés pour expliquer votre raisonnement sous pression.
Entraînez-vous aux questions de Ingénieur Frontend 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.