Funciones en TypeScript

Lección 4 de 8. Las funciones son la frontera del código: lo que entra y lo que sale. Anotar esa frontera es lo que más rinde de TypeScript, porque el compilador comprueba cada llamada, en cualquier archivo, contra lo que la función dice que acepta. Al final, la primera función genérica.

Anotar parámetros y valor devuelto

En una función se anotan los parámetros, siempre, y el valor devuelto cuando ayuda a leerla. Todo lo demás (declaraciones, funciones flecha, parámetros por omisión, resto) funciona como en JavaScript, y lo tienes en la lección de funciones del curso de JavaScript.

function precioFinal(precio: number, iva: number): number {
  return precio * (1 + iva);
}

const redondear = (n: number): number => Math.round(n * 100) / 100;

console.log(redondear(precioFinal(19.99, 0.21)));

Ábrelo en el editor y cámbialo →

salida24.19

Si no anotas un parámetro, el modo estricto no te deja seguir: un parámetro sin tipo sería any, y any apaga la revisión de todo lo que lo usa.

function doble(x) {
  return x * 2;
}

Ábrelo en el editor y cámbialo →

tsc --strictimplicito.ts(1,16): error TS7006: El parámetro 'x' tiene un tipo 'any' implícitamente.

El valor devuelto, en cambio, se puede dejar sin anotar: TypeScript lo deduce del return. Anotarlo tiene una ventaja: si dentro de la función te equivocas y devuelves otra cosa, el error sale en la función y no en el sitio, lejos, donde se usa el resultado.

El error que te ahorra: llamar a una función con argumentos de menos

function precioFinal(precio, iva) {
  return precio * (1 + iva);
}
console.log("Total:", precioFinal(100));
node argumentos_antes.mjsTotal: NaN

JavaScript no comprueba cuántos argumentos recibe una función: los que faltan valen undefined, 1 + undefined es NaN, y el total sale como «NaN» sin ningún error. En TypeScript:

function precioFinal(precio: number, iva: number): number {
  return precio * (1 + iva);
}
console.log("Total:", precioFinal(100));
tsc --strictargumentos.ts(4,23): error TS2554: Se esperaban 2 argumentos, pero se obtuvieron 1.

Opcionales, por omisión y resto

Si un parámetro puede faltar, se dice con ? o dándole un valor por omisión. Con ? su tipo incluye undefined y hay que comprobarlo; con un valor por omisión no hace falta, porque nunca llega vacío. Los parámetros de resto se anotan como un array:

function saludar(nombre: string, titulo?: string): string {
  return titulo ? `Hola, ${titulo} ${nombre}` : `Hola, ${nombre}`;
}

function precioFinal(precio: number, iva = 0.21): number {   // iva es number
  return Math.round(precio * (1 + iva) * 100) / 100;
}

function sumar(...numeros: number[]): number {
  return numeros.reduce((a, b) => a + b, 0);
}

console.log(saludar("Ruiz"), "|", saludar("Ruiz", "Dra."));
console.log(precioFinal(100), precioFinal(100, 0.04));
console.log(sumar(), sumar(1, 2, 3));

Ábrelo en el editor y cámbialo →

salidaHola, Ruiz | Hola, Dra. Ruiz
121 104
0 6

Funciones como valores: tipos de función y callbacks

En JavaScript las funciones se pasan como cualquier otro valor. Su tipo se escribe con la forma de una flecha: (a: number, b: number) => number es «una función que recibe dos números y devuelve un número». Los nombres de los parámetros del tipo son solo documentación:

type Operacion = (a: number, b: number) => number;

const sumar: Operacion = (a, b) => a + b;        // a y b ya son number
const multiplicar: Operacion = (x, y) => x * y;

function aplicar(op: Operacion, a: number, b: number): number {
  return op(a, b);
}

console.log(aplicar(sumar, 6, 7), aplicar(multiplicar, 6, 7));

const precios = [10, 25.5, 8];
const conIva = precios.map((p) => p * 1.21);     // p es number: lo deduce de precios
console.log(conIva.map((p) => p.toFixed(2)));

Ábrelo en el editor y cámbialo →

salida13 42
[ '12.10', '30.86', '9.68' ]

En sumar y en los map no hay ninguna anotación dentro de la flecha, y sin embargo a, b y p son números. Es el tipado contextual: cuando una función se escribe en un sitio que ya espera un tipo de función concreto, TypeScript deduce los parámetros de ahí. Por eso en los callbacks casi nunca hace falta anotar nada.

void: funciones que no devuelven nada

Una función que no devuelve ningún valor tiene tipo de retorno void. Sirve, sobre todo, para que nadie use por error su resultado, que en JavaScript sería undefined:

function registrar(mensaje: string): void {
  console.log(`[registro] ${mensaje}`);
}

const r = registrar("inicio");
console.log(r.length);   // error

Ábrelo en el editor y cámbialo →

tsc --strictvoid.ts(6,15): error TS2339: La propiedad 'length' no existe en el tipo 'void'.
Un detalle que sorprende: si un parámetro es un callback de tipo () => void, puedes pasarle una función que sí devuelve algo, y el compilador no protesta. Es a propósito, para que lista.forEach((x) => otra.push(x)) funcione aunque push devuelva un número. Lo explicamos en las rarezas de TypeScript.

Sobrecargas: varias firmas para una función

A veces una función acepta combinaciones de argumentos distintas, y el tipo que devuelve depende de cuál uses. Las sobrecargas lo describen: se escriben varias firmas sin cuerpo y, debajo, una sola implementación que las cubre todas. Desde fuera solo se ven las firmas:

function crearFecha(marcaDeTiempo: number): Date;
function crearFecha(anio: number, mes: number, dia: number): Date;
function crearFecha(a: number, mes?: number, dia?: number): Date {
  if (mes !== undefined && dia !== undefined) {
    return new Date(Date.UTC(a, mes - 1, dia));
  }
  return new Date(a);
}

console.log(crearFecha(0).toISOString());
console.log(crearFecha(2026, 10, 20).toISOString());

Ábrelo en el editor y cámbialo →

salida1970-01-01T00:00:00.000Z
2026-10-20T00:00:00.000Z
function crearFecha(marcaDeTiempo: number): Date;
function crearFecha(anio: number, mes: number, dia: number): Date;
function crearFecha(a: number, mes?: number, dia?: number): Date {
  if (mes !== undefined && dia !== undefined) {
    return new Date(Date.UTC(a, mes - 1, dia));
  }
  return new Date(a);
}

crearFecha(2026, 10);   // ¿año y mes, sin día?

Ábrelo en el editor y cámbialo →

tsc --strictsobrecargas_mal.ts(10,1): error TS2575: Ninguna sobrecarga espera argumentos 2, pero existen sobrecargas que esperan argumentos 1 o 3.

La implementación admitiría dos argumentos, pero como no hay ninguna firma con dos, la llamada se rechaza. Úsalas con moderación: casi siempre basta con una unión o con parámetros opcionales, que son más fáciles de leer.

El parámetro this

En JavaScript, el valor de this depende de cómo se llama la función, no de dónde se escribió; las reglas están en la lección de clases y this del curso de JavaScript. TypeScript permite declarar qué this espera una función con un primer parámetro llamado this, que no es un argumento real y desaparece al compilar:

type Cuenta = { titular: string; saldo: number };

function describir(this: Cuenta): string {
  return `${this.titular}: ${this.saldo} EUR`;
}

const cuenta = { titular: "Ana", saldo: 120, describir };
console.log(cuenta.describir());   // this es cuenta: bien

const suelta = cuenta.describir;
console.log(suelta());             // error: aquí this no es una Cuenta

Ábrelo en el editor y cámbialo →

tsc --strictthis.ts(11,13): error TS2684: El contexto 'this' de tipo 'void' no se puede asignar al contexto 'this' de tipo 'Cuenta' del método.

La primera función genérica

Imagina una función que devuelve el primer elemento de un array. ¿De qué tipo es su parámetro? Si escribes any[], sirve para cualquier array, pero pierdes la información: el resultado también es any, y el compilador deja pasar cualquier cosa.

function primero(lista: any[]): any {
  return lista[0];
}

const n = primero([3, 1, 2]);
console.log(n.toUpperCase());   // tsc no dice nada: n es any

Ábrelo en el editor y cámbialo →

node primero_any.tsTypeError: n.toUpperCase is not a function

La solución es un parámetro de tipo: un nombre entre < y >, por costumbre T, que funciona como un hueco que se rellena en cada llamada con el tipo que corresponda:

function primero<T>(lista: T[]): T | undefined {
  return lista[0];
}

const pan = primero(["pan", "té"]);    // string | undefined
const tres = primero([3, 1, 2]);       // number | undefined
console.log(pan?.toUpperCase(), tres?.toFixed(1));
console.log(tres?.toUpperCase());      // error: tres es un número

Ábrelo en el editor y cámbialo →

tsc --strictprimero.ts(8,19): error TS2339: La propiedad 'toUpperCase' no existe en el tipo 'number'.

La función genérica primero con un parámetro de tipo T: al llamarla con un array de textos, T es string; con uno de números, T es numberUn genérico es un hueco que se rellena en cada llamadafunction primero<T>(lista: T[]): T | undefinedprimero(["pan", "té"])T = stringdevuelve string | undefinedprimero([3, 1, 2])T = numberdevuelve number | undefinedTypeScript deduce T mirando el argumento; no hace falta escribirlo.

El parámetro de tipo T se deduce del argumento en cada llamada, y el tipo devuelto lo arrastra. Esquema: decodigo.com.

Fíjate en que devuelve T | undefined y no T: un array vacío no tiene primer elemento. Los genéricos tienen su propia lección, la 6, con restricciones y varios parámetros de tipo; con esta idea ya puedes leer la mayoría de las firmas de las bibliotecas.

Resumen

ParaEscribeRecuerda
Anotar una función(precio: number): numberlos parámetros siempre; el retorno, si ayuda
Un parámetro que puede faltartitulo?: string o iva = 0.21con ? hay que comprobarlo
Muchos argumentos...numeros: number[]—
El tipo de una función(a: number) => stringen callbacks, se deduce solo
No devuelve nada: voidlos callbacks void sí pueden devolver algo
Combinaciones de argumentossobrecargasmejor una unión si basta
Fijar el thisfunction f(this: Tipo)no es un argumento real
Que sirva para cualquier tipofunction f<T>(x: T[]): Tany pierde la información

Retos

Reto 1. Escribe una función repetir que reciba un texto y, opcionalmente, cuántas veces repetirlo (2 por omisión) y un separador (un espacio por omisión), y que devuelva el resultado.

Ver la solución
function repetir(texto: string, veces = 2, separador = " "): string {
  return Array(veces).fill(texto).join(separador);
}

console.log(repetir("eco"));
console.log(repetir("na", 4, "-"));

Ábrelo en el editor y cámbialo →

salidaeco eco
na-na-na-na

Reto 2. Define el tipo Filtro para funciones que reciben un número y devuelven si pasa o no, y escribe aplicarFiltros(lista: number[], ...filtros: Filtro[]), que devuelva los números que pasan todos los filtros.

Ver la solución
type Filtro = (n: number) => boolean;

function aplicarFiltros(lista: number[], ...filtros: Filtro[]): number[] {
  return lista.filter((n) => filtros.every((f) => f(n)));
}

const esPar: Filtro = (n) => n % 2 === 0;
const mayorQue10: Filtro = (n) => n > 10;
console.log(aplicarFiltros([4, 12, 15, 18, 7], esPar, mayorQue10));

Ábrelo en el editor y cámbialo →

salida[ 12, 18 ]

Reto 3. Escribe una función genérica ultimo que devuelva el último elemento de un array, con el tipo correcto. Comprueba que ultimo([true, false]) es boolean | undefined intentando asignarlo a una variable de tipo string.

Ver la solución
function ultimo<T>(lista: T[]): T | undefined {
  return lista[lista.length - 1];
}

const b = ultimo([true, false]);
const s: string = b;   // error

Ábrelo en el editor y cámbialo →

tsc --strictreto3.ts(6,7): error TS2322: El tipo 'boolean | undefined' no se puede asignar al tipo 'string'.
  El tipo 'undefined' no se puede asignar al tipo 'string'.

También valdría lista.at(-1), que ya devuelve T | undefined.

Todo el código de esta lección se comprobó con tsc 7.0.2 en modo estricto (--strict --locale es) y se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. ¿Quieres probar algo? Usa el editor de TypeScript.