TypeScript Never and Unknown
TypeScript provides two special types — never and unknown — that handle edge cases no basic type can express. unknown represents a value whose type is not yet known. never represents a value that can never exist or a code path that can never finish normally. Both exist to make TypeScript safer in tricky situations.
The unknown Type
The unknown type is the safe version of any. Both accept any value, but any lets you do anything with that value immediately. unknown forces a type check first.
ANY vs UNKNOWN ────────────────────────────────────────────────────── let a: any = "Hello"; a.toUpperCase(); // Works — TypeScript trusts you blindly let u: unknown = "Hello"; u.toUpperCase(); // Error! TypeScript says: prove it's a string first ──────────────────────────────────────────────────────
Assigning to unknown
let value: unknown;
value = 42; // OK
value = "text"; // OK
value = true; // OK
value = [1, 2, 3]; // OK
value = { name: "Pooja" }; // OK
// unknown cannot be used directly without type checking
let text: string = value; // Error: Type 'unknown' is not assignable to type 'string'
Using unknown Safely
function printValue(val: unknown): void {
if (typeof val === "string") {
console.log(val.toUpperCase()); // Safe — val is string here
} else if (typeof val === "number") {
console.log(val.toFixed(2)); // Safe — val is number here
} else if (Array.isArray(val)) {
console.log(val.length); // Safe — val is array here
} else {
console.log("Unknown type received");
}
}
printValue("hello"); // HELLO
printValue(3.14159); // 3.14
printValue([1, 2, 3]); // 3
Real Use of unknown — API Responses
// When fetching data from an API, the response type is not guaranteed async function fetchData(url: string): Promise{ let response = await fetch(url); return response.json(); // could be anything } async function main() { let data: unknown = await fetchData("https://api.example.com/user"); // Must check before using if (typeof data === "object" && data !== null && "name" in data) { let user = data as { name: string }; console.log(user.name); } }
The never Type
The never type represents a value that can never occur. TypeScript uses it in two situations: functions that never return (they throw an error or run forever), and impossible type branches in type guards.
NEVER IN CODE FLOW ───────────────────────────────────────────────── Function A → returns string (returns something) Function B → returns void (returns nothing/undefined) Function C → returns never (never returns at all — throws or loops forever) ─────────────────────────────────────────────────
never in Functions That Throw
function throwError(message: string): never {
throw new Error(message);
// Nothing after this runs — function never returns
}
function crashApp(): never {
throw new Error("Critical failure — app stopped.");
}
// Using in code
function divide(a: number, b: number): number {
if (b === 0) {
throwError("Cannot divide by zero"); // never — stops here
}
return a / b; // TypeScript knows this line only runs if b !== 0
}
console.log(divide(10, 2)); // 5
// divide(10, 0) — throws error
never in Infinite Loops
function keepListening(): never {
while (true) {
// process events forever
// never returns — hence return type is never
}
}
never as an Exhaustiveness Check
The most powerful use of never is catching missing cases in a switch statement. When TypeScript narrows a type down to nothing, the remaining type becomes never. Assigning it to a variable typed as never causes an error if any case was missed.
type Status = "active" | "inactive" | "suspended";
function handleStatus(status: Status): string {
switch (status) {
case "active":
return "Account is active";
case "inactive":
return "Account is inactive";
case "suspended":
return "Account is suspended";
default:
// If all cases are covered, TypeScript knows status is never here
const _exhaustive: never = status;
return _exhaustive;
}
}
// Now add a new status without updating the switch:
// type Status = "active" | "inactive" | "suspended" | "banned";
// TypeScript immediately shows an error at the default line
// because "banned" is not handled — it is not assignable to never
Diagram: Where never Appears Automatically
function classify(x: string | number): string {
if (typeof x === "string") {
// x is string here
return x.toUpperCase();
}
if (typeof x === "number") {
// x is number here
return x.toFixed(2);
}
// At this point TypeScript sees: all types are exhausted
// x is inferred as never here
// Nothing can reach this line
}
unknown vs any vs never
| Type | Meaning | Requires Type Check? | When to Use |
|---|---|---|---|
any | Could be anything — TypeScript ignores type | No | Quick prototypes (avoid in production) |
unknown | Could be anything — must check before using | Yes | API responses, external data, user input |
never | Cannot exist — function never returns | N/A | Error functions, infinite loops, exhaustiveness checks |
Practical Example: Safe JSON Parser
function parseJSON(jsonString: string): unknown {
try {
return JSON.parse(jsonString);
} catch {
throwParseError(jsonString);
}
}
function throwParseError(input: string): never {
throw new Error(`Failed to parse: ${input}`);
}
// Caller must check the type before using the result
let result = parseJSON('{"name": "Aditya", "age": 22}');
if (typeof result === "object" && result !== null) {
let user = result as { name: string; age: number };
console.log(user.name); // Aditya
console.log(user.age); // 22
}
Key Takeaways
| Type | Key Rule |
|---|---|
unknown | Accept anything, but check type before use — safer than any |
never | Used for functions that never return and for exhaustiveness checks |
| Both together | Help write code that handles every possible case with zero guessing |
