Vol. 2 · № 01 · Un atelier TypeScriptA TypeScript workshop

L'effacement des types, ou la preuve qui s'évapore Type erasure, or the proof that evaporates

const u: User = JSON.parse(x) compile sans broncher — puis u.name plante. L'annotation : User n'a rien vérifié : à l'exécution, elle n'existe plus. Les types prouvent à la compilation, puis s'effacent. Tout le reste de la série tient dans cette phrase. const u: User = JSON.parse(x) compiles without a blink — then u.name throws. The : User annotation verified nothing: at runtime, it no longer exists. Types prove at compile time, then erase. The rest of the series fits inside that sentence.

AudienceAudience
Dev qui sait composer des types et croit encore qu'ils existent à l'exécution Dev who can compose types and still believes they exist at runtime
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●○○ EffacementRuntimeTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

L'annotation a promis une forme. À l'exécution, il n'y a personne.The annotation promised a shape. At runtime, there's no one there.

Quatre lignes qui compilent, et plantent. Le réflexe, après le Vol 1, est de croire qu'un : User garantit un User. Il ne garantit rien à l'exécution — parce qu'à l'exécution, il n'existe plus. Les types prouvent à la compilation, puis s'effacent. C'est la faille fondamentale, et le cœur de la série.Four lines that compile, and crash. The reflex, after Vol 1, is to believe a : User guarantees a User. It guarantees nothing at runtime — because at runtime, it no longer exists. Types prove at compile time, then erase. That's the fundamental gap, and the heart of the series.

La promesse qui mentThe promise that lies
type User = { name: string; age: number };

const u: User = JSON.parse('null');   // ✓ compile sans une plainte
console.log(u.name);                  // 💥 Cannot read properties of null

// L'annotation : User a promis une forme.
// JSON.parse a rendu any. Personne n'a vérifié. À l'exécution, u vaut null.
Ce qui franchit la frontièreWhat crosses the boundary
À la compilationAt compile time
type User = { name, age }
const u: User
— le compilateur voit la forme
⟶
effacementerasure
À l'exécutionAt runtime
type User
: User
u = null

Tout ce qui est type s'évapore à la frontière. Il ne reste que la valeur JavaScript nue — ici, un null que rien n'a arrêté.Everything that is a type evaporates at the boundary. Only the bare JavaScript value remains — here, a null nothing stopped.

Le réflexe du volumeThe volume's reflex

« Ce que j'écris prouve-t-il quoi que ce soit à l'exécution — ou s'évapore-t-il à la compilation ? » Une annotation est une preuve pour le compilateur, valable jusqu'à la frontière. Après, il n'y a que des valeurs."Does what I write prove anything at runtime — or does it evaporate at compile time?" An annotation is a proof for the compiler, valid up to the boundary. After that, there are only values.

Pourquoi ce volume est le cœur.Why this volume is the core.

C'est l'équivalent de la possession en Rust : la règle qui, une fois comprise, explique cent comportements. Pourquoi instanceof marche et pas : Type, pourquoi il faut un discriminant, pourquoi on valide à la frontière — tout découle de l'effacement. On ne commence pas par typeof : on commence par une valeur qui a menti.It's the equivalent of ownership in Rust: the rule that, once understood, explains a hundred behaviors. Why instanceof works and : Type doesn't, why you need a discriminant, why you validate at the boundary — all of it follows from erasure. We don't start with typeof: we start with a value that lied.

№ 02La surpriseThe surprise4 min

On voudrait demander « es-tu un User ? ». Il n'y a personne à qui demander.You'd love to ask "are you a User?". There's no one to ask.

Le premier réflexe pour se protéger du plantage du chapitre 1 : tester le type à l'exécution. Sauf qu'aucune syntaxe ne le permet — parce qu'il n'y a rien à tester. Une interface ne produit aucun JavaScript : elle n'existe nulle part au runtime. Le code compilé ne contient que des valeurs.The first instinct to guard against chapter 1's crash: test the type at runtime. Except no syntax allows it — because there's nothing to test. An interface produces no JavaScript: it exists nowhere at runtime. The compiled code holds only values.

Le test qui n'existe pasThe test that doesn't exist
interface User { name: string; age: number }

function handle(x: unknown) {
  if (x is User) { /* ... */ }      // ✗ n'existe pas comme test runtime
  if (typeof x === User) { /* ... */ } // ✗ 'User' only refers to a type, but is being used as a value here
}
// Aucune de ces lignes ne compile : on ne peut pas INTERROGER un type.

Un type vit dans un univers parallèle, consultable seulement par le compilateur. On ne peut pas écrire x is User comme condition, ni mettre un type derrière typeof : un type n'est pas une valeur, et seules les valeurs survivent.A type lives in a parallel universe, readable only by the compiler. You can't write x is User as a condition, nor put a type after typeof: a type is not a value, and only values survive.

Compiler, c'est effacer les types. Ce qui tourne dans le navigateur n'a jamais entendu parler de tes interfaces.To compile is to erase the types. What runs in the browser never heard of your interfaces.
№ 03La mécaniqueThe mechanics5 min

Ce qui est une valeur survit. Ce qui n'est qu'un type disparaît.What is a value survives. What is only a type disappears.

La ligne de partage est nette : tout ce qui existe aussi comme valeur JavaScript franchit la frontière ; tout ce qui n'existe que dans l'univers des types s'efface. C'est pour ça qu'instanceof marche (une classe est une fonction réelle) et que : Cat ne marche pas (une interface n'émet rien). L'enum, lui, triche : il produit un objet.The dividing line is sharp: anything that also exists as a JavaScript value crosses the boundary; anything that lives only in the type universe is erased. That's why instanceof works (a class is a real function) and : Cat doesn't (an interface emits nothing). The enum cheats: it produces an object.

Ce qui survit, ce qui s'effaceWhat survives, what erases
class Dog { bark() {} }
const d: unknown = new Dog();
d instanceof Dog;          // ✓ marche — Dog est une vraie valeur JS (une fonction)

interface Cat { meow(): void }
// d instanceof Cat        // ✗ 'Cat' only refers to a type, but is being used as a value here

enum Color { Red, Green }
Object.values(Color);      // ✓ Color émet un vrai objet à l'exécution

typeof "x";                // ✓ "string"  — typeof JS, sur des valeurs
typeof d;                  // ✓ "object"  — incapable de dire "Dog" vs autre
De part et d'autre de la frontièreOn either side of the boundary
Survit (existe comme valeur)Survives (exists as a value)
✓class → fonction + prototype (instanceof)class → function + prototype (instanceof)
✓enum → un objet réel à l'exécutionenum → a real object at runtime
✓Les valeurs littérales ("ok", 0, true)Literal values ("ok", 0, true)
✓Tout objet, tableau, fonctionAny object, array, function
S'efface (n'est qu'un type)Erases (is only a type)
✗type et interfacetype and interface
✗Les annotations : T et types de retourAnnotations : T and return types
✗Les paramètres génériques <T>Generic parameters <T>
✗Les assertions as TAssertions as T
La règle, en une phraseThe rule, in one sentence

Pour décider à l'exécution, il faut un témoin runtime : une classe (instanceof), une primitive (typeof), ou un champ-valeur qu'on a soi-même posé. Jamais un type — il n'est plus là.To decide at runtime, you need a runtime witness: a class (instanceof), a primitive (typeof), or a value-field you placed yourself. Never a type — it's gone.

Le voisin, seulement nommé.The neighbor, only named.

L'enum est l'exception qui émet du code — d'où son poids, et les surprises de const enum (inliné, donc à nouveau effacé). Beaucoup d'équipes préfèrent une union de littéraux ("red" | "green") : zéro code émis, et le narrowing du 2.2.The enum is the exception that emits code — hence its weight, and the surprises of const enum (inlined, so erased again). Many teams prefer a union of literals ("red" | "green"): zero emitted code, and the narrowing of 2.2.

№ 04Le piègeThe trap4 min

Le type ne décide rien à l'exécution. Une valeur, si.The type decides nothing at runtime. A value does.

Deux usages du même malentendu. À gauche, on traite l'annotation comme une garantie sur des données venues du dehors — un as User sur une réponse réseau, et le crash est juste reporté. À droite, le bon réflexe : pour décider à l'exécution, on lit un champ qui existe vraiment dans la valeur — un discriminant.Two faces of the same misunderstanding. On one side, treating the annotation as a guarantee on data from outside — an as User on a network response, and the crash is merely deferred. On the other, the right reflex: to decide at runtime, read a field that genuinely exists in the value — a discriminant.

L'annotation sur une donnée du dehorsThe annotation on data from outside
// Le type comme preuve — un vœu, pas une vérification
async function load(): Promise<User> {
  const res = await fetch('/api/user');
  return (await res.json()) as User;   // as = « crois-moi », rien n'est vérifié
}

const u = await load();
u.name.toUpperCase();   // 💥 si l'API a renvoyé autre chose, ça pète ici

Tout ce qui entre — réseau, JSON.parse, localStorage, process.env — arrive non typé. Le as User ne vérifie rien : il fait taire le compilateur. Le type a beau dire User, l'exécution a ce que l'API a vraiment renvoyé. Le crash n'est pas évité, il est déplacé plus loin — là où il est le plus dur à diagnostiquer.Everything that enters — network, JSON.parse, localStorage, process.env — arrives untyped. The as User checks nothing: it silences the compiler. The type may say User, but runtime has whatever the API actually returned. The crash isn't avoided, it's moved further out — where it's hardest to diagnose.

Pour qu'une décision survive à l'effacement, elle doit s'appuyer sur ce qui reste — une valeur, jamais sur ce qui s'efface — un type.For a decision to survive erasure, it must rest on what remains — a value, never on what erases — a type.
№ 05Bilan & éditoWrap-up & editorial2 min

Une frontière, et deux mondes qui ne se parlent qu'une fois.One boundary, and two worlds that speak only once.

Le compilateur raisonne sur des types ; le moteur exécute des valeurs. Entre les deux, une frontière à sens unique : les types descendent vers le runtime en s'effaçant, et rien ne remonte. Savoir de quel côté on se trouve, c'est la moitié du métier.The compiler reasons about types; the engine runs values. Between them, a one-way boundary: types flow down to runtime by erasing, and nothing flows back. Knowing which side you're on is half the craft.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Prouver une forme au compilateurProve a shape to the compilerAnnoter : const u: User. Vérifié à la compilation, jusqu'à la frontière.Annotate: const u: User. Checked at compile time, up to the boundary.Aucune garantie au-delà : sur une donnée du dehors, l'annotation est un vœu.No guarantee beyond it: on outside data, the annotation is a wish.
Distinguer une classe au runtimeTell a class apart at runtimex instanceof Dog : une classe est une vraie valeur JS.x instanceof Dog: a class is a real JS value.Ne marche que pour les classes — pas les interfaces, pas les types nus.Works only for classes — not interfaces, not bare types.
Choisir selon une varianteBranch on a variantLire un discriminant-valeur : switch (s.kind). Narrowing, 2.2.Read a value discriminant: switch (s.kind). Narrowing, 2.2.Il faut avoir posé le champ soi-même — un type seul ne suffira jamais.You must have placed the field yourself — a type alone never will.
Faire confiance à une donnée du dehorsTrust data from outsideLa parser à la frontière, pas un as. Vol 7.Parse it at the boundary, not an as. Vol 7.Un vrai contrôle à l'exécution à écrire — le seul qui survit à l'effacement.A real runtime check to write — the only one that survives erasure.
« 'User' only refers to a type, but is being used as a value here » n'est pas une limite agaçante. C'est le compilateur qui te rappelle : ici commence le runtime, et tes types n'y entrent pas."'User' only refers to a type, but is being used as a value here" isn't an annoying limit. It's the compiler reminding you: runtime starts here, and your types don't come in.
Mot de l'éditeurFrom the editor

Le type prouve, puis il part.The type proves, then it leaves.

Venant de Java ou de C#, on imagine un type comme une étiquette collée à la valeur pour toute sa vie. En TypeScript, c'est faux, et c'est même le contrat : le type sert à prouver des choses pendant la compilation, et il s'efface entièrement avant l'exécution. Ce n'est ni un bug ni une faiblesse — c'est ce qui permet à TypeScript de tourner sur n'importe quel moteur JavaScript sans runtime spécial. Mais ça impose une discipline : tout ce qui doit être vrai à l'exécution doit reposer sur une valeur, pas sur une annotation. Le jour où l'on cesse de demander aux types de garder la maison après leur départ, les bugs de frontière — le as qui ment, le JSON qui n'a pas la forme promise — cessent de surprendre.Coming from Java or C#, you picture a type as a label stuck to the value for life. In TypeScript that's false, and it's even the contract: the type proves things during compilation, and erases entirely before runtime. It's neither a bug nor a weakness — it's what lets TypeScript run on any JavaScript engine with no special runtime. But it imposes a discipline: anything that must be true at runtime must rest on a value, not an annotation. The day you stop asking types to mind the house after they've left, boundary bugs — the lying as, the JSON that lacks the promised shape — stop surprising you.

Prochain numéro : le narrowing. Puisqu'à l'exécution il ne reste que des valeurs, comment le compilateur, lui, suit-il le fil ? En lisant nos tests — typeof, in, le discriminant — pour rétrécir un type branche par branche, jusqu'à ce qu'il sache exactement ce qu'on tient. C'est la façon de reconstruire une preuve par-dessus des valeurs nues.Next issue: narrowing. Since only values remain at runtime, how does the compiler keep the thread? By reading our tests — typeof, in, the discriminant — to shrink a type branch by branch, until it knows exactly what you hold. It's how a proof is rebuilt on top of bare values.