Menu

Questions d'entretien TypeScript : 25 réponses avec code

25 questions d'entretien TypeScript avec des réponses courtes, justes et accompagnées de code, classées du niveau débutant au niveau avancé : any ou unknown, interface ou type, génériques, narrowing, types utilitaires, mapped types et types conditionnels, typage structurel, tsconfig et TypeScript 7.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Voici les questions TypeScript que les recruteurs posent le plus, avec la réponse courte que vous devriez savoir donner et un petit exemple. Elles sont classées par niveau ; un poste junior s'arrête en général après le premier groupe, et un poste senior attend que vous écriviez de mémoire les types du dernier groupe.

Questions de niveau débutant

1. Qu'est-ce que TypeScript ?

TypeScript est du JavaScript avec des types statiques, créé par Microsoft. Vous annotez les valeurs avec des types, le compilateur les vérifie, puis il retire les types et produit du JavaScript simple qui s'exécute partout où JavaScript s'exécute.

Sans le commentaire @ts-expect-error, le second appel est une erreur de compilation, donc le bug n'arrive jamais en production.

2. TypeScript vérifie-t-il les types à l'exécution ?

Non. Les types sont effacés à la compilation ; la sortie n'en garde aucune trace. L'exemple ci-dessus affiche 23 parce que rien, à l'exécution, ne sait que a devrait être un nombre. Pour vérifier des données venant de l'extérieur du programme (JSON, saisies utilisateur, réponses d'API), vous écrivez de vraies vérifications, comme un type guard ou un validateur de schéma.

3. Quels sont les types de base ?

string, number (un seul type pour les entiers et les flottants ; il n'y a pas de int), boolean, bigint, symbol, null et undefined, plus les tableaux (number[]), les tuples ([string, number]), les types objet, any, unknown, never et void. Utilisez les noms en minuscules : String et Number sont les types des objets enveloppes.

4. Quelle est la différence entre any et unknown ?

Les deux acceptent n'importe quelle valeur. any désactive en plus la vérification, donc toute opération compile. unknown n'autorise rien tant que vous n'avez pas restreint son type, ce qui en fait le type sûr pour les valeurs pas encore vérifiées.

5. Qu'est-ce que l'inférence de types ?

Le compilateur détermine les types que vous n'avez pas écrits : let count = 0 est un number, const mode = "dark" a le type littéral "dark", et le type de retour d'une fonction vient de ses instructions return. La règle habituelle : annoter les paramètres de fonctions et les types de retour publics, et laisser l'inférence gérer les variables locales.

6. Quelle est la différence entre interface et type ?

Les deux décrivent des formes d'objets, et une classe peut faire implements de l'un comme de l'autre. Une interface peut être rouverte et fusionnée (declaration merging) et peut faire extends d'autres interfaces. Un alias type peut nommer n'importe quoi : unions, tuples, primitifs, mapped types et types conditionnels.

interface User { name: string }
interface User { age: number }       // merged: User has name and age

type Id = string | number;           // only a type alias can be a union
type Pair = [string, number];        // or a tuple
type Admin = User & { role: "admin" };

Une convention courante : interface pour les formes d'objets et type pour tout le reste. La comparaison complète est sur la page interface ou type.

7. Que sont les types union et intersection ?

Une union A | B est une valeur qui est l'un de ces types ; vous ne pouvez utiliser que les membres communs à tous tant que vous n'avez pas restreint son type. Une intersection A & B est une valeur qui est les deux à la fois, avec tous les membres de chacun.

type Id = string | number;                 // either
type Timestamped = { createdAt: Date };
type Post = { title: string } & Timestamped; // both: title and createdAt

8. Quelle est la différence entre void et never ?

void est le type de retour d'une fonction qui se termine normalement sans valeur utile. never est le type de ce qui ne peut pas arriver : une fonction qui lève toujours une exception ou boucle à l'infini, ou une union dont tous les cas ont été retirés. never est affectable à tous les types, et aucune valeur n'est affectable à never.

function log(msg: string): void { console.log(msg); }
function fail(msg: string): never { throw new Error(msg); }
type Impossible = string & number; // never

Questions de niveau intermédiaire

9. Qu'est-ce que le narrowing ?

Le narrowing, c'est le compilateur qui suit vos tests et affine un type dans chaque branche. Il comprend typeof, instanceof, in, les comparaisons d'égalité, la valeur truthy et les type guards définis par l'utilisateur.

10. Qu'est-ce qu'un type guard défini par l'utilisateur ?

Une fonction dont le type de retour est value is T. Quand elle renvoie true, la variable de l'appelant est restreinte à T. Le compilateur fait confiance à la fonction, donc son corps doit vraiment vérifier la valeur.

interface Cat { meow(): void }

function isCat(value: unknown): value is Cat {
    return (
        typeof value === "object" &&
        value !== null &&
        "meow" in value &&
        typeof value.meow === "function"
    );
}

Une fonction d'assertion, function assertCat(v: unknown): asserts v is Cat, restreint le type en levant une exception au lieu de renvoyer false.

11. Que sont les génériques ?

Des paramètres de type qui permettent à une fonction, une classe ou un type de fonctionner avec de nombreux types tout en gardant le lien entre l'entrée et la sortie. T est déduit des arguments, et extends restreint ce qu'il peut être.

pluck(users, "email") serait une erreur de compilation, car "email" n'est pas un keyof du type utilisateur.

12. Que font keyof et typeof dans un type ?

keyof T est l'union des noms de propriétés de T. En position de type, typeof x donne le type d'une variable. Ensemble, keyof typeof obj transforme les clés d'un objet en union.

const colors = { red: "#f00", green: "#0f0" };
type Colors = typeof colors;        // { red: string; green: string }
type ColorName = keyof typeof colors; // "red" | "green"

13. Que sont les types utilitaires ? Citez-en quelques-uns.

Des types génériques intégrés qui transforment d'autres types. Ceux sur lesquels on vous interroge le plus :

UtilitaireRésultat
Partial<T>toutes les propriétés optionnelles
Required<T>toutes les propriétés obligatoires
Readonly<T>toutes les propriétés readonly
Pick<T, "a" | "b">seulement les propriétés listées
Omit<T, "a">toutes les propriétés sauf celles listées
Record<K, V>un objet avec des clés K et des valeurs V
Exclude<U, X>, Extract<U, X>retirer ou garder des membres d'une union
NonNullable<T>T sans null ni undefined
ReturnType<F>, Parameters<F>le type de retour ou les types des paramètres d'une fonction
Awaited<T>la valeur avec laquelle une promesse se résout

La liste complète est sur la page types utilitaires.

14. Qu'est-ce qu'une union discriminée ?

Une union de types objet qui partagent une propriété « étiquette » littérale. Tester l'étiquette restreint la valeur à un seul membre, et une vérification never dans la branche default fait signaler par le compilateur tout cas oublié.

15. Quelle est la différence entre as et satisfies ?

value as T est une assertion de type : elle dit au compilateur de traiter la valeur comme un T et saute l'essentiel de la vérification. value satisfies T vérifie la valeur par rapport à T mais garde le type propre de la valeur, plus précis.

type Theme = { primary: string; secondary: string };

const a = { primary: "#07f" } as Theme;        // compiles: the missing key is not reported
const b = { primary: "#07f" } satisfies Theme; // error TS2741: Property 'secondary' is missing
const c = {} as { name: string };              // compiles; c.name is undefined at run time

Une fois l'objet complet, satisfies garde aussi son propre type déduit : vérifiée par rapport à Record<string, string>, la variable sait toujours exactement quelles clés elle a, là où une annotation l'élargirait à n'importe quelle clé de type chaîne.

16. Que fait l'opérateur ! après une variable ?

C'est l'assertion non nulle : el! retire null et undefined du type. Elle ne génère aucune vérification, donc si la valeur est en réalité null, le programme plante là où elle est utilisée. Préférez ?., ?? ou un if explicite.

17. Quelle est la différence entre private et #private ?

private n'est imposé que par le compilateur ; dans la sortie JavaScript, la propriété est une propriété ordinaire. #field est un champ privé JavaScript, imposé par le moteur d'exécution.

protected fonctionne comme private mais autorise aussi l'accès depuis les sous-classes, et readonly interdit la réaffectation après la construction.

18. Quelle est la différence entre une classe abstraite et une interface ?

Une interface n'est qu'un type : elle décrit une forme et disparaît de la sortie. Une classe abstraite est une vraie classe qui ne peut pas être instanciée ; elle peut contenir des méthodes implémentées, des champs et des constructeurs à côté de membres abstract que les sous-classes doivent implémenter. Une classe peut implémenter plusieurs interfaces mais n'étendre qu'une seule classe.

abstract class Repository<T> {
    protected items: T[] = [];
    abstract validate(item: T): boolean; // subclasses must implement
    add(item: T): void {                 // shared implementation
        if (this.validate(item)) this.items.push(item);
    }
}

19. Qu'est-ce que la surcharge de fonctions en TypeScript ?

Plusieurs signatures d'appel suivies d'une seule implémentation dont la signature est compatible avec toutes. Les appelants ne voient que les surcharges, donc chaque appel obtient un type de retour précis.

function parse(value: string): number;
function parse(value: string[]): number[];
function parse(value: string | string[]): number | number[] {
    return Array.isArray(value) ? value.map(Number) : Number(value);
}

const one = parse("4");          // number
const many = parse(["1", "2"]);  // number[]

Quand le type de retour ne dépend pas du type de l'argument, une seule signature avec un paramètre en union est plus simple.

Questions de niveau avancé

20. Qu'est-ce que le typage structurel ?

TypeScript compare les types par leur forme, pas par leur nom. Toute valeur qui possède les propriétés requises est acceptée, même si elle n'a jamais été déclarée avec ce type.

Le color en trop ne pose pas de problème ici, car pixel est une variable. Passer directement le littéral objet, show({ x: 3, y: 4, color: "red" }), provoque une erreur de propriété en excès, une vérification qui ne s'applique qu'aux littéraux frais. Quand deux types de même forme ne doivent pas se mélanger (un UserId et un OrderId qui sont tous deux des chaînes), utilisez un branded type : type UserId = string & { readonly __brand: "UserId" }.

21. Comment implémenteriez-vous vous-même Readonly et Partial ?

Avec des mapped types : parcourez keyof T et ajoutez un modificateur à chaque propriété.

type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
type MyPartial<T> = { [K in keyof T]?: T[K] };
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // "-" removes a modifier

type User = { name: string; age: number };
type Draft = MyPartial<User>; // { name?: string; age?: number }

22. Que sont les types conditionnels et infer ?

T extends U ? X : Y choisit un type selon une condition. Dans la condition, infer déclare une variable de type qui capture une partie du type reconnu. Les types conditionnels se distribuent sur les unions.

type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never;
type ElementOf<T> = T extends (infer E)[] ? E : T;

type A = MyReturnType<() => Promise<number>>; // Promise<number>
type B = ElementOf<string[]>;                 // string
type C = ElementOf<number | boolean[]>;       // number | boolean (distributed)

D'autres patterns sont présentés sur la page types conditionnels.

23. Qu'est-ce qu'un fichier .d.ts, et que fait declare ?

Un fichier de déclaration ne contient que les types d'un code qui existe ailleurs, comme une bibliothèque JavaScript ou les API du navigateur. declare affirme qu'une valeur existe sans la créer : declare const VERSION: string; ne se compile en rien. Les types des bibliothèques sont livrés avec le paquet ou viennent des paquets @types/{name}, et tsc génère des fichiers .d.ts pour votre propre code avec declaration: true.

24. Que fait strict, et quelles options du tsconfig comptent le plus ?

"strict": true active noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables et strictBuiltinIteratorReturn. Dans TypeScript 7, il est activé par défaut. Les autres options sur lesquelles les recruteurs posent des questions :

OptionRéponse en une ligne
targetla version de JavaScript utilisée par la sortie
modulele format de module de la sortie : nodenext suit les règles de Node, esnext/preserve gardent import pour un bundler
moduleResolutioncomment les imports sont trouvés : nodenext veut ./file.js dans les fichiers module ES, bundler accepte ./file
noUncheckedIndexedAccessarr[i] inclut undefined dans son type
pathsdes alias d'import pour le vérificateur de types seulement ; la sortie garde l'alias, donc un bundler ou le champ imports de Node doit le résoudre
skipLibCheckne pas vérifier les fichiers .d.ts, pour gagner du temps
verbatimModuleSyntaxles imports utilisés seulement comme types doivent être marqués type ; les autres imports sont gardés tels qu'écrits

25. Qu'est-ce que TypeScript 7 ?

Le compilateur TypeScript réécrit en Go sous forme de programme natif, publié comme version 7 du paquet npm typescript. La commande reste tsc et le langage est le même, mais il est beaucoup plus rapide que le compilateur 6.x écrit en JavaScript : l'équipe TypeScript annonce des gains de 8x à 12x sur des builds complets typiques, grâce au code natif et au multithreading à mémoire partagée. Il applique aussi les suppressions que la 6.0 avait dépréciées ; par exemple baseUrl, outFile, moduleResolution: "node10", module: "amd" et alwaysStrict: false sont désormais des erreurs (TS5102, TS5108) qui vous demandent de les retirer.

Questions fréquentes

Quelles sont les questions d'entretien TypeScript les plus courantes ?

Celles qui reviennent le plus : la différence entre any et unknown, interface ou type, le fonctionnement des génériques, le narrowing et les type guards, ce que font les types utilitaires comme Partial, Pick et Omit, et si TypeScript vérifie les types à l'exécution (ce n'est pas le cas).

Quelles questions TypeScript pose-t-on aux développeurs seniors ?

Attendez-vous à écrire des types, pas seulement à les lire : implémenter Readonly ou ReturnType avec des mapped types et des types conditionnels, expliquer le typage structurel et quand utiliser des branded types, concevoir une union discriminée avec une vérification d'exhaustivité, et discuter des choix de tsconfig comme strict, noUncheckedIndexedAccess et la résolution des modules.

Comment se préparer à un entretien TypeScript ?

Écrivez de petits programmes avec strict activé et lisez les erreurs du compilateur jusqu'à pouvoir les prévoir. Sachez expliquer le narrowing, les génériques avec contraintes, les principaux types utilitaires et pourquoi les assertions de type sont dangereuses, chaque fois avec un exemple de deux lignes.

TypeScript est-il demandé dans les entretiens React et Angular ?

En général, oui. Angular est lui-même écrit en TypeScript et les applications Angular aussi, donc les entretiens Angular le supposent souvent acquis, y compris les décorateurs comme @Component et les modificateurs d'accès. Les entretiens React pour une base de code TypeScript demandent souvent de typer les props, l'état et les gestionnaires d'événements, avec les mêmes interfaces, unions et génériques que ceux présentés ici.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER