Understanding TypeScript's Special Types: any, unknown, void, and never
The any Type
The any type instructs the TypeScript compiler to bypass static type checking.
- Variables of type
anycan be reassigned to values of any other type. - You can call arbitrary methods or access properties on an
anyvalue with out compile-time errors. - The
anytype is assignable to any other type. - If a variable is declared without a type annotation or initial value, TypeScript infers it as
any.
let dynamicValue: any = 123;
dynamicValue = 'a string value';
dynamicValue = { prop: 'value' };
// Assigning an `any` value to a strictly typed variable
let numericValue: number = dynamicValue;
console.log(numericValue);
Using any eliminates the benefits of TypeScript's static type safety, similar to writing plain JavaScript.
The unknown Type
The unknown type represents a value whose type is not yet known, providing a type-safe alternative to any.
- An
unknownvariable can be assigned a value of any type. - However, you cannot perform operations on an
unknownvalue or assign it to a variable of a different type (exceptanyoranotherunknown`) without first narrowing its type. - Type narrowing can be achieved through type guards, assertions, or control flow analysis.
let uncertainValue: unknown;
uncertainValue = 100;
uncertainValue = false;
uncertainValue = { data: 'test' };
// Valid assignments
let anotherUnknown: unknown = uncertainValue;
let anAnyValue: any = uncertainValue;
// Invalid assignment - causes a compile error
// let isReady: boolean = uncertainValue; // Error
To safely use a unknown value, you must first narrow its type.
function safelyProcess(value: unknown) {
if (typeof value === 'string') {
// Within this block, `value` is type `string`
console.log(value.toUpperCase());
}
if (value instanceof Date) {
// Here, `value` is type `Date`
console.log(value.getFullYear());
}
}
A type assertion can also be used to direct the compiler, but this requires confidence in the runtime type.
let userInput: unknown = fetchInput();
let textLength: number = (userInput as string).length;
The void Type
The void type signifies the absence of a value, typically used as the return type for funcsions that do not return a value.
function logMessage(msg: string): void {
console.log(`Log: ${msg}`);
// No return statement needed, or `return;` / `return undefined;`
}
When used for a variable, a void type can only be assigned undefined or null (with strictNullChecks disabled).
let noReturnValue: void = undefined;
// let invalidAssignment: void = 10; // Compilation error
The never Type
The never type represents values that never occur. It is used for functions that never return normally or for variables that cannot have a value.
neveris a subtype of every other type, meaning anevervalue can be assigned to any variable.- No other type (except
neveritself) is a subtype ofnever. You cannot assign any other value (includingany) to anevervariable. - Common use cases include functions that always throw an error or contain an infinite loop.
// Function that always throws an error
function reportFatalError(message: string): never {
throw new Error(`Fatal: ${message}`);
}
// Function with an infinite loop
function runIndefinitely(): never {
while (true) {
// Perform an ongoing task
}
}
// Example of exhaustive checking in a union type
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function calculateArea(s: Shape): number {
switch (s.kind) {
case 'circle':
return Math.PI * s.radius ** 2;
case 'square':
return s.side ** 2;
default:
// `s` has type `never` here, ensuring all cases are handled
const exhaustiveCheck: never = s;
return exhaustiveCheck;
}
}
Declaring a variable of type never is possible but it cannot be assigned any value.
let impossibleState: never;
// impossibleState = 123; // Compilation error
// impossibleState = null; // Compilation error