Le bug que personne ne voit en relecture
Tu as un formulaire d'inscription. L'utilisateur valide, tu envoies un email de confirmation. Sauf que l'email n'arrive jamais. Pas d'erreur, pas de crash : juste le silence. Trois jours plus tard tu fouilles le code et tu trouves ça :
const user = {
nom: 'Sam',
emial: 'sam@exemple.fr' // 👈 relis bien
};
envoyerConfirmation(user.email); // undefined : l'email ne partira pas
emial au lieu de email. Quatre lettres transposées. Tu as relu ce code deux fois, ton collègue aussi : le cerveau humain corrige silencieusement ce qu'il lit. Il voit « email » même quand il y a « emial ». La machine, elle, compare lettre par lettre. Elle ne corrige pas. Elle compare.
En JavaScript pur, user.email vaut undefined et personne ne te le dit. En TypeScript, si tu déclares que user a la forme d'un Utilisateur, cette faute de frappe est refusée à la frappe, avec une suggestion : « Did you mean 'email'? »
Définition : une interface (ou un type alias) TypeScript est un contrat de forme : il décrit exactement quelles propriétés un objet doit avoir, leurs types, et lesquelles sont optionnelles ou en lecture seule. Tout objet qui prétend avoir cette forme est vérifié lettre par lettre par le compilateur.
L'objet a emial: au lieu de email:, et il est typé Utilisateur. À ton avis : que dit TypeScript, et que ferait JavaScript pur au même endroit ?
Voir la réponse
TypeScript refuse l'objet : propriété inconnue emial (il suggère email) ET propriété email manquante : deux erreurs pour une seule faute de frappe. JavaScript pur l'accepterait sans rien dire, et u.email vaudrait undefined en silence.
Décrire la forme exacte d'un objet
Une interface TypeScript, c'est un gabarit. Tu dis au compilateur : « tout objet de ce type DOIT avoir exactement ces propriétés, avec ces types ». Ni plus, ni moins :
interface Utilisateur {
nom: string;
email: string;
}
const u: Utilisateur = {
nom: 'Sam',
emial: 'sam@exemple.fr' // ✗ 'emial' n'existe pas dans Utilisateur
// Did you mean 'email'?
// ✗ 'email' est manquant
};
Le compilateur vérifie deux choses : chaque propriété déclarée dans l'interface est présente, et chaque propriété de l'objet existe dans l'interface. Une faute de frappe génère les deux erreurs à la fois.
Tu peux affiner le contrat avec deux modificateurs :
interface Utilisateur {
readonly id: number; // lecture seule après création : toute réaffectation est refusée
nom: string;
email: string;
telephone?: string; // optionnelle : présente → string, sinon absente
}
const u: Utilisateur = { id: 1, nom: 'Sam', email: 'sam@exemple.fr' };
u.id = 2; // ✗ Cannot assign to 'id' because it is a read-only property
telephone?: string signifie que la propriété peut être absente. Si elle est présente, TS force à gérer les deux cas avant d'utiliser la valeur : tu ne peux pas appeler u.telephone.length sans vérifier d'abord que u.telephone !== undefined.
À toi : attrape la faute de frappe
En JavaScript, cette typo vivrait des mois dans le code. Clique sur Vérifier les types et regarde le compilateur la pointer, et suggérer la correction. Clique d'abord sur Exécuter avant le fix : tu verras undefined s'afficher : le silence JS en action.
Bloqué sur la correction ? Voir le fix
Corrige emial → email. TS fait même la suggestion dans son message d'erreur. Une lettre changée, deux erreurs supprimées. Revérifie : ✓ 0 erreur, puis Exécute pour voir l'email s'afficher à la place d'undefined.
Erreur attendue : Object literal may only specify known properties, and 'emial' does not exist in type 'Utilisateur'. Did you mean 'email'? (+ propriété email manquante). C'est le compilateur qui joue le relecteur que le cerveau humain ne peut pas être.
Interface ou type : le choix honnête
TypeScript offre deux syntaxes pour décrire la forme d'un objet. Elles font (presque) la même chose :
// Syntaxe interface
interface Utilisateur {
nom: string;
email: string;
}
// Syntaxe type alias
type Utilisateur = {
nom: string;
email: string;
};
Résultat identique pour un objet. Mais type va plus loin : il peut nommer n'importe quoi (une union, un primitif, un tuple) :
type Id = string | number; // union : impossible avec interface seule
type Statut = 'actif' | 'inactif'; // union de littéraux
type Paire = [string, number]; // tuple
Le piège de la fusion de déclarations : si tu déclares deux fois la même interface dans un projet, TypeScript les fusionne silencieusement (c'est la « declaration merging »). Pratique pour étendre des types de bibliothèques tierces, mais piège redoutable si c'est accidentel. type lève une erreur immédiate si tu le déclares deux fois : bien plus sûr au quotidien.
Reco pragmatique (Matt Pocock, Total TypeScript) : utilise type par défaut, il couvre tous les cas et ne fusionne pas silencieusement. Utilise interface quand tu veux délibérément l'héritage (extends) ou étendre des types de librairie via la fusion. En pratique : type pour tes modèles de données, interface pour les contrats d'API publiques que d'autres devront étendre.
The bug nobody catches in review
You have a sign-up form. The user submits, you send a confirmation email. Except the email never arrives. No error, no crash — just silence. Three days later you dig through the code and find this:
const user = {
name: 'Sam',
emial: 'sam@example.com' // 👈 read carefully
};
sendConfirmation(user.email); // undefined — email will never be sent
emial instead of email. Four transposed letters. You read this code twice, so did your colleague — the human brain silently corrects what it reads. It sees "email" even when there's "emial". The machine, on the other hand, compares character by character. It doesn't correct. It compares.
In plain JavaScript, user.email is undefined — and nobody tells you. In TypeScript, if you declare that user has the shape of a User, this typo is rejected at the keystroke, with a suggestion: "Did you mean 'email'?"
Definition: a TypeScript interface (or type alias) is a shape contract: it describes exactly which properties an object must have, their types, and which ones are optional or read-only. Every object claiming to have this shape is checked character by character by the compiler.
The object has emial: instead of email:, and it's typed as User. Your guess: what does TypeScript say, and what would plain JavaScript do in the same place?
Show the answer
TypeScript rejects the object: unknown property emial (it suggests email) AND missing property email — two errors for one typo. Plain JavaScript would accept it without a word, and u.email would be undefined in silence.
Describing the exact shape of an object
A TypeScript interface is a template. You tell the compiler: "any object of this type MUST have exactly these properties, with these types". No more, no less:
interface User {
name: string;
email: string;
}
const u: User = {
name: 'Sam',
emial: 'sam@example.com' // ✗ 'emial' does not exist in User
// Did you mean 'email'?
// ✗ 'email' is missing
};
The compiler checks two things: every property declared in the interface is present, and every property of the object exists in the interface. One typo generates both errors at once.
You can refine the contract with two modifiers:
interface User {
readonly id: number; // read-only after creation — any reassignment is rejected
name: string;
email: string;
phone?: string; // optional: present → string, otherwise absent
}
const u: User = { id: 1, name: 'Sam', email: 'sam@example.com' };
u.id = 2; // ✗ Cannot assign to 'id' because it is a read-only property
phone?: string means the property may be absent. If it's present, TS forces you to handle both cases before using the value — you can't call u.phone.length without first checking that u.phone !== undefined.
Your turn: catch the typo
In JavaScript, this typo would live in the code for months. Click Check the types and watch the compiler point it out — and suggest the fix. Click Run first, before the fix: you'll see undefined appear — JS silence in action.
Stuck on the fix? Show it
Fix emial → email. TS even suggests it in the error message. One letter changed, two errors gone. Re-check: ✓ 0 errors, then Run to see the email appear instead of undefined.
Expected error: Object literal may only specify known properties, and 'emial' does not exist in type 'User'. Did you mean 'email'? (+ missing email property). That's the compiler playing the proofreader the human brain simply can't be.
Interface or type — the honest choice
TypeScript offers two syntaxes to describe the shape of an object. They do (almost) the same thing:
// interface syntax
interface User {
name: string;
email: string;
}
// type alias syntax
type User = {
name: string;
email: string;
};
Identical result for an object. But type goes further: it can name anything — a union, a primitive, a tuple:
type Id = string | number; // union — not possible with interface alone
type Status = 'active' | 'inactive'; // literal union
type Pair = [string, number]; // tuple
The declaration merging trap: if you declare the same interface twice in a project, TypeScript silently merges them (this is "declaration merging"). Handy for extending third-party library types — a nasty trap when accidental. type raises an immediate error if declared twice: much safer day-to-day.
Pragmatic recommendation (Matt Pocock, Total TypeScript): use type by default — it covers every case and doesn't silently merge. Use interface when you deliberately want inheritance (extends) or to extend library types via merging. In practice: type for your data models, interface for public API contracts that others need to extend.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
💬 Ré-explique sans regarder
Explique ce qu'apporte une interface face à un objet JS libre, avec l'exemple de la faute de frappe.
undefined; with an interface, the typo is caught at the keystroke, before anything runs.">Une bonne explication dit : l'interface est un contrat de forme : toute propriété manquante ou inconnue est refusée à la compilation avec une suggestion (« Did you mean… ? »). En JS libre, user.emial produit undefined en silence ; avec une interface, la faute de frappe est détectée au moment de la frappe, avant même d'exécuter.🧠 Rappel libre
Sans remonter : que font ? et readonly dans une interface ?
? rend la propriété optionnelle : si elle est présente, son type doit correspondre ; si elle est absente, TS force à vérifier avant tout usage (pas de undefined silencieux). readonly la rend en lecture seule après la création : toute réaffectation est refusée par le compilateur.⚖️ Juge le code de l'IA
Ton objet ne passe pas la vérification de types : email est manquant. Tu demandes à l'IA de corriger. Elle répond : « Corrigé ! J'ai retiré email de l'interface Utilisateur, comme ça plus d'erreur. » Tu acceptes, ou tu rejettes ?
as any (leçon 1) : on affaiblit le contrat pour qu'il ne dise plus rien, et l'email ne partira toujours pas. La bonne réponse : soit l'email est requis (on complète l'objet), soit il est vraiment optionnel (email?:) et alors on gère son absence partout dans le code.telephone?: string dans une interface ?interface Config {…} dans le même projet. Que se passe-t-il ?type ou interface : quelle est la reco pragmatique ?Tu sais décrire la forme d'un objet. Mais parfois une valeur peut être deux choses différentes : un identifiant string ou number, un résultat ou une erreur. À la leçon 4, tu découvres les unions et l'art du narrowing : rétrécir l'incertitude jusqu'à savoir exactement ce que tu tiens.
Leçon 4 : Unions et narrowing →