Para describir la forma de un objeto, interface y type hacen el mismo trabajo, y los valores de uno se pueden asignar al otro cuando las formas coinciden. Las diferencias están en los bordes: type puede nombrar cosas que una interfaz no puede (uniones, tuplas, tipos calculados), y una interfaz puede hacer algunas cosas que un alias de tipo no puede (fusionarse, un extends comprobado).
TypeScript compara los tipos objeto por estructura, así que el nombre de la declaración no importa para la asignabilidad. Lo que sigue es la lista de sitios donde la elección sí importa.
Tabla comparativa
| Característica | interface | type |
|---|---|---|
| Formas de objeto | sí | sí |
| Opcionales, readonly, métodos, firmas de índice | sí | sí |
| Genéricos | sí | sí |
Uniones (A | B) | no | sí |
| Tuplas, primitivos, tipos de función por sí solos | no (tipos de función solo como firmas de llamada) | sí |
| Mapped types y conditional types | no | sí |
| Extender | extends, los conflictos son errores | &, los conflictos pasan a ser never |
| Fusión de declaraciones | sí | no (identificador duplicado) |
Asignable a Record<string, T> | no | sí, cuando las propiedades encajan |
implements en una clase | sí | sí, si es un tipo objeto |
| Definiciones recursivas | sí | sí |
Lo que solo puede hacer type
Todo lo que no sea una única forma de objeto necesita un alias de tipo:
Nada de esto se puede escribir con interface (los dos últimos se explican en mapped types y conditional types). Esa es la razón práctica de que todos los proyectos acaben usando type en algún sitio, usen lo que usen para las formas de objeto.
Lo que solo puede hacer interface: la fusión de declaraciones
Dos declaraciones interface con el mismo nombre en el mismo ámbito se combinan en una. Dos declaraciones type con el mismo nombre son el error TS2300, Duplicate identifier.
interface Settings {
theme: string;
}
interface Settings {
fontSize: number;
}
const s: Settings = { theme: "dark", fontSize: 14 }; // needs both
type Options = { a: number };
type Options = { b: number }; // error TS2300: Duplicate identifier 'Options'.
La fusión es cómo se amplían desde fuera los tipos de las librerías: añadir una propiedad al Window global, al Request de Express o al tipo del tema de una librería. Si publicas tipos que los usuarios podrían necesitar ampliar, usa interfaces. En tu propio código de aplicación, una fusión accidental (dos archivos de script que declaran el mismo nombre de interfaz global) ocurre sin avisar salvo que las dos declaren la misma propiedad con tipos distintos, y ese es uno de los argumentos que dan algunos equipos para preferir type.
extends frente a intersección
Una interfaz se extiende con extends, un alias de tipo con &. Casi siempre dan el mismo resultado, pero tratan de forma distinta una propiedad en conflicto. extends informa del conflicto en la declaración:
index.ts(7,11): error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
Types of property 'id' are incompatible.
Type 'number' is not assignable to type 'string'.
Una intersección acepta el mismo conflicto sin avisar y convierte la propiedad en never (un string que también es un number). El error solo aparece después, cuando intentas crear un valor:
El error señala el objeto, lejos de la declaración que lo causó. Para construir tipos objeto a partir de otros tipos objeto, extends da el mejor mensaje.
Firmas de índice: una diferencia fácil de pasar por alto
Un alias de un tipo objeto recibe una firma de índice implícita, así que se puede pasar donde se espera un Record<string, unknown>. Una interfaz no. Es deliberado: la explicación del equipo de TypeScript (issue #15300 del repositorio de TypeScript) es que una interfaz se puede ampliar con declaraciones posteriores, así que inferirle una firma de índice es menos seguro, y cambiar la regla ahora rompería demasiado código.
La llamada marcada con @ts-expect-error se ejecuta igualmente e imprime los campos de Alan: la regla es de tiempo de compilación. Esta es la razón habitual de un error confuso cuando se pasa un valor de una interfaz a una función auxiliar de logging, serialización o consultas tipada con Record<string, ...>. Cambia esa declaración a type, expande el valor, o tipa el parámetro de la función auxiliar con una interfaz o un genérico.
Rendimiento y mensajes de error
La página Performance de la wiki de TypeScript (sección "Preferring Interfaces Over Intersections") sugiere interface Foo extends Bar, Baz { ... } en lugar de type Foo = Bar & Baz & { ... } al componer tipos objeto. Sus razones: una interfaz es un único tipo objeto plano que detecta conflictos de propiedades, las relaciones entre interfaces se guardan en caché (las intersecciones en su conjunto no), y comprobar un valor contra una intersección comprueba cada componente antes del tipo aplanado. La diferencia importa en proyectos grandes con muchos tipos compuestos; con unas pocas formas de objeto normales no la vas a notar.
La misma página señala que las interfaces se muestran mejor. Una interfaz aparece por su nombre al pasar el cursor y en los mensajes de error, mientras que un alias de intersección a menudo se imprime desplegado en sus partes, lo que hace más difíciles de leer los mensajes largos.
Cuál usar
Una regla que funciona:
- Formas de objeto:
interface. Da unextendscomprobado, errores más claros en composiciones grandes y permite que los usuarios de una librería la amplíen. Coincide con la heurística del handbook de TypeScript: «usainterfacehasta que necesites características detype». - Todo lo demás:
type. Uniones, tuplas, tipos de función, tipos literales y todo lo que se construya con mapped types, conditional types o template literal types. - Excepción: usa
typepara una forma de objeto que tenga que encajar en parámetrosRecord<string, ...>, o cuando quieras impedir la fusión a propósito.
Usar type para todo también es una opción coherente, y muchos proyectos lo hacen. Mezclar los dos al azar es la única opción que conviene evitar: hace que quien lee se pregunte si la diferencia era intencionada.
Preguntas frecuentes
¿Qué diferencia hay entre type e interface en TypeScript?
Los dos describen formas de objeto y para eso son intercambiables. type además puede nombrar uniones, tuplas, primitivos y mapped types o conditional types, cosa que interface no puede. interface admite la fusión de declaraciones y extends, que comprueba si hay propiedades en conflicto. Los tipos objeto escritos con type también se pueden asignar a tipos con firma de índice como Record<string, unknown>, mientras que las interfaces no.
¿Uso type o interface?
La heurística del handbook de TypeScript es: usa interface hasta que necesites algo que solo tiene type. En la práctica significa interfaces para formas de objeto y type para uniones, tuplas, tipos de función y tipos calculados. A los equipos que usan type para todo también les va bien; lo importante es seguir una regla coherente.
¿interface es más rápido que type en TypeScript?
Para componer tipos objeto, sí en algunos casos. La guía de rendimiento del equipo de TypeScript recomienda interface ... extends en lugar de intersecciones grandes (A & B & { ... }), porque las relaciones entre interfaces se guardan en caché y una interfaz es un único tipo plano. Para una forma de objeto sencilla no hay una diferencia apreciable.
¿Puede una clase implementar un alias de tipo?
Sí, si el alias es un tipo objeto (o una intersección de tipos objeto): class Point implements PointType { ... } funciona. Una clase no puede implementar un tipo unión; eso es el error TS2422.
¿Puede una interfaz extender un alias de tipo?
Sí, siempre que el alias sea un tipo objeto: type Base = { id: string }; interface User extends Base { name: string } es válido. No puede extender un alias de unión. En la otra dirección, un alias de tipo puede construirse sobre una interfaz con &.