The never and unknown types are essential for type-safe programming in TypeScript. Here's how to use them.
The unknown Type#
1// unknown is the type-safe version of any
2let value: unknown;
3
4value = 'hello';
5value = 42;
6value = { name: 'John' };
7value = [1, 2, 3];
8
9// Can't use unknown directly
10const str: unknown = 'hello';
11// str.toUpperCase(); // Error: Object is of type 'unknown'
12
13// Must narrow the type first
14if (typeof str === 'string') {
15 str.toUpperCase(); // OK
16}
17
18// unknown in function parameters
19function processValue(value: unknown) {
20 if (typeof value === 'string') {
21 return value.toUpperCase();
22 }
23 if (typeof value === 'number') {
24 return value * 2;
25 }
26 return String(value);
27}unknown vs any#
1// any - bypasses type checking (unsafe)
2function withAny(value: any) {
3 value.foo(); // No error, might crash at runtime
4 value.bar.baz(); // No error
5 const num: number = value; // No error
6}
7
8// unknown - requires type checking (safe)
9function withUnknown(value: unknown) {
10 // value.foo(); // Error: Object is of type 'unknown'
11
12 // Must narrow first
13 if (value && typeof value === 'object' && 'foo' in value) {
14 (value as { foo: () => void }).foo(); // OK
15 }
16}
17
18// Use unknown for truly unknown values
19// Use any only for migration/interopType Narrowing with unknown#
1// typeof narrowing
2function processUnknown(value: unknown): string {
3 if (typeof value === 'string') {
4 return value;
5 }
6 if (typeof value === 'number') {
7 return value.toString();
8 }
9 if (typeof value === 'boolean') {
10 return value ? 'yes' : 'no';
11 }
12 return 'unknown';
13}
14
15// instanceof narrowing
16function processError(error: unknown): string {
17 if (error instanceof Error) {
18 return error.message;
19 }
20 if (typeof error === 'string') {
21 return error;
22 }
23 return 'Unknown error';
24}
25
26// Type predicate
27function isUser(value: unknown): value is User {
28 return (
29 typeof value === 'object' &&
30 value !== null &&
31 'id' in value &&
32 'name' in value
33 );
34}
35
36function processUser(data: unknown) {
37 if (isUser(data)) {
38 console.log(data.name); // OK, data is User
39 }
40}The never Type#
1// never represents values that never occur
2
3// Function that never returns
4function throwError(message: string): never {
5 throw new Error(message);
6}
7
8// Infinite loop
9function infiniteLoop(): never {
10 while (true) {
11 // do something
12 }
13}
14
15// never in union types
16type NonNull<T> = T extends null | undefined ? never : T;
17
18type StringOrNull = string | null;
19type StringOnly = NonNull<StringOrNull>; // stringExhaustive Checks#
1// never for exhaustive checking
2type Shape = 'circle' | 'square' | 'triangle';
3
4function getArea(shape: Shape): number {
5 switch (shape) {
6 case 'circle':
7 return Math.PI * 10 ** 2;
8 case 'square':
9 return 10 * 10;
10 case 'triangle':
11 return (10 * 10) / 2;
12 default:
13 // shape is never here - all cases handled
14 const exhaustiveCheck: never = shape;
15 throw new Error(`Unknown shape: ${exhaustiveCheck}`);
16 }
17}
18
19// If you add a new shape type without handling it:
20type Shape2 = 'circle' | 'square' | 'triangle' | 'rectangle';
21
22function getArea2(shape: Shape2): number {
23 switch (shape) {
24 case 'circle':
25 return Math.PI * 10 ** 2;
26 case 'square':
27 return 10 * 10;
28 case 'triangle':
29 return (10 * 10) / 2;
30 default:
31 // Error: Type 'string' is not assignable to type 'never'
32 const exhaustiveCheck: never = shape;
33 throw new Error(`Unknown shape: ${exhaustiveCheck}`);
34 }
35}
36
37// Helper function
38function assertNever(value: never): never {
39 throw new Error(`Unexpected value: ${value}`);
40}never in Conditional Types#
1// Exclude from union
2type Exclude<T, U> = T extends U ? never : T;
3
4type Numbers = 1 | 2 | 3 | 4 | 5;
5type SmallNumbers = Exclude<Numbers, 4 | 5>; // 1 | 2 | 3
6
7// Extract from union
8type Extract<T, U> = T extends U ? T : never;
9
10type ExtractedNumbers = Extract<Numbers, 2 | 3 | 4>; // 2 | 3 | 4
11
12// Filter object properties
13type FilterByType<T, U> = {
14 [K in keyof T as T[K] extends U ? K : never]: T[K]
15};
16
17interface Mixed {
18 name: string;
19 age: number;
20 email: string;
21 active: boolean;
22}
23
24type StringProps = FilterByType<Mixed, string>;
25// { name: string; email: string; }unknown in API Responses#
1// Safe API response handling
2async function fetchData<T>(
3 url: string,
4 validator: (data: unknown) => data is T
5): Promise<T> {
6 const response = await fetch(url);
7 const data: unknown = await response.json();
8
9 if (validator(data)) {
10 return data;
11 }
12
13 throw new Error('Invalid response data');
14}
15
16// Validator
17interface User {
18 id: number;
19 name: string;
20}
21
22function isUser(data: unknown): data is User {
23 return (
24 typeof data === 'object' &&
25 data !== null &&
26 typeof (data as User).id === 'number' &&
27 typeof (data as User).name === 'string'
28 );
29}
30
31// Usage
32const user = await fetchData<User>('/api/user', isUser);
33console.log(user.name); // Type-safeError Handling with unknown#
1// In catch blocks, error is unknown (TypeScript 4.4+)
2async function safeOperation() {
3 try {
4 await riskyOperation();
5 } catch (error: unknown) {
6 // Must narrow the error type
7 if (error instanceof Error) {
8 console.error(error.message);
9 } else if (typeof error === 'string') {
10 console.error(error);
11 } else {
12 console.error('Unknown error', error);
13 }
14 }
15}
16
17// Helper function
18function getErrorMessage(error: unknown): string {
19 if (error instanceof Error) return error.message;
20 if (typeof error === 'string') return error;
21 return 'Unknown error';
22}
23
24// Usage
25try {
26 throw 'Something went wrong';
27} catch (error: unknown) {
28 console.error(getErrorMessage(error));
29}never as Return Type#
1// Functions that throw
2function fail(message: string): never {
3 throw new Error(message);
4}
5
6// Using with conditional logic
7function processValue(value: string | number): string {
8 if (typeof value === 'string') {
9 return value;
10 }
11 if (typeof value === 'number') {
12 return value.toString();
13 }
14 // TypeScript knows this is unreachable
15 return fail('Unexpected value type');
16}
17
18// Assertion functions
19function assert(condition: boolean, msg?: string): asserts condition {
20 if (!condition) {
21 throw new Error(msg ?? 'Assertion failed');
22 }
23}
24
25function processUser(user: User | null) {
26 assert(user !== null, 'User is required');
27 // user is User here, not null
28 console.log(user.name);
29}Type Inference with never and unknown#
1// Union with never
2type A = string | never; // string (never disappears)
3
4// Intersection with never
5type B = string & never; // never (never absorbs)
6
7// Union with unknown
8type C = string | unknown; // unknown (unknown absorbs)
9
10// Intersection with unknown
11type D = string & unknown; // string (unknown disappears)
12
13// Practical implications
14type NonNullable<T> = T extends null | undefined ? never : T;
15
16type E = NonNullable<string | null>; // string
17type F = NonNullable<undefined>; // nevernever in Mapped Types#
1// Remove keys from type
2type RemoveKeys<T, K extends keyof T> = {
3 [P in keyof T as P extends K ? never : P]: T[P]
4};
5
6interface User {
7 id: number;
8 name: string;
9 password: string;
10 email: string;
11}
12
13type SafeUser = RemoveKeys<User, 'password'>;
14// { id: number; name: string; email: string; }
15
16// Filter keys by type
17type KeysOfType<T, V> = {
18 [K in keyof T]: T[K] extends V ? K : never
19}[keyof T];
20
21type StringKeys = KeysOfType<User, string>; // 'name' | 'password' | 'email'Best Practices#
Use unknown When:
✓ Receiving external data (API, user input)
✓ Catch blocks
✓ JSON parsing
✓ Generic value containers
Use never When:
✓ Functions that throw or loop forever
✓ Exhaustive type checking
✓ Filtering union types
✓ Impossible code paths
Narrowing unknown:
✓ typeof for primitives
✓ instanceof for classes
✓ Type predicates for objects
✓ Assertion functions
Avoid:
✗ Using any instead of unknown
✗ Type assertions without validation
✗ Ignoring never in switch defaults
✗ Over-complicating type guards
Conclusion#
unknown is the type-safe alternative to any - use it for values whose type is truly unknown and narrow with type guards. never represents impossible values - use it for exhaustive checks, functions that never return, and filtering types. Together, they enable robust type-safe programming while maintaining flexibility for dynamic values.