Interfaces en TypeScript: contratos y mejores usos

En esta clase entramos a uno de los conceptos más importantes (y más discutidos en Twitter) de TypeScript: las interfaces.

Las interfaces son otra forma de definir tipos, pero con una idea central muy clara: definir un contrato. Es decir, la forma que debe tener un objeto, incluyendo propiedades y métodos, para que el resto del código pueda confiar en ello sin miedo.

¿Qué es una interface?

Una interface define la estructura que debe cumplir un objeto:

  • Qué propiedades tiene
  • Qué tipos tienen esas propiedades
  • Qué métodos existen y qué devuelven
interface User {
  name: string
  age: number
  email?: string
  greet(): string
}

En la práctica, para muchos casos se parece muchísimo a usar type para un objeto. Y ahí llega la pregunta clásica: vale… ¿entonces cuál uso?

Interface vs type: diferencias clave (las que de verdad importan)

Aunque se solapan en un montón de casos, hay diferencias prácticas:

Cosas que type puede hacer y interface no

  • Union types: cuando algo puede ser “A o B” (por ejemplo, User | null), esto es terreno de type.
  • Tuplas y ciertos tipos más “técnicos” también encajan mejor con type.
  • Definir un tipo como primitivo directamente (string, number, etc.) no es el punto fuerte de una interface — las interfaces están más pensadas para objetos.
// Esto solo puede hacerse con type
type StringOrNumber = string | number
type Coords = [number, number]
type ID = string

Cosas donde interface se siente más natural

  • Modelar objetos complejos
  • Extender contratos (herencia de forma clara)
  • Trabajar con clases usando implements para obligar a cumplir un contrato

Regla práctica recomendada (para no vivir en el caos)

Una estrategia simple que funciona muy bien:

  • Usa interfaces para objetos (especialmente si van a crecer o extenderse).
  • Usa type para todo lo demás (unions, tuplas, utilidades, combinaciones raras, etc.).

¿Puedes usar type para todo? Sí. ¿Te va a explotar el proyecto? No. ¿Pero te quedará más ordenado si sigues una regla? También.

Métodos en interfaces: dos estilos válidos

Cuando defines métodos, TypeScript permite más de una sintaxis:

interface User {
  // Estilo 1: como propiedad de tipo función
  greet: (greeting: string) => string

  // Estilo 2: como declaración de método
  greet(greeting: string): string
}

Las dos son correctas, y puedes elegir la que te resulte más cómoda. Lo importante es que la interface puede describir métodos igual que propiedades.

Extender interfaces: contratos que se componen

Uno de los puntos fuertes de las interfaces:

interface Persona {
  name: string
  age: number
}

interface Usuario extends Persona {
  email: string
  role: 'admin' | 'user'
}

Incluso puedes extender de más de una interface:

interface Timestamps {
  createdAt: Date
  updatedAt: Date
}

interface Usuario extends Persona, Timestamps {
  email: string
}

Esto hace que sea muy fácil reutilizar y mantener modelos sin duplicar campos por todas partes.

El “lado oscuro”: la fusión automática (declaration merging)

Aquí viene una rareza importante:

Si declaras la misma interface más de una vez en el mismo ámbito, TypeScript las fusiona automáticamente.

interface Config {
  apiKey: string
}

interface Config {
  baseUrl: string
}

// Config ahora tiene AMBAS propiedades: apiKey y baseUrl
const config: Config = {
  apiKey: '123',
  baseUrl: 'https://api.example.com',
}
  • Puede ser confuso
  • Puede ser peligroso si no lo esperas
  • Pero tiene una utilidad real: extender tipos de librerías externas (module augmentation)

La idea no es abusar, pero sí saber que existe para no quedarte mirando el error como si fuera un bug de la Matrix.

Interfaces y clases: el caso donde brillan

El uso más potente y “natural” de las interfaces es cuando quieres que una clase cumpla un contrato:

interface Reproducible {
  play(): void
  pause(): void
  stop(): void
}

class MusicPlayer implements Reproducible {
  play() { console.log('Reproduciendo...') }
  pause() { console.log('Pausado') }
  stop() { console.log('Detenido') }
}
  • La clase declara que implementa una (o varias) interfaces
  • TypeScript obliga a que estén todos los métodos/propiedades requeridos
  • Si falta algo, se queja (y con razón)

Esto es clave cuando quieres asegurar consistencia entre distintas implementaciones (por ejemplo, distintos reproductores, adaptadores, clientes, etc.).

Resumen

  • Interfaces: contratos para objetos y especialmente útiles con clases y extensiones.
  • Types: más flexibles para uniones, tuplas y tipos compuestos.
  • Si quieres una regla fácil: interfaces para objetos, types para el resto.
  • Las interfaces pueden fusionarse si las declaras varias veces (declaration merging) — a veces útil, a veces susto.
  • Con implements, las interfaces obligan a las clases a cumplir un contrato completo.