Un atelier TypeScript · Édition d'ApprentissageA TypeScript Workshop · Learning Edition

Le typage structurel, ou la forme avant le nom Structural typing, or shape before name

Le code naïf cherche un implements et ne le trouve pas : let n: Named = new Dog(). En TypeScript, ce qui rend deux types compatibles, ce n'est pas leur nom — c'est leur forme. Tout objet qui a les bons champs passe, qu'il ait entendu parler du type ou non. Une seule règle, et tout le modèle de TypeScript se met à pencher. Naive code looks for an implements and can't find it: let n: Named = new Dog(). In TypeScript, what makes two types compatible isn't their name — it's their shape. Any object with the right fields passes, whether it ever heard of the type or not. One rule, and all of TypeScript's model starts to tilt.

AudienceAudience
Dev venant du JavaScript dynamique ou d'un langage nominal (Java/C#) Dev from dynamic JavaScript or a nominal language (Java/C#)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●○○○ Typage structurelAssignabilitéTradeoff
SommaireContents ·
01CadrageFraming3 min

Un objet qui n'a jamais entendu parler du type. Et qui passe.An object that never heard of the type. And it passes.

Sept lignes que tout le monde écrit le premier jour. Elles compilent — et le fait qu'elles compilent, ici, n'est pas une tolérance : c'est tout le modèle de TypeScript qui tient dans un seul appel. La compatibilité se décide sur la forme, pas sur le nom.Seven lines everyone writes on day one. They compile — and that they compile, here, is no leniency: it's all of TypeScript's model fitting inside a single call. Compatibility is decided by shape, not by name.

Le code qu'on croit anodinThe code we think is harmless
interface Point { x: number; y: number }

function dist(p: Point) {
  return Math.hypot(p.x, p.y);
}

const here = { x: 3, y: 4, label: "home" };
dist(here);   // ✓ accepté — here n'a jamais entendu parler de Point
Pourquoi here passe ?Why does here pass?
here (la valeur)here (the value)
x: number
y: number
·label: string
forme compatibleshape matches
Point (le type)Point (the type)
x: number
y: number

here n'« implémente » pas Point. Il a la forme de Pointx et y en number — et ça suffit. Le label en trop ne gêne personne.here doesn't "implement" Point. It has the shape of Pointx and y as number — and that's enough. The extra label bothers no one.

Le réflexe du volumeThe volume's reflex

« Qu'est-ce qui rend deux types compatibles — leur nom, ou leur forme ? » En TypeScript, la réponse est toujours : la forme. Un type, ce n'est pas une étiquette qu'on colle ; c'est un contrat de structure que toute valeur ayant les bons membres remplit, sans rien déclarer."What makes two types compatible — their name, or their shape?" In TypeScript, the answer is always: the shape. A type isn't a label you stick on; it's a structural contract that any value with the right members fulfills, declaring nothing.

Pourquoi ce numéro ouvre la série.Why this issue opens the series.

Tout le reste de TypeScript — unions, génériques, narrowing — se lit à travers cette lentille. On ne commence pas par la syntaxe des interfaces : on commence par un objet qui passe sans avoir rien promis. Comprends le structurel, et l'assignabilité cesse d'être une loterie.Everything else in TypeScript — unions, generics, narrowing — reads through this lens. We don't start with interface syntax: we start with an object that passes without promising anything. Understand structural typing, and assignability stops being a lottery.

02La surpriseThe surprise4 min

Ailleurs, il faut déclarer le lien. Ici, il suffit de l'avoir.Elsewhere you must declare the link. Here you just have it.

Quand on vient de Java ou de C#, « être d'un type » veut dire « avoir écrit implements quelque part ». Le lien est nominal : explicite, déclaré, vérifié par le nom. TypeScript prend le contre-pied — le lien n'est jamais déclaré, il est constaté sur la forme.Coming from Java or C#, 'being a type' means 'having written implements somewhere'. The link is nominal: explicit, declared, checked by name. TypeScript takes the opposite stance — the link is never declared, it's observed on the shape.

Nominal — le lien doit être déclaréNominal — the link must be declared
// Java / C# — typage nominal
interface Named { String name(); }

class Dog { String name() { return "Rex"; } }
//    ^ Dog ne déclare PAS « implements Named »

Named n = new Dog();   // ✗ refusé à la compilation
//        incompatible types: Dog cannot be converted to Named

Dog a bien une méthode name(), mais il n'a jamais signé le contrat Named. Le compilateur regarde l'arbre des déclarations, pas la forme : pas de implements, pas de compatibilité. Le nom fait foi.Dog does have a name() method, but it never signed the Named contract. The compiler looks at the declaration tree, not the shape: no implements, no compatibility. The name is what counts.

Dans un langage nominal, un type est un club avec une liste de membres. En TypeScript, c'est une forme que n'importe qui peut avoir.In a nominal language, a type is a club with a membership list. In TypeScript, it's a shape anyone can have.
03La mécaniqueThe mechanics5 min

« Au moins » la forme demandée. Plus, jamais moins."At least" the required shape. More, never less.

La règle d'assignabilité tient en une phrase : une valeur est acceptée si elle possède au moins tous les membres requis, chacun d'un type compatible. Des champs en plus ? Sans importance. Un champ qui manque ? Refus immédiat. Le sens de la flèche n'est pas symétrique.The assignability rule fits in one sentence: a value is accepted if it has at least all the required members, each of a compatible type. Extra fields? They don't matter. A missing field? Immediate refusal. The arrow's direction is not symmetric.

La règle, en deux affectationsThe rule, in two assignments
type Target = { id: number };

const wide = { id: 1, name: "a", active: true };
const t: Target = wide;     // ✓ plus de champs que demandé = OK

const narrow = { name: "a" };
const t2: Target = narrow;  // ✗ Property 'id' is missing in type
                            //   { name: string } is not assignable to Target
Le sens de l'assignabilitéThe direction of assignability
{ id, name, active }
plus de champsmore fields
assignable àassignable to
{ id }
la ciblethe target
{ name }
champ manquantmissing field
PAS assignableNOT assignable
{ id }
la ciblethe target

Un objet plus large est un objet plus étroit — il sait tout faire de ce qu'on lui demande, et plus. L'inverse est faux : il manquerait des champs au moment de les lire.A wider object is a narrower one — it can do everything asked of it, and more. The reverse is false: it would be missing fields the moment you read them.

Le coût, déjàThe cost, already

Le structurel a un prix : un type ne « protège » rien par son nom. Si deux concepts ont la même forme, TypeScript les confond — le numéro №04 en fait son piège. Le structurel n'est pas une garantie d'intention, seulement de structure.Structural typing has a price: a type "protects" nothing through its name. If two concepts share a shape, TypeScript conflates them — issue №04 makes that its trap. Structural typing guarantees structure, never intent.

Les voisins, seulement nommés.The neighbors, only named.

type et interface sont tous deux structurels — le choix entre eux n'a presque rien à voir avec l'assignabilité. La compatibilité des fonctions (paramètres, retour) suit la même logique de forme, avec une subtilité de variance : ce sera le sujet du Vol 5.Both type and interface are structural — choosing between them has almost nothing to do with assignability. Function compatibility (params, return) follows the same shape logic, with a variance subtlety: that's Vol 5's subject.

04Le piègeThe trap4 min

Le littéral est refusé. La même valeur, via une variable, passe.The literal is refused. The same value, through a variable, passes.

Il existe une entorse au structurel — une seule. Un littéral d'objet « frais », assigné directement à un type, voit ses propriétés en trop refusées. Sortez la même valeur dans une variable d'abord, et tout passe. Ce n'est pas une garantie de structure : c'est un garde-fou contre les fautes de frappe.There's one exception to structural typing — exactly one. A 'fresh' object literal, assigned straight to a type, has its excess properties rejected. Pull the same value into a variable first, and it all passes. This is no structural guarantee: it's a guardrail against typos.

Refusé — excess property checkRejected — excess property check
type Config = { width: number; height: number };

const c: Config = { width: 10, height: 20, depth: 5 };
//                                          ^^^^^^^^
// error: Object literal may only specify known properties,
//        and 'depth' does not exist in type 'Config'

Sur un littéral écrit à l'endroit même de l'affectation, TypeScript fait un contrôle en plus du structurel : il refuse les champs inconnus. L'intention est généreuse — attraper le widht mal tapé qui, sinon, partirait silencieusement.On a literal written at the assignment site, TypeScript adds a check on top of structural typing: it rejects unknown fields. The intent is kind — catch the mistyped widht that would otherwise slip through silently.

Le vrai prix : deux concepts, une seule formeThe real price: two concepts, one shape
type Meters  = { value: number };
type Seconds = { value: number };

function wait(s: Seconds) { /* ... */ }

const distance: Meters = { value: 100 };
wait(distance);   // ✓ accepté — même forme, TS ne voit aucune différence
//   100 mètres passés à une fonction qui attend des secondes

Meters et Seconds ont la même forme : pour TypeScript, ce sont le même type. Passer des mètres à une fonction qui attend des secondes ne déclenche aucune erreur — le structurel ne connaît pas l'intention, seulement la structure. Le rendre impossible demande de brander le type : c'est le Vol 3 · №03.Meters and Seconds share a shape: to TypeScript, they're the same type. Passing meters to a function expecting seconds raises no error — structural typing doesn't know intent, only structure. Making it impossible means branding the type: that's Vol 3 · №03.

L'excess property check rassure, mais ne prouve rien. La vraie leçon : une forme partagée est une identité partagée — pour le meilleur, et pour le bug.The excess property check is reassuring, but proves nothing. The real lesson: a shared shape is a shared identity — for better, and for the bug.
05Bilan & éditoWrap-up & editorial2 min

Une forme, et tout ce qui la porte.One shape, and everything that wears it.

Le structurel n'est pas un nominal raté : c'est un choix qui colle au JavaScript réel, où un objet n'est qu'un sac de propriétés. Une fois le réflexe « la forme, pas le nom » en place, le reste de TypeScript se lit comme ses conséquences.Structural typing isn't a botched nominal system: it's a choice that fits real JavaScript, where an object is just a bag of properties. Once the reflex 'shape, not name' is in place, the rest of TypeScript reads as its consequences.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Accepter par la formeAccept by shapeNe rien déclarer : toute valeur ayant au moins les bons membres passe. C'est le défaut.Declare nothing: any value with at least the right members passes. It's the default.Aucun nom ne protège l'intention — deux concepts de même forme deviennent interchangeables.No name protects intent — two concepts of the same shape become interchangeable.
Attraper les fautes de frappeCatch typosAnnoter le littéral directement : const c: Config = {…}. L'excess property check rejette l'inconnu.Annotate the literal directly: const c: Config = {…}. The excess property check rejects the unknown.Ne vaut que sur un littéral frais. Passe par une variable et la protection disparaît.Only holds on a fresh literal. Go through a variable and the protection vanishes.
Distinguer deux formes égalesTell two equal shapes apartBrander le type : ajouter un marqueur que la forme nue n'a pas. Vol 3 · №03.Brand the type: add a marker the bare shape lacks. Vol 3 · №03.Une cérémonie de conversion à la frontière. Juste si la confusion coûte cher ; un poids mort sinon.A conversion ceremony at the boundary. Right if the mix-up is costly; dead weight otherwise.
Composer des formesCompose shapesUnions et intersections : A | B, A & B. Vol 1 · №02.Unions and intersections: A | B, A & B. Vol 1 · №02.Une nouvelle règle de lecture (l'une OU l'autre vs les deux) — le sujet du prochain numéro.A new reading rule (one OR the other vs both) — the next issue's subject.
« { name: string } is not assignable » n'est pas un caprice. C'est le compilateur qui te dit : il manque un champ que quelqu'un, plus loin, va lire."{ name: string } is not assignable" is no whim. It's the compiler telling you: a field is missing that someone, further on, will read.
Mot de l'éditeurFrom the editor

Le type ne scelle rien. Il décrit une forme.The type seals nothing. It describes a shape.

On arrive souvent à TypeScript en croyant qu'une annotation enferme une valeur dans une catégorie étanche, comme une classe Java. C'est l'inverse : un type est une description ouverte que toute valeur conforme satisfait, sans rien promettre. Tant qu'on attend du nominal, on s'étonne — du Dog qui passe pour un Named, du Meters confondu avec un Seconds. Le jour où l'on pense en formes — qui a quels champs, et qui va les lire ? — le système de types cesse de surprendre et commence à composer.People often come to TypeScript believing an annotation locks a value into a sealed category, like a Java class. It's the opposite: a type is an open description that any conforming value satisfies, promising nothing. As long as you expect nominal typing, you're surprised — by the Dog passing as a Named, the Meters confused with a Seconds. The day you think in shapes — who has which fields, and who will read them? — the type system stops surprising you and starts composing.

Prochain numéro : composer ces formes. Les unions et les intersections — l'une OU l'autre, ou les deux à la fois — et le narrowing qu'elles appellent : comment prouver, branche par branche, laquelle on tient.Next issue: composing those shapes. Unions and intersections — one OR the other, or both at once — and the narrowing they call for: how to prove, branch by branch, which one you're holding.

Retour au kiosqueBack to newsstand