Vol. 1 · № 03 · Un atelier TypeScriptA TypeScript workshop

Excess property checks, ou la fraîcheur d'un littéral Excess property checks, or a literal's freshness

Le typage structurel accepte les champs en trop — sauf un cas. Un littéral d'objet « frais », assigné directement, voit ses propriétés inconnues refusées. Ce n'est pas une garantie de structure : c'est un linter cousu dans le type, qui attrape le isAdmin mal tapé. Et il s'évapore dès qu'on passe par une variable. Structural typing accepts extra fields — except in one case. A 'fresh' object literal, assigned directly, has its unknown properties rejected. It's not a structural guarantee: it's a linter sewn into the type, catching the mistyped isAdmin. And it vanishes the moment you go through a variable.

AudienceAudience
Dev qui a vu le structurel et s'étonne d'un littéral parfois refusé Dev who has seen structural typing and is puzzled by a sometimes-rejected literal
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 17 min ●○○○ FreshnessExcess propertyTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Le structurel avalerait la faute de frappe. Un contrôle l'arrête.Structural typing would swallow the typo. One check stops it.

Vol 1.1 l'a posé : un objet avec des champs en trop passe, du moment qu'il a la bonne forme. Mais alors un isAdmin mal tapé — au lieu de admin — devrait passer aussi, et la propriété attendue resterait silencieusement vide. TypeScript fait ici une exception : sur un littéral écrit à l'endroit même, il refuse les champs inconnus.Vol 1.1 established it: an object with extra fields passes, as long as it has the right shape. But then a mistyped isAdmin — instead of admin — should pass too, and the expected property would stay silently empty. TypeScript makes an exception here: on a literal written at the very spot, it rejects unknown fields.

La faute qu'on ne verrait jamais à l'exécutionThe mistake you'd never see at runtime
type Options = { name: string; admin?: boolean };

function createUser(o: Options) { /* ... */ }

createUser({ name: "Ada", isAdmin: true });
//                        ^^^^^^^
// error: Object literal may only specify known properties,
//        but 'isAdmin' does not exist in type 'Options'.
//        Did you mean to write 'admin'?
Sans le contrôle, que se passerait-il ?Without the check, what would happen?
✓name: string
·admin?: boolean
optionnel → toléréoptional → tolerated
✗isAdmin: true
ignoré en silencesilently ignored

Structurellement, l'objet est un Options valide : il a name, admin est optionnel. isAdmin serait juste du bruit en trop — et admin resterait undefined. Le contrôle de champs en trop est là pour ça.Structurally, the object is a valid Options: it has name, admin is optional. isAdmin would just be extra noise — and admin would stay undefined. The excess property check exists for exactly this.

Le réflexe du numéroThe issue's reflex

« Ce contrôle qui refuse mes champs en trop — sur quoi porte-t-il exactement, et que ne protège-t-il pas ? » Ce n'est pas une garantie structurelle de plus : c'est un linter cousu dans le type, qui ne s'arme que dans un cas précis."This check that rejects my extra fields — what exactly does it cover, and what does it not protect?" It's not one more structural guarantee: it's a linter sewn into the type, armed only in one precise case.

Pourquoi ce numéro ferme le Vol 1.Why this issue closes Vol 1.

Le volume a montré le structurel (la forme), puis la composition (unions, intersections). Il restait une exception à nommer — la seule entorse au structurel — sous peine de la subir comme une incohérence. Comprends la fraîcheur, et le compilateur cesse de souffler le chaud et le froid sur les mêmes objets.The volume showed structural typing (the shape), then composition (unions, intersections). One exception remained to name — the only break from structural typing — lest you suffer it as an inconsistency. Understand freshness, and the compiler stops blowing hot and cold on the same objects.

№ 02La surpriseThe surprise4 min

Le contrôle porte sur comment c'est écrit, pas sur ce que c'est.The check is about how it's written, not what it is.

La même valeur, exactement la même, est refusée écrite en littéral et acceptée passée par une variable. Déroutant — jusqu'à ce qu'on comprenne que la « fraîcheur » est un drapeau attaché au littéral lui-même, à l'endroit où on l'écrit. Dès qu'il transite par une variable, le drapeau tombe, et le structurel pur reprend la main ; et ce qu'un spread fait entrer dans le littéral n'est jamais contrôlé.The same value, exactly the same, is rejected as a literal and accepted through a variable. Baffling — until you grasp that 'freshness' is a flag attached to the literal itself, at the spot you write it. The moment it passes through a variable, the flag drops, and pure structural typing takes over; and whatever a spread brings into the literal is never checked.

Frais — le contrôle s'armeFresh — the check is armed
createUser({ name: "Ada", isAdmin: true });
//                        ^^^^^^^ ✗ refusé
// le littéral est « frais » : écrit directement à l'endroit de l'appel

Un littéral d'objet écrit directement comme argument (ou en partie droite d'une affectation typée) est frais. Sur lui seul, TypeScript ajoute le contrôle des champs en trop. C'est le moment — et le seul — où une faute de frappe se fait attraper.An object literal written directly as an argument (or on the right of a typed assignment) is fresh. On it alone, TypeScript adds the excess property check. That's the moment — and the only one — where a typo gets caught.

La fraîcheur n'est pas une propriété de l'objet. C'est un drapeau sur l'endroit où tu l'écris — et il tombe à la première indirection.Freshness isn't a property of the object. It's a flag on where you write it — and it drops at the first indirection.
№ 03La mécaniqueThe mechanics5 min

Deux conditions, et le contrôle s'arme. Une seule manque, il dort.Two conditions, and the check arms. One missing, it sleeps.

Le contrôle des champs en trop se déclenche quand — et seulement quand — deux choses sont réunies : un littéral frais, et un type cible connu pour le confronter (le « type contextuel »). Annotation, argument, retour, élément de tableau, objet imbriqué : partout où ces deux conditions tiennent, il s'arme, récursivement.The excess property check fires when — and only when — two things meet: a fresh literal, and a known target type to compare it against (the 'contextual type'). Annotation, argument, return, array element, nested object: wherever both conditions hold, it arms, recursively.

Où il s'arme, où il dortWhere it arms, where it sleeps
type T = { id: number };

const a: T = { id: 1, x: 0 };           // ✗ annotation directe
function f(t: T) {} ; f({ id: 1, x: 0 });// ✗ argument
function g(): T { return { id: 1, x: 0 }; } // ✗ valeur de retour
const arr: T[] = [{ id: 1, x: 0 }];     // ✗ éléments de tableau littéral

const o = { id: 1, x: 0 }; const b: T = o; // ✓ passé par variable
type Bag = { id: number; [k: string]: unknown };
const c: Bag = { id: 1, x: 0 };         // ✓ la cible accepte l'inconnu
La table de véritéThe truth table
S'arme (littéral frais + cible)Arms (fresh literal + target)
✓Annotation directeDirect annotation
✓Argument de fonctionFunction argument
✓Valeur de retourReturn value
✓Élément de tableau littéralArray literal element
✓Objet imbriqué (récursif)Nested object (recursive)
Dort (une condition manque)Sleeps (a condition missing)
○Passé par une variablePassed through a variable
○Champs venus d'un spreadFields from a spread
○Cible avec index signatureTarget with an index signature
○Aucun type contextuelNo contextual type
Ce qu'il faut retenirWhat to keep

Le contrôle est local et syntaxique. Il ne suit pas la donnée à travers le programme — il regarde l'endroit précis où tu écris un littéral face à un type attendu. Utile comme alarme à l'écriture ; nul comme garantie sur la durée de vie d'une valeur.The check is local and syntactic. It doesn't follow data through the program — it looks at the exact spot where you write a literal against an expected type. Useful as a writing-time alarm; useless as a guarantee over a value's lifetime.

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

Cousin de la fraîcheur : l'élargissement (widening). const m = { status: "ok" } donne status: string, pas "ok" — le littéral est élargi. as const le fige. Deux mécanismes liés au littéral, distincts dans l'effet.Freshness's cousin: widening. const m = { status: "ok" } gives status: string, not "ok" — the literal is widened. as const freezes it. Two literal-bound mechanisms, distinct in effect.

№ 04Le piègeThe trap3 min

Faire taire l'alarme, ou déclarer que l'extra est prévu.To silence the alarm, or declare the extra is expected.

Le réflexe paresseux face à « Object literal may only specify known properties » : sortir l'objet dans une variable jusqu'à ce que ça compile. Ça marche — et ça désarme la seule protection contre les fautes de frappe, pour de bon. Quand les champs en plus sont vraiment attendus, il y a un geste honnête : le dire dans le type.The lazy reflex to 'Object literal may only specify known properties': pull the object into a variable until it compiles. It works — and it disarms the only protection against typos, for good. When the extra fields are genuinely expected, there's an honest move: say so in the type.

L'indirection qui cache le bugThe indirection that hides the bug
// On voulait juste « que ça compile »
const o = { name: "Ada", isAdmin: true };
createUser(o);
// ✓ compile — mais isAdmin survit, admin reste undefined.
// On a éteint l'alarme, pas le bug. Et partout où o passera,
// plus aucune faute de frappe ne sera attrapée.

La variable n'a rien corrigé : elle a juste retiré la fraîcheur, donc le contrôle. Le isAdmin mal tapé est toujours là, admin est toujours undefined, et l'utilisateur ne sera jamais admin. Faire compiler n'est pas faire marcher.The variable fixed nothing: it just stripped freshness, hence the check. The mistyped isAdmin is still there, admin is still undefined, and the user will never be admin. Making it compile isn't making it work.

Une variable qui « fait passer » un littéral ne corrige rien : elle débranche l'alarme et laisse le feu.A variable that "makes a literal pass" fixes nothing: it cuts the alarm and leaves the fire.
№ 05Bilan & éditoWrap-up & editorial2 min

Une alarme à l'écriture. Pas un mur sur la durée.An alarm at writing time. Not a wall over time.

Le contrôle des champs en trop est la seule entorse au structurel — et il faut le lire pour ce qu'il est : un linter local qui attrape les fautes de frappe sur un littéral frais, rien de plus. Bien compris, il cesse d'être une incohérence et devient un coup de pouce.The excess property check is structural typing's only exception — and you must read it for what it is: a local linter that catches typos on a fresh literal, nothing more. Understood well, it stops being an inconsistency and becomes a helping hand.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Attraper une faute de frappeCatch a typoÉcrire le littéral frais, directement face au type : f({…}), const x: T = {…}.Write the literal fresh, directly against the type: f({…}), const x: T = {…}.Ne marche que sur un littéral frais. Une variable ou un spread suffit à le désarmer.Only works on a fresh literal. A variable or a spread disarms it.
Autoriser des clés en plusAllow extra keysLe dire dans le type : une index signature [k: string]: unknown.Say it in the type: an index signature [k: string]: unknown.On perd la détection des clés inattendues sur ce type — c'est le marché assumé.You lose detection of unexpected keys on that type — the bargain you accept.
Vérifier sans élargirCheck without wideningsatisfies T : conformité contrôlée, type précis conservé. Vol 7.satisfies T: checked conformance, precise type kept. Vol 7.Encore un mot-clé à connaître — mais le bon, là où on mettait un as.One more keyword to know — but the right one, where an as used to go.
Juste faire compilerJust make it compileSortir l'objet dans une variable, ou as. À éviter.Pull the object into a variable, or as. Avoid.On éteint l'alarme sans corriger le bug — et on l'éteint pour tous les usages suivants.You silence the alarm without fixing the bug — and silence it for every later use.
« Object literal may only specify known properties » n'est pas le structurel qui se contredit. C'est un linter qui te dit : tu viens d'écrire un champ que personne ne lira."Object literal may only specify known properties" isn't structural typing contradicting itself. It's a linter telling you: you just wrote a field no one will read.
Mot de l'éditeurFrom the editor

Le seul endroit où TypeScript n'est pas structurel.The one place TypeScript isn't structural.

On termine le Vol 1 sur une bizarrerie assumée. Le typage structurel dit « tout ce qui a la forme passe » ; le contrôle des champs en trop dit « sauf si tu écris un littéral à la main, là je deviens regardant ». Ce n'est pas une incohérence : c'est un compromis pragmatique. Sans lui, chaque faute de frappe sur une clé optionnelle partirait en silence ; avec lui appliqué partout, on ne pourrait plus passer un objet plus riche que demandé — ce que le structurel autorise exprès. L'équipe TypeScript a tranché : on n'est strict que là où l'humain vient d'écrire, à l'endroit où une coquille est la plus probable.We close Vol 1 on a deliberate quirk. Structural typing says 'anything with the shape passes'; the excess property check says 'except when you write a literal by hand, then I get picky'. It's not an inconsistency: it's a pragmatic compromise. Without it, every typo on an optional key would slip by silently; with it applied everywhere, you couldn't pass a richer object than required — which structural typing allows on purpose. The TypeScript team drew the line: strict only where a human just typed, where a slip is most likely.

Fin du Vol 1 : on sait lire et composer des formes. Prochain volume — le cœur de la série : ces types que nous écrivons s'effacent à l'exécution. Une annotation ne vérifie rien au runtime ; le narrowing est la seule façon de reconstruire, branche par branche, une preuve qui tienne quand le type a disparu.End of Vol 1: you can read and compose shapes. Next volume — the heart of the series: the types we write are erased at runtime. An annotation verifies nothing at runtime; narrowing is the only way to rebuild, branch by branch, a proof that holds once the type is gone.