Interfaces en TypeScript

Básico
TypeScript
TypeScript
Actualizado: 04/05/2026

Diagrama: tutorial-typescript-interfaces

Declaración de interfaces

Una interface en TypeScript define la forma que debe tener un objeto. Actua como un contrato que específica que propiedades y métodos debe contener un valor para ser compatible con esa interfaz. Las interfaces son una herramienta exclusiva del sistema de tipos: no generan código JavaScript y solo existen durante la compilación.

interface Usuario {
  nombre: string
  edad: number
  email: string
}

const usuario: Usuario = {
  nombre: "Ana",
  edad: 28,
  email: "ana@mail.com"
}

console.log(usuario.nombre) // "Ana"
console.log(usuario.edad)   // 28

Las interfaces se basan en el tipado estructural de TypeScript. Un objeto es compatible con una interfaz si tiene al menos las propiedades requeridas con los tipos correctos, independientemente de como se haya creado.

Las interfaces pueden usarse para tipar parámetros de función, valores de retorno y variables:

interface Producto {
  nombre: string
  precio: number
  stock: number
}

function mostrarProducto(producto: Producto): string {
  return `${producto.nombre}: ${producto.precio} EUR (${producto.stock} unidades)`
}

const laptop: Producto = { nombre: "Laptop", precio: 999, stock: 15 }
console.log(mostrarProducto(laptop))
// "Laptop: 999 EUR (15 unidades)"

Propiedades opcionales

Las propiedades opcionales se marcan con ? después del nombre. El objeto puede incluirlas o no:

interface Configuración {
  host: string
  puerto: number
  ssl?: boolean
  timeout?: number
}

const configMinima: Configuración = {
  host: "localhost",
  puerto: 3000
}

const configCompleta: Configuración = {
  host: "api.ejemplo.com",
  puerto: 443,
  ssl: true,
  timeout: 5000
}

function describir(config: Configuración): string {
  let descripcion = `${config.host}:${config.puerto}`
  if (config.ssl) {
    descripcion += " (SSL)"
  }
  if (config.timeout) {
    descripcion += ` timeout=${config.timeout}ms`
  }
  return descripcion
}

console.log(describir(configMinima))    // "localhost:3000"
console.log(describir(configCompleta))  // "api.ejemplo.com:443 (SSL) timeout=5000ms"

Propiedades de solo lectura

La palabra clave readonly impide que una propiedad se modifique después de la asignación inicial:

interface Punto {
  readonly x: number
  readonly y: number
}

const origen: Punto = { x: 0, y: 0 }
console.log(origen.x) // 0

// Error: Cannot assign to 'x' because it is a read-only property
// origen.x = 10

interface Documento {
  readonly id: string
  titulo: string
  contenido: string
}

const doc: Documento = {
  id: "doc-001",
  titulo: "Borrador",
  contenido: "Contenido inicial"
}

doc.titulo = "Versión final"   // Permitido
doc.contenido = "Nuevo texto"  // Permitido
// doc.id = "doc-002"          // Error: readonly

La marca readonly solo afecta a la propiedad directa. Si la propiedad es un objeto, sus propiedades internas siguen siendo mutables a menos que también se marquen como readonly.

interface Equipo {
  readonly nombre: string
  readonly miembros: string[]
}

const equipo: Equipo = {
  nombre: "Backend",
  miembros: ["Ana", "Carlos"]
}

// Error: readonly en la propiedad directa
// equipo.nombre = "Frontend"

// Permitido: el contenido del array no está protegido por readonly
equipo.miembros.push("Elena")
console.log(equipo.miembros) // ["Ana", "Carlos", "Elena"]

Extensión de interfaces con extends

Las interfaces pueden extender otras interfaces para heredar sus propiedades y añadir nuevas. Esto permite componer tipos complejos a partir de tipos más simples.

interface Entidad {
  id: number
  creadoEn: Date
}

interface Persona extends Entidad {
  nombre: string
  email: string
}

interface Empleado extends Persona {
  departamento: string
  salario: number
}

const empleado: Empleado = {
  id: 1,
  creadoEn: new Date(),
  nombre: "Carlos",
  email: "carlos@empresa.com",
  departamento: "Ingenieria",
  salario: 45000
}

console.log(empleado.nombre)       // "Carlos"
console.log(empleado.departamento) // "Ingenieria"

Extensión múltiple

Una interfaz puede extender varias interfaces a la vez, combinando todas sus propiedades:

interface ConNombre {
  nombre: string
}

interface ConTimestamp {
  creadoEn: Date
  actualizadoEn: Date
}

interface ConEstado {
  activo: boolean
}

interface Recurso extends ConNombre, ConTimestamp, ConEstado {
  tipo: string
  url: string
}

const recurso: Recurso = {
  nombre: "Manual de TypeScript",
  creadoEn: new Date(),
  actualizadoEn: new Date(),
  activo: true,
  tipo: "documento",
  url: "/docs/typescript"
}

console.log(recurso.nombre) // "Manual de TypeScript"
console.log(recurso.activo) // true

Convertir propiedades opcionales en obligatorias

Al extender una interfaz, es posible redefinir propiedades opcionales como obligatorias, siempre que el tipo sea compatible:

interface ConfigBase {
  host: string
  puerto?: number
  reintentos?: number
}

interface ConfigProduccion extends ConfigBase {
  puerto: number
  reintentos: number
  nivelLog: "info" | "warn" | "error"
}

const devConfig: ConfigBase = {
  host: "localhost"
}

const prodConfig: ConfigProduccion = {
  host: "api.producción.com",
  puerto: 443,
  reintentos: 3,
  nivelLog: "error"
}

console.log(devConfig.host)         // "localhost"
console.log(prodConfig.reintentos)  // 3

Declaration merging

Una de las caracteristicas exclusivas de las interfaces frente a los alias de tipo es la declaration merging (fusión de declaraciones). Cuando se declaran dos interfaces con el mismo nombre en el mismo ámbito, TypeScript las fusiona automáticamente en una sola interfaz que contiene los miembros de ambas:

interface Ventana {
  ancho: number
  alto: number
}

interface Ventana {
  titulo: string
}

// La interfaz Ventana ahora tiene ancho, alto y titulo
const ventana: Ventana = {
  ancho: 800,
  alto: 600,
  titulo: "Mi aplicación"
}

console.log(ventana.titulo) // "Mi aplicación"
console.log(ventana.ancho)  // 800

Las propiedades no funcionales de ambas declaraciones deben tener tipos compatibles. Si dos declaraciones definen la misma propiedad con tipos diferentes, TypeScript genera un error.

Uso práctico: ampliar tipos de terceros

Declaration merging es especialmente útil para ampliar tipos de bibliotecas externas sin modificar su código fuente:

interface Peticion {
  url: string
  método: string
}

// En otro lugar del código, ampliamos la interfaz
interface Peticion {
  cabeceras?: Record<string, string>
  cuerpo?: unknown
}

const peticion: Peticion = {
  url: "/api/usuarios",
  método: "POST",
  cabeceras: { "Content-Type": "application/json" },
  cuerpo: { nombre: "Ana" }
}

console.log(peticion.url)     // "/api/usuarios"
console.log(peticion.método)  // "POST"

Merging con métodos sobrecargados

Cuando ambas declaraciones incluyen métodos con el mismo nombre pero firmas diferentes, se fusionan como sobrecargas:

interface Buscador {
  buscar(termino: string): string[]
}

interface Buscador {
  buscar(termino: string, limite: number): string[]
}

// Buscador ahora tiene dos firmas de buscar
const buscador: Buscador = {
  buscar(termino: string, limite?: number): string[] {
    const resultados = ["resultado1", "resultado2", "resultado3"]
    if (limite !== undefined) {
      return resultados.slice(0, limite)
    }
    return resultados
  }
}

console.log(buscador.buscar("test"))     // ["resultado1", "resultado2", "resultado3"]
console.log(buscador.buscar("test", 2))  // ["resultado1", "resultado2"]

Interfaces para tipos de función y tipos híbridos

Interfaces como tipos de función

Una interfaz puede describir un tipo de función mediante una call signature sin nombre de método:

interface Comparador {
  (a: string, b: string): number
}

const compararAlfabetico: Comparador = (a, b) => {
  return a.localeCompare(b)
}

const compararPorLongitud: Comparador = (a, b) => {
  return a.length - b.length
}

const palabras = ["typescript", "go", "python", "c"]
console.log([...palabras].sort(compararAlfabetico))
// ["c", "go", "python", "typescript"]

console.log([...palabras].sort(compararPorLongitud))
// ["c", "go", "python", "typescript"]

Los nombres de los parámetros en la interfaz no necesitan coincidir con los de la implementación. TypeScript verifica los tipos posicionalmente:

interface Predicado {
  (valor: string): boolean
}

const esVacio: Predicado = (texto) => texto.length === 0
const contieneArroba: Predicado = (entrada) => entrada.includes("@")

console.log(esVacio(""))            // true
console.log(contieneArroba("a@b"))  // true

Tipos híbridos

Un tipo híbrido es una interfaz que combina una call signature con propiedades. Esto modela objetos en JavaScript que son invocables y además tienen estado:

interface Contador {
  (): number
  valor: number
  reiniciar(): void
}

function crearContador(inicial: number = 0): Contador {
  let cuenta = inicial

  const contador = function(): number {
    cuenta++
    contador.valor = cuenta
    return cuenta
  } as Contador

  contador.valor = cuenta
  contador.reiniciar = () => {
    cuenta = inicial
    contador.valor = cuenta
  }

  return contador
}

const miContador = crearContador(0)

console.log(miContador())       // 1
console.log(miContador())       // 2
console.log(miContador.valor)   // 2

miContador.reiniciar()
console.log(miContador.valor)   // 0
console.log(miContador())       // 1

Interfaces con index signatures

Las interfaces pueden incluir index signatures para describir objetos con claves dinámicas:

interface Diccionario {
  [clave: string]: string
}

const traducciones: Diccionario = {
  hello: "hola",
  world: "mundo",
  goodbye: "adios"
}

traducciones["thanks"] = "gracias"
console.log(traducciones["hello"]) // "hola"
console.log(traducciones["thanks"]) // "gracias"

Las index signatures pueden combinarse con propiedades fijas, siempre que los tipos sean compatibles:

interface CabecerasHTTP {
  "content-type": string
  "authorization"?: string
  [clave: string]: string | undefined
}

const cabeceras: CabecerasHTTP = {
  "content-type": "application/json",
  "authorization": "Bearer token123",
  "x-custom-header": "valor-personalizado"
}

console.log(cabeceras["content-type"])    // "application/json"
console.log(cabeceras["x-custom-header"]) // "valor-personalizado"

Interfaces genéricas

Las interfaces admiten parámetros de tipo para crear estructuras reutilizables:

interface Respuesta<T> {
  datos: T
  exito: boolean
  mensaje?: string
}

interface ListaPaginada<T> {
  elementos: T[]
  total: number
  página: number
  porPagina: number
}

const respuestaUsuario: Respuesta<{ nombre: string; edad: number }> = {
  datos: { nombre: "Ana", edad: 30 },
  exito: true
}

const listaProductos: ListaPaginada<{ nombre: string; precio: number }> = {
  elementos: [
    { nombre: "Laptop", precio: 999 },
    { nombre: "Teclado", precio: 75 }
  ],
  total: 50,
  página: 1,
  porPagina: 10
}

console.log(respuestaUsuario.datos.nombre)      // "Ana"
console.log(listaProductos.elementos.length)     // 2
console.log(listaProductos.total)                // 50
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de TypeScript

Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Declarar interfaces con propiedades obligatorias, opcionales y readonly. Extender interfaces con extends para componer tipos. Aprovechar declaration merging para ampliar interfaces existentes. Crear interfaces para tipos de función y tipos híbridos.