Menu

El tipo never en TypeScript: exhaustividad y never frente a void

never es el tipo sin valores. Es el tipo de retorno de las funciones que nunca terminan, el tipo que queda cuando el estrechamiento ha descartado todos los casos y la herramienta detrás de las comprobaciones exhaustivas de un switch. Aprende de dónde viene y en qué se diferencia de void.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

never es el tipo sin valores. Una función cuyo tipo de retorno es never nunca termina con normalidad: lanza un error o se ejecuta para siempre. Una variable de tipo never marca código que no puede ejecutarse, y eso es lo que hace posibles las comprobaciones exhaustivas.

return fail(...) compila en una función que devuelve number porque never se puede asignar a cualquier tipo: un valor de tipo never nunca puede llegar de verdad.

Funciones que nunca terminan

Hay dos tipos de función que nunca llegan a su final: una que siempre lanza un error, y otra con un bucle infinito. El compilador comprueba la afirmación. Una función anotada con never cuyo final es alcanzable es el error TS2534, A function returning 'never' cannot have a reachable end point.

function fail(message: string): never {
  throw new Error(message);
}

function runForever(): never {
  while (true) {
    // poll, serve requests...
  }
}

La inferencia depende de la sintaxis. Una declaración de función que solo lanza un error se infiere como que devuelve void, mientras que una función flecha o una expresión de función que solo lanza se infiere como never:

function f1() { throw new Error("x"); }       // () => void
const f2 = () => { throw new Error("x"); };   // () => never

El estrechamiento solo trata una llamada como callejón sin salida cuando el nombre llamado tiene un tipo explícito que devuelve never: una declaración de función anotada con : never, como fail arriba, o una variable con una anotación de tipo, const fail: (m: string) => never = (m) => { throw new Error(m); }. Un never inferido no cuenta, ni tampoco const fail = (m: string): never => ..., donde solo está anotada la flecha y no la variable.

never frente a void

voidnever
La función terminasíno (lanza un error o no sale nunca de un bucle)
Valor en tiempo de ejecuciónundefinedninguno: la llamada nunca produce uno
Código después de la llamadaalcanzableinalcanzable
Se puede asignar a otros tipossolo a void, unknown, anya todos los tipos
Uso típicocallbacks, manejadores de eventos, funciones con efectos secundariosfail(), assertNever(), bucles infinitos

La diferencia práctica se ve en el estrechamiento. Tras if (!user) fail("no user"), el compilador sabe que user está definido en la línea siguiente solo si fail devuelve never. Con un retorno void supone que la ejecución puede continuar.

Comprobaciones exhaustivas con never

Cada case de un switch sobre una unión estrecha el valor. Cuando se han manejado todos los miembros, lo que queda en default es never. Asignarlo a una variable never convierte «he manejado todos los casos» en algo que el compilador verifica:

Ahora añade un tercer miembro a la unión sin añadir un case:

index.ts(18,26): error TS2345: Argument of type '{ kind: "triangle"; base: number; height: number; }' is not assignable to parameter of type 'never'.

El error nombra el miembro que olvidaste. Añade case "triangle": return (shape.base * shape.height) / 2; y vuelve a compilar. La función auxiliar assertNever es la forma reutilizable de la misma comprobación, y su throw sigue importando en tiempo de ejecución: los datos de un JSON o de un cliente antiguo pueden contener un kind que los tipos consideran imposible. Este patrón es la base de las uniones discriminadas.

Estrechar hasta never

Lo mismo ocurre con cualquier estrechamiento, no solo con switch. Una vez descartadas todas las posibilidades, la variable tiene tipo never:

Si más adelante amplías el parámetro a string | number | boolean | bigint, la línea const nothing: never = x pasa a ser un error, que señala la función que hay que actualizar.

never desaparece en las uniones

never es el conjunto vacío de valores, así que añadirlo a una unión no cambia nada: string | never es simplemente string. Así es como los conditional types filtran uniones. Una rama que devuelve never quita ese miembro:

Los tipos integrados Exclude<T, U> y Extract<T, U> funcionan exactamente así. En una intersección ocurre lo contrario: string & never es never.

Los tipos imposibles se convierten en never

Una intersección que ningún valor puede cumplir se reduce a never:

type A = string & number;                   // never
type B = { kind: "a" } & { kind: "b" };     // never

Leer una propiedad de un valor B informa del motivo: Property 'kind' does not exist on type 'never'. The intersection 'B' was reduced to 'never' because property 'kind' has conflicting types in some constituents. Cuando un tipo que has construido resulta ser never, busca dos partes que se contradigan.

never, unknown y any

TipoValores que contieneSe puede asignar aAcepta
unknowntodos los valoressolo unknown y anytodo
anytodos los valorestodo excepto nevertodo
neverningún valortodosolo never

unknown es la cima de la jerarquía de tipos y never es el fondo. any no forma parte de la jerarquía: desactiva las comprobaciones.

Preguntas frecuentes

¿Qué es el tipo never en TypeScript?

never es el tipo que no tiene valores. No se le puede asignar nada (salvo otro never), y se puede asignar a cualquier tipo. Aparece como tipo de retorno de funciones que siempre lanzan un error o no terminan nunca, como tipo de una variable después de que el estrechamiento haya descartado todas las posibilidades, y como resultado de tipos imposibles como string & number.

¿Qué diferencia hay entre never y void?

Una función que devuelve void termina con normalidad; simplemente no devuelve un valor útil (en tiempo de ejecución devuelve undefined). Una función que devuelve never no termina: lanza un error o se ejecuta para siempre. El código posterior a una llamada a una función never es inalcanzable, y TypeScript lo trata así al estrechar.

¿Cómo se hace una comprobación exhaustiva en TypeScript?

En la rama default de un switch sobre una unión, asigna el valor a una variable de tipo never, o pásalo a una función assertNever(value: never): never que lance un error. Si se manejan todos los casos, ahí el valor es never y el código compila. Si falta un caso, el compilador informa de que el miembro que falta no se puede asignar a never.

¿Por qué mi tipo es never?

Normalmente porque TypeScript descartó todas las opciones al estrechar (por ejemplo, tras comprobar typeof x === "string" y typeof x === "number" sobre un string | number), o porque una intersección es imposible: string & number, o dos tipos objeto cuya propiedad compartida tiene tipos literales contradictorios. Pasa el cursor por el tipo en el editor para ver qué paso lo produjo.

¿Qué significa "is not assignable to type never"?

El código intentó poner un valor real donde solo se permite never. En una comprobación exhaustiva significa que no se manejó un miembro de la unión. En otros casos suele significar que un array se infirió como never[] o que una intersección se redujo a never; añade una anotación o corrige los tipos contradictorios.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR