El tipo unknown
El tipo unknown representa un valor cuyo tipo es desconocido. A diferencia de any, que desactiva las comprobaciones de tipo, unknown obliga a verificar el tipo antes de realizar cualquier operación. Es la forma segura de manejar valores de tipo desconocido:

let valor: unknown = "hola"
valor = 42
valor = { nombre: "Ana" }
valor = [1, 2, 3]
// Todas las asignaciones son válidas, pero no podemos operar directamente
// valor.toUpperCase() // Error: 'valor' is of type 'unknown'
// valor.nombre // Error: 'valor' is of type 'unknown'
// valor + 1 // Error: 'valor' is of type 'unknown'
Para usar un valor unknown, primero hay que estrechar su tipo mediante comprobaciones:
function procesar(valor: unknown): string {
if (typeof valor === "string") {
return valor.toUpperCase()
}
if (typeof valor === "number") {
return valor.toFixed(2)
}
if (Array.isArray(valor)) {
return valor.join(", ")
}
return String(valor)
}
console.log(procesar("hola")) // "HOLA"
console.log(procesar(3.14159)) // "3.14"
console.log(procesar([1, 2, 3])) // "1, 2, 3"
unknown vs any
La diferencia fundamental es que any elimina las comprobaciones de tipo, mientras que unknown las exige:
// Con any: sin seguridad
function peligroso(valor: any): string {
return valor.nombre.toUpperCase() // Compila, pero puede fallar en runtime
}
// Con unknown: seguro
function seguro(valor: unknown): string {
if (
typeof valor === "object" &&
valor !== null &&
"nombre" in valor &&
typeof (valor as { nombre: unknown }).nombre === "string"
) {
return (valor as { nombre: string }).nombre.toUpperCase()
}
return "Sin nombre"
}
console.log(seguro({ nombre: "Ana" })) // "ANA"
console.log(seguro(42)) // "Sin nombre"
console.log(seguro(null)) // "Sin nombre"
Usa unknown en lugar de any cuando recibas datos de fuentes externas. El esfuerzo adicional de comprobar tipos previene errores en tiempo de ejecución.
Las reglas de asignabilidad son opuestas:
let valorAny: any = 10
let valorUnknown: unknown = 20
// any se puede asignar a cualquier tipo
let texto1: string = valorAny // Permitido (peligroso)
// unknown solo se puede asignar a unknown o any
// let texto2: string = valorUnknown // Error
let otro: unknown = valorUnknown // Permitido
let cualquiera: any = valorUnknown // Permitido
Patrones con unknown para datos externos
Al recibir datos de una API, parsear JSON o leer entrada del usuario, unknown es el tipo apropiado:
function parsearJSON(texto: string): unknown {
return JSON.parse(texto)
}
interface Producto {
id: number
nombre: string
precio: number
}
function esProducto(datos: unknown): datos is Producto {
if (typeof datos !== "object" || datos === null) return false
const obj = datos as Record<string, unknown>
return (
typeof obj.id === "number" &&
typeof obj.nombre === "string" &&
typeof obj.precio === "number"
)
}
const json = '{"id": 1, "nombre": "Laptop", "precio": 999}'
const datos = parsearJSON(json)
if (esProducto(datos)) {
console.log(`${datos.nombre}: ${datos.precio}e`) // "Laptop: 999e"
}
Un patrón más elaborado para respuestas de API con manejo de errores:
type RespuestaAPI<T> =
| { ok: true; datos: T }
| { ok: false; error: string }
function procesarRespuesta<T>(
respuesta: unknown,
validar: (datos: unknown) => datos is T
): RespuestaAPI<T> {
if (typeof respuesta !== "object" || respuesta === null) {
return { ok: false, error: "Respuesta inválida" }
}
const obj = respuesta as Record<string, unknown>
if (obj.error) {
return { ok: false, error: String(obj.error) }
}
if (validar(obj.datos)) {
return { ok: true, datos: obj.datos }
}
return { ok: false, error: "Datos no válidos" }
}
interface Usuario {
id: number
nombre: string
email: string
}
function esUsuario(datos: unknown): datos is Usuario {
if (typeof datos !== "object" || datos === null) return false
const obj = datos as Record<string, unknown>
return (
typeof obj.id === "number" &&
typeof obj.nombre === "string" &&
typeof obj.email === "string"
)
}
const respuestaOk = { datos: { id: 1, nombre: "Ana", email: "ana@ej.com" } }
const resultado = procesarRespuesta(respuestaOk, esUsuario)
if (resultado.ok) {
console.log(resultado.datos.nombre) // "Ana"
} else {
console.log(resultado.error)
}
Operaciones seguras con unknown
Ciertas operaciones son válidas con unknown sin necesidad de narrowing:
let valor: unknown = 42
// Comparaciones de igualdad: siempre válidas
console.log(valor === 42) // true
console.log(valor !== "hola") // true
// typeof: siempre válido
console.log(typeof valor) // "number"
// Negacion lógica: siempre válida
console.log(!valor) // false
// Operadores logicos con unknown
const resultado = valor || "default" // tipo: unknown
const definido = valor ?? "default" // tipo: unknown
El tipo never
El tipo never representa valores que nunca pueden existir. Es el tipo más restrictivo de TypeScript: ningún valor es asignable a never (excepto never mismo). Se utiliza en tres contextos principales:
Funciones que nunca retornan
Una función que siempre lanza un error o contiene un bucle infinito tiene tipo de retorno never:
function lanzarError(mensaje: string): never {
throw new Error(mensaje)
}
function fallarConCodigo(código: number, mensaje: string): never {
console.error(`[${código}] ${mensaje}`)
throw new Error(`Error ${código}: ${mensaje}`)
}
function procesarInfinito(): never {
while (true) {
// Proceso que nunca termina
}
}
A diferencia de void, que indica que la función retorna sin valor, never indica que la función nunca llega a retornar:
function logear(mensaje: string): void {
console.log(mensaje)
// Retorna implícitamente undefined
}
function fallar(mensaje: string): never {
throw new Error(mensaje)
// Nunca se llega al punto de retorno
}
Never en el análisis de control de flujo
Cuando TypeScript agota todas las posibilidades de una unión, el tipo residual es never:
function ejemplo(valor: string | number): string {
if (typeof valor === "string") {
return valor.toUpperCase()
}
if (typeof valor === "number") {
return valor.toFixed(2)
}
// TypeScript infiere que valor es never aquí
// porque string y number ya están cubiertos
const imposible: never = valor
return imposible
}
Never en switch exhaustivos
El uso más práctico de never es garantizar que un switch cubre todos los casos de una unión discriminada:
type EstadoPedido = "pendiente" | "procesando" | "enviado" | "entregado" | "cancelado"
function mensajeEstado(estado: EstadoPedido): string {
switch (estado) {
case "pendiente":
return "Su pedido está pendiente de confirmacion"
case "procesando":
return "Su pedido se está preparando"
case "enviado":
return "Su pedido está en camino"
case "entregado":
return "Su pedido ha sido entregado"
case "cancelado":
return "Su pedido fue cancelado"
default: {
const _exhaustivo: never = estado
throw new Error(`Estado no contemplado: ${_exhaustivo}`)
}
}
}
Si alguien agrega un nuevo estado a EstadoPedido sin actualizar el switch, TypeScript genera un error de compilación porque el nuevo estado no es asignable a never.
Se puede crear una función auxiliar reutilizable:
function exhaustivo(valor: never, mensaje?: string): never {
throw new Error(mensaje ?? `Caso no contemplado: ${JSON.stringify(valor)}`)
}
type Formato = "json" | "xml" | "csv"
function serializar(datos: Record<string, string>, formato: Formato): string {
switch (formato) {
case "json":
return JSON.stringify(datos)
case "xml":
return Object.entries(datos).map(([k, v]) => `<${k}>${v}</${k}>`).join("")
case "csv":
return Object.entries(datos).map(([k, v]) => `${k},${v}`).join("\n")
default:
return exhaustivo(formato)
}
}
Never en tipos condicionales
En tipos condicionales, never actúa como filtro porque desaparece de las uniones:
type SoloStrings<T> = T extends string ? T : never
type SoloNumeros<T> = T extends number ? T : never
type Mezcla = "hola" | 42 | true | "mundo" | 100
type Textos = SoloStrings<Mezcla> // "hola" | "mundo"
type Números = SoloNumeros<Mezcla> // 42 | 100
Este patrón es la base de utility types como Extract y Exclude:
// Extract<T, U> es equivalente a: T extends U ? T : never
type Resultado = Extract<string | number | boolean, string | number>
// Tipo: string | number
// Exclude<T, U> es equivalente a: T extends U ? never : T
type SinString = Exclude<string | number | boolean, string>
// Tipo: number | boolean
Diferencias entre void, never y undefined
Estos tres tipos representan conceptos diferentes y se usan en contextos distintos:
// void: la función retorna pero sin valor útil
function saludar(nombre: string): void {
console.log(`Hola, ${nombre}`)
}
// undefined: la función retorna explícitamente undefined
function buscar(id: number): string | undefined {
if (id === 1) return "Ana"
return undefined
}
// never: la función nunca retorna
function fallar(msg: string): never {
throw new Error(msg)
}
Las diferencias de asignabilidad:
// void acepta undefined
let v: void = undefined
// undefined es un valor concreto
let u: undefined = undefined
// never no acepta ningun valor
// let n: never = undefined // Error
// let n: never = null // Error
// void y undefined pueden usarse como tipos de retorno
function f1(): void { }
function f2(): undefined { return undefined }
// function f3(): never { } // Error: no puede retornar
void en callbacks
El tipo void tiene un comportamiento especial en callbacks: permite que la función callback devuelva un valor que será ignorado:
type Callback = (item: string) => void
const items = ["a", "b", "c"]
// Array.push devuelve number, pero la firma espera void
// Esto es válido porque void en callbacks permite ignorar el retorno
const cb: Callback = (item) => items.push(item)
Esto es deliberado en TypeScript para mantener compatibilidad con patrones comunes de JavaScript donde se pasan funciones que retornan valores a contextos que no los usan.
Patrones prácticos combinando los tres
type Resultado<T> =
| { estado: "exito"; valor: T }
| { estado: "vacío" }
| { estado: "error"; mensaje: string }
function procesarResultado<T>(resultado: Resultado<T>): T {
switch (resultado.estado) {
case "exito":
return resultado.valor
case "vacío":
throw new Error("Resultado vacío") // never implícitamente
case "error":
throw new Error(resultado.mensaje) // never implícitamente
default: {
const _: never = resultado
throw new Error("Estado imposible")
}
}
}
// Función que usa void para logging
function conLog<T>(
operación: () => T,
logger: (mensaje: string) => void
): T {
logger("Iniciando operación")
const resultado = operación()
logger("Operación completada")
return resultado
}
// Función que usa undefined para busquedas
function buscarEnLista<T>(
items: T[],
predicado: (item: T) => boolean
): T | undefined {
for (const item of items) {
if (predicado(item)) return item
}
return undefined
}
interface Tarea {
id: number
titulo: string
completada: boolean
}
const tareas: Tarea[] = [
{ id: 1, titulo: "Estudiar TypeScript", completada: false },
{ id: 2, titulo: "Escribir tests", completada: true }
]
const tarea = buscarEnLista(tareas, t => t.id === 1)
if (tarea !== undefined) {
console.log(tarea.titulo) // TypeScript sabe que tarea es Tarea
}
Un ejemplo completo que muestra el uso coordinado de unknown, never, void y undefined:
type Validador<T> = {
validar: (datos: unknown) => datos is T
mensajeError: string
}
function crearValidador<T>(
validar: (datos: unknown) => datos is T,
mensajeError: string
): Validador<T> {
return { validar, mensajeError }
}
function parsearYValidar<T>(
json: string,
validador: Validador<T>,
onError: (error: string) => void
): T | undefined {
let datos: unknown
try {
datos = JSON.parse(json)
} catch {
onError("JSON inválido")
return undefined
}
if (validador.validar(datos)) {
return datos
}
onError(validador.mensajeError)
return undefined
}
interface Config {
host: string
puerto: number
}
const validadorConfig = crearValidador<Config>(
(datos: unknown): datos is Config => {
if (typeof datos !== "object" || datos === null) return false
const obj = datos as Record<string, unknown>
return typeof obj.host === "string" && typeof obj.puerto === "number"
},
"Configuración inválida: se requiere host (string) y puerto (number)"
)
const errores: string[] = []
const config = parsearYValidar(
'{"host": "localhost", "puerto": 3000}',
validadorConfig,
(err) => errores.push(err)
)
if (config !== undefined) {
console.log(`Servidor en ${config.host}:${config.puerto}`)
} else {
console.log(`Errores: ${errores.join(", ")}`)
}
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
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
Comprender el tipo unknown como alternativa segura a any. Dominar el tipo never para valores imposibles y comprobaciones exhaustivas. Diferenciar entre void, never y undefined. Aplicar patrones practicos con unknown para respuestas de API y parseo de JSON. Usar never en switch exhaustivos para garantizar cobertura completa de casos.