Uff, esto me quemó más de una vez, sobre todo al principio. Recuerdo un proyecto, hace ya varios años, donde la base de código era un desorden de interface y type mezclados sin un criterio claro. Uno de esos proyectos donde el equipo no se puso de acuerdo, o simplemente nadie sabía bien la diferencia. ¿El resultado? Refactors dolorosos cada vez que teníamos que añadir o modificar un tipo complejo, porque a veces un type no se podía extender de la forma que queríamos, o un interface no nos dejaba hacer una unión. Una locura.
Es una de esas preguntas que uno se hace al principio con TypeScript, y de hecho, sigue saliendo en entrevistas: "¿Cuál es la diferencia entre interface y type en TypeScript?". Y aunque la respuesta más honesta es "depende del contexto", eso no ayuda mucho cuando estás partiendo. Yo tengo mi opinión y mi forma de trabajar, que es la que me ha funcionado para mantener la cordura.
Primero lo básico: ¿Para qué sirven ambos?
Tanto interface como type nos permiten definir la forma de nuestros datos. Es decir, podemos describir la estructura de un objeto o la firma de una función. En eso son muy similares, y para casos simples, la verdad es que la diferencia es mínima. Mira estos ejemplos:
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
type PointType = {
x: number;
y: number;
};
type SetPointType = (x: number, y: number) => void;
Como ves, el resultado es prácticamente el mismo para definir un objeto o una función. Entonces, ¿dónde está la gracia?
Las diferencias que importan
1. type es más versátil
Esta es la primera gran diferencia. Mientras que un interface está pensado para describir la forma de objetos, un type puede describir cualquier tipo. Esto incluye:
- Primitivos:
type MyString = string; - Uniones:
type ID = string | number; - Tuplas:
type Coords = [number, number]; - Intersecciones:
type UserWithID = User & { id: string };
Aquí, el interface simplemente no puede competir. Si necesitas definir un tipo que no sea un objeto puro (como una unión o una tupla), tu única opción es type.
2. Extender y combinar tipos
Ambos se pueden "extender" o combinar, pero la sintaxis y las implicaciones son distintas.
interfaceextiendeinterface: Usa la palabra claveextends.typeextiendetype: Usa la intersección (&).interfacepuede extendertypey viceversa: Sí, esto es posible y a veces útil.
Por ejemplo:
interface BaseEntity {
id: string;
}
interface User extends BaseEntity {
name: string;
}
type ErrorCode = 'NOT_FOUND' | 'FORBIDDEN';
type APIResponse<T> = { data: T } & { status: number, error?: ErrorCode };
Ojo que, hasta hace un tiempo, las clases no podían implementar type aliases para objetos. Eso ya cambió hace rato (desde TS 2.7, si no me falla la memoria), así que ahora una clase puede implementar tanto un interface como un type que defina un objeto.
3. Fusión de declaraciones (Declaration Merging): el gran diferenciador de interface
Esta es, para mí, la razón más potente para preferir interface en la mayoría de los casos donde defines la forma de un objeto. Un interface puede ser declarado múltiples veces en el mismo scope, y TypeScript automáticamente fusionará todas esas declaraciones en una sola.
Un type alias, en cambio, no permite esto. Si intentas declarar un type con el mismo nombre dos veces, obtendrás un error.
interface ButtonProps {
onClick: () => void;
}
// Más tarde, en otro archivo o librería, si quiero añadir algo a ButtonProps
interface ButtonProps {
label: string;
}
const MyButton: ButtonProps = {
onClick: () => console.log('Click'),
label: 'Enviar' // Funciona, label se fusionó
};
Esto es súper útil para extender interfaces de librerías de terceros (por ejemplo, añadir propiedades a interfaces de React o Express) sin tener que duplicar o reescribir todo. Es una característica de diseño bastante potente.
Mi veredicto (y lo que hago yo)
Después de años quemándome con esto, mi enfoque es bastante directo:
- Para definir la forma de un objeto o una clase, casi siempre uso
interface. La posibilidad de declaration merging me parece demasiado valiosa para proyectos medianos a grandes, o cuando trabajo con librerías de terceros. Me da más flexibilidad a futuro. - Para todo lo demás (uniones, tuplas, tipos primitivos, combinaciones complejas que no son solo objetos), uso
type. Aquí es dondetypebrilla por su versatilidad.
¿Es una regla universal? No. Hay gente que prefiere usar type para todo y le funciona bien. De hecho, para objetos muy simples que sé que no voy a extender ni fusionar nunca, a veces uso type por pura inercia. Pero me gusta tener un criterio claro que me salve de dolores de cabeza en el futuro. Me frustra cuando veo una base de código donde no hay consistencia, y tienes que adivinar qué se usó en cada caso. La sobreingeniería me cansa, pero la inconsistencia me enferma.
Cerrando: ¿Qué haría diferente?
Si tuviera que empezar de cero con TypeScript hoy, y con lo que sé, me diría a mí mismo: "Jorge, para objetos, usa interface. Para el resto (uniones, tuplas, primitivos), usa type. No le des más vueltas que eso. La fusión de declaraciones te va a salvar varias veces, y la versatilidad de type para uniones es un must. Enfócate en la legibilidad y la consistencia antes que en la "pureza" de la elección."