TypeScript interview question
Explain any, unknown and never. When should you use each?
Short answer
any switches off type checking and spreads silently. unknown accepts any value but forces you to narrow it before use, which makes it the safe type for JSON, caught errors and external input. never is the type with no values, used for impossible states and exhaustive checks on unions.
Answer
any and unknown are both “top” types that accept every value, but they behave in opposite ways. With any, the compiler lets you do anything with the value, so errors appear at runtime. With unknown, the compiler lets you do nothing until you prove what the value is. never is the bottom type: no value has type never, which makes it useful for marking impossible code paths.
any: an escape hatch that spreads
const data: any = JSON.parse('{"user": null}');
data.user.name.toUpperCase(); // compiles, crashes at runtime
const name: string = data.whatever; // any flows into typed code silently
any is contagious: anything derived from it is also unchecked. Legitimate uses are rare: migrating JavaScript gradually, or working around a broken third-party type. Prefer a narrow cast with a comment, and enable @typescript-eslint/no-explicit-any and noImplicitAny (included in strict).
unknown: accept anything, verify before use
function getErrorMessage(err: unknown): string {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
return JSON.stringify(err);
}
try {
await page.getByRole('button', { name: 'Pay' }).click();
} catch (err) {
// With "strict" (TS 4.4+), err is unknown via useUnknownInCatchVariables
console.error(getErrorMessage(err));
}
Use unknown at trust boundaries: parsed JSON, API responses, catch variables, message events, environment input. Narrow it with typeof, instanceof, in, discriminant checks, user-defined type guards, or a schema validator.
function isUser(v: unknown): v is { id: string; email: string } {
return typeof v === 'object' && v !== null
&& 'id' in v && typeof v.id === 'string'
&& 'email' in v && typeof v.email === 'string';
}
never: impossible states and exhaustiveness
Functions that always throw or never return have return type never. More usefully, never catches unhandled union members at compile time:
type Browser = 'chromium' | 'firefox' | 'webkit';
function launchArgs(b: Browser): string[] {
switch (b) {
case 'chromium': return ['--disable-gpu'];
case 'firefox': return [];
case 'webkit': return [];
default: {
const unreachable: never = b; // error if a new Browser is added and not handled
throw new Error(`Unhandled browser: ${unreachable}`);
}
}
}
If someone adds 'msedge' to Browser, this file stops compiling until it is handled.
never also appears in conditional types to filter members (Exclude<T, U> returns never for removed members) and as the result of impossible intersections such as string & number.
Quick comparison
| Assign anything to it | Use it without checks | Typical use | |
|---|---|---|---|
any |
yes | yes | migration, last resort |
unknown |
yes | no | untrusted input |
never |
no | (unreachable) | exhaustiveness, impossible branches |
What interviewers look for
- Clear contrast:
anydisables checking,unknownforces narrowing. - Concrete
unknownuses: JSON,catch, external input. - An exhaustive
switchwithnever. - Tooling discipline:
strict,no-explicit-any.
A strong answer covers
- any disables type checking and is contagious
- unknown is type-safe: must narrow before use
- Uses unknown for external data and caught errors
- never represents impossible values and enables exhaustive checks
- Mentions lint or compiler settings to limit any