← React & TypeScript

TypeScript Fundamentals

What is the difference between `any`, `unknown`, and `never`? When would you use each one?

Show answer — try answering out loud first

any turns off type checking: you can do anything and the compiler says nothing. unknown is the opposite: it accepts any value, but forces you to check its type before using it. never represents something that should never happen: functions that don't return, or impossible code branches.

All three sound similar but play opposite roles:

  • any: turns off the compiler for that value. You can call nonexistent methods and TypeScript won't complain. It also spreads: anything coming out of an any expression is also any, and the type hole extends throughout your code.
  • unknown: the "safe any". It's used for data whose type you don't know yet (API responses, JSON.parse, catch). You can't do anything with it until you do narrowing with typeof, instanceof, or a validation.
  • never: the "this should never happen" type. It shows up in functions that always throw an error and in the exhaustiveness check of a switch. No value can be assigned to never, and that's exactly what makes it useful.

Rule of thumb: if you find yourself writing any, what you almost always want is unknown plus a check.

// any: the compiler turns off and the error arrives at runtime
const datoAny: any = "hola";
datoAny.toFixed(2); // compiles... and blows up at runtime
const contagiado = datoAny + 1; // contagiado is also any

// unknown: accepts everything, but requires checking before use
function procesarEntrada(entrada: unknown): string {
  // entrada.toUpperCase();
  // Error: 'entrada' is of type 'unknown'.

  if (typeof entrada === "string") {
    return entrada.toUpperCase(); // already checked: here it's a string
  }
  if (typeof entrada === "number") {
    return entrada.toFixed(2);
  }
  return "tipo no soportado";
}

// Real-world unknown case: JSON.parse returns any, better to treat it as unknown
const crudo: unknown = JSON.parse('{"nombre":"Ana"}');

// never: functions that never return
function fallar(mensaje: string): never {
  throw new Error(mensaje);
}

// never in exhaustiveness: guarantees we cover all cases
type Rol = "admin" | "usuario";

function describirRol(rol: Rol): string {
  switch (rol) {
    case "admin": return "Acceso total";
    case "usuario": return "Acceso limitado";
    default: {
      const nunca: never = rol; // if Rol grows, this stops compiling
      return nunca;
    }
  }
}

Fixing a type error with any "just to make it compile":

// The junior does this to silence the error:
const respuesta: any = await fetch("/api/usuario").then((r) => r.json());
console.log(respuesta.nombre.toUpperCase()); // can blow up at runtime

// Better: unknown + check
const datos: unknown = await fetch("/api/usuario").then((r) => r.json());
if (typeof datos === "object" && datos !== null && "nombre" in datos) {
  console.log(datos.nombre); // safe access after narrowing
}

With any the error doesn't disappear: it just moves from compile time to runtime, which is the worst place.

"any disables type checking completely and also spreads to everything it touches, so I avoid using it. unknown is the safe alternative: it also accepts any value, but the compiler forces me to check the type before using it; it's ideal for API responses or JSON.parse. And never represents the impossible: the return of a function that always throws an error, or the default of an exhaustive switch, where I assign the value to never so the compiler warns me if an unhandled case shows up."

Quick challenge

Write longitud(valor: unknown): number that returns the length if valor is a string or an array, and 0 in any other case. Without using any or as.

See solution
function longitud(valor: unknown): number {
  if (typeof valor === "string") {
    return valor.length; // narrowing to string
  }
  if (Array.isArray(valor)) {
    return valor.length; // narrowing to array
  }
  return 0;
}

longitud("hola");      // 4
longitud([1, 2, 3]);   // 3
longitud(42);          // 0