📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

~3 min de lecture

type vs interface en TypeScript : il est temps de trancher

Si tu as récemment mis à jour tes règles ESLint, tu es peut-être tombé sur @typescript-eslint/consistent-type-definitions. Cette règle te force à choisir entre type et interface pour tes définitions de types objets.

Nous l'avons aperçu lors de notre montée de version vers Angular v21.

Mais comment choisir ? Et surtout, pourquoi ce choix a-t-il autant d'importance aujourd'hui ?

La règle ESLint en question

{
  "rules": {
    "@typescript-eslint/consistent-type-definitions": [
      "error",
      "type"
    ]
    // ou "interface"
  }
}

L'objectif : garantir la cohérence dans ta codebase. Fini le mélange arbitraire entre type et interface selon l'humeur du développeur.

Les vraies différences techniques

Avant de choisir, clarifions ce que chacun peut faire.

Ce que seul type peut faire

// Union types
type Status = 'pending' | 'active' | 'closed';
type Result<T> = Success<T> | Error;

// Mapped types
type Readonly<T> = { readonly [K in keyof T]: T[K] };

// Conditional types
type Unwrap<T> = T extends Promise<infer U> ? U : T;

// Tuples et primitives
type Coordinates = [number, number];
type ID = string | number;

Ce que seul interface peut faire

// Declaration merging
interface Window {
  myCustomProperty: string;
}

// Fusionne automatiquement avec l'interface Window existante

Le declaration merging est utile pour étendre des types de librairies tierces sans modifier leur code source.

Pour les objets simples : équivalents

// Ces deux définitions sont fonctionnellement identiques
type User = {
  id: string;
  name: string;
};

interface User {
  id: string;
  name: string;
}

Pourquoi interface a dominé pendant des années

La réponse est historique. Regardons l'évolution de type dans TypeScript :

Version Année Capacités de type
1.0 2014 Alias basiques uniquement (type ID = string)
1.4 2015 Union types (A | B)
1.6 2015 Intersection types (A & B)
2.1 2016 Mapped types (keyof, in)
2.8 2018 Conditional types

En TypeScript 1.x, type ne pouvait tout simplement pas décrire des objets complexes :

// TypeScript 1.0 - Ce qui était possible
type ID = string;
type Callback = () => void;

// Ce qui était IMPOSSIBLE
type User = { name: string };        // ❌
type Admin = User & { role: string }; // ❌
type Status = 'on' | 'off';           // ❌

Angular 2 est sorti en 2016. Sa documentation a été écrite à une époque où interface était le seul choix viable pour les modèles de données. Cette convention s'est perpétuée par habitude, même si elle n'a plus de justification technique.

L'argument sémantique : type pour les données

Au-delà des capacités techniques, il y a un argument de sens :

  • interface → un contrat, un comportement (ce qu'un objet peut faire)
  • type → une description de structure (ce qu'une donnée est)

Un modèle de données (DTO, entité) décrit ce que c'est, pas un contrat d'implémentation :

// Données → type (ce que c'est)
type User = {
  id: string;
  name: string;
  email: string;
};

type UserRole = 'admin' | 'editor' | 'viewer';

type UserWithRole = User & {
  role: UserRole;
};

// Contrats → interface (ce que ça fait)
interface UserRepository {
  findById(id: string): Promise<User>;

  save(user: User): Promise<void>;
}

interface Serializable {
  serialize(): string;
}

Cette distinction n'est pas juste théorique. Elle rend le code plus lisible : quand tu vois interface, tu sais immédiatement qu'il s'agit d'un contrat à implémenter.

Notre recommandation

Configure ESLint avec "type" par défaut :

{
  "rules": {
    "@typescript-eslint/consistent-type-definitions": [
      "error",
      "type"
    ]
  }
}

Puis utilise interface uniquement quand tu en as besoin :

  1. Declaration merging (extension de types externes)
  2. Contrats explicites que des classes doivent implémenter
// Vos modèles de données
type Product = {
  id: string;
  name: string;
  price: number;
};

type CartItem = Product & {
  quantity: number;
};

type PaymentStatus = 'pending' | 'completed' | 'failed';

// Vos contrats de service
interface PaymentGateway {
  process(amount: number): Promise<PaymentStatus>;

  refund(transactionId: string): Promise<void>;
}

// Extension d'un type externe
interface ImportMeta {
  env: Record<string, string>;
}

Et les performances ?

Tu liras parfois que interface est plus performant à la compilation. C'est techniquement vrai : le compilateur TypeScript peut mettre en cache les interfaces plus efficacement.

En pratique ? La différence est négligeable sauf sur des codebases de plusieurs millions de lignes. Ne laisse pas cet argument guider ton choix.

En résumé

Usage Choix Raison
Modèles de données type Sémantique : décrit ce que c'est
Unions, intersections type Seul capable
Mapped/Conditional types type Seul capable
Contrats de service interface Sémantique : décrit un comportement
Extension de types externes interface Declaration merging

La règle @typescript-eslint/consistent-type-definitions te force à prendre cette décision une fois pour toutes.

Fais le bon choix : type par défaut, interface quand c'est justifié.

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

EasyAngularKit

Formation complète pour maîtriser Angular et développer des applications web modernes.

Navigation

Contact

Légal

© 2026 Easy Angular Kit. Tous droits réservés.