TypeScript Special Types
TypeScript has several special types with their own behaviour in the type system.
You use them when the type of a value is not known in advance, or when you need to handle JavaScript values like null in a type-safe way.
Note: These special types are part of TypeScript's type system. They help make your code safer and easier to read.
Type: any
any is the most flexible type in TypeScript.
It tells the compiler to skip type checking for that variable.
This can be useful, but use it sparingly — it throws away TypeScript's type safety.
When to use any:
- When migrating JavaScript code to TypeScript
- When working with dynamic content whose type is unknown
- When you need to opt out of type checking in one specific place
Note: any can get you past errors because it disables type checking, but TypeScript can no longer protect you, and tools that rely on type data, such as auto completion, stop working.
Remember, it should be avoided at "any" cost...
Example without any
This example does not use any. u is inferred as boolean, so assigning a string and passing it to Math.round are both errors.
any when a variable is used as different types.
Declare u = true (inferred boolean), assign the string "string" to it, then pass u to Math.round — both lines must fail to compile: "Type 'string' is not assignable to type 'boolean'" and "Argument of type 'boolean' is not assignable to parameter of type 'number'".
Output: TypeScript file and captured output
let u = true;
u = "string"; // Error: Type 'string' is not assignable to type 'boolean'.
Math.round(u); // Error: Argument of type 'boolean' is not assignable to parameter of type 'number'.
Example with any
Giving v the type any disables type checking, so the same code compiles without errors.
any type disables type checking.
Declare v: any = true, assign the string "string" to it, then pass v to Math.round — neither line produces an error, and nothing is printed.
Output: TypeScript file and captured output
let v: any = true;
v = "string"; // no error as it can be "any" type
Math.round(v); // no error as it can be "any" type
Type: unknown
unknown is the type-safe counterpart of any.
It means "this could be anything, so you must check what it is before you use it".
Key differences between unknown and any:
unknownmust be type-checked before use- You cannot read properties of an
unknownvalue without a type assertion (a cast) - You cannot call or construct a value of type
unknown
TypeScript stops you from using an unknown value until you have checked its type, as the example shows.
When to use unknown:
- When working with data from external sources (APIs, user input, etc.)
- When you want type safety but still need flexibility
- When migrating from JavaScript to TypeScript in a type-safe way
Type narrowing with unknown: you can narrow an unknown value to a concrete type with type guards such as typeof and Array.isArray, as in the snippet below.
Example
w accepts a number, a string and an object. Calling a method on it directly is an error; after a typeof check and a cast, the call is allowed and prints its message.
unknown accepts any value but must be checked or cast before use.
Declare w: unknown = 1, reassign it to a string, then to an object with a runANonExistentMethod method that logs "I think therefore I am" (cast with as to an object type). Keep a commented-out direct call that would error with "Object is of type 'unknown'", then guard with typeof w === 'object' && w !== null and call the method through a cast inside the if.
Output: TypeScript file and captured output
let w: unknown = 1;
w = "string"; // no error
w = {
runANonExistentMethod: () => {
console.log("I think therefore I am");
}
} as { runANonExistentMethod: () => void}
// How can we avoid the error for the code commented out below when we don't know the type?
// w.runANonExistentMethod(); // Error: Object is of type 'unknown'.
if(typeof w === 'object' && w !== null) {
(w as { runANonExistentMethod: Function }).runANonExistentMethod();
}
// Although we have to cast multiple times we can do a check in the if to secure our type and have a safer casting
Narrowing unknown with type guards
Inside each branch TypeScript treats value as a string or as an array, so its members can be used safely.
function processValue(value: unknown) {
if (typeof value === 'string') {
// value is now treated as string
console.log(value.toUpperCase());
} else if (Array.isArray(value)) {
// value is now treated as any[]
console.log(value.length);
}
}
Type: never
never is the type of values that never occur.
It says that something never happens, or should never happen.
Common use cases for never:
- Functions that never return (they always throw an error or loop forever)
- Type guards that never pass type checking
- Exhaustiveness checking in discriminated unions
The snippets below show never in action, and the example shows what happens when you try to assign a value to it.
When to use never:
- For functions that will never return a value
- In type guards that should never match
- For exhaustive type checking in
switchstatements - In generic types to mark certain cases as impossible
Basic never type
No value can be assigned to never, so assigning true is a compile error.
never type.
Declare x: never and assign true to it — the compiler must report "Type 'boolean' is not assignable to type 'never'".
Output: TypeScript file and captured output
let x: never = true; // Error: Type 'boolean' is not assignable to type 'never'.
Function that never returns
A function that always throws can declare the return type never.
function throwError(message: string): never {
throw new Error(message);
}
Exhaustiveness check with never
In the default branch shape should have no cases left, so assigning it to a never variable makes TypeScript complain if a shape was forgotten.
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
default:
// TypeScript knows this should never happen
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
Type: undefined & null
In TypeScript, undefined and null each have their own type, just like string or number.
By default these values can be assigned to any other type. You can change that with TypeScript's strict null checks.
Key points about undefined and null:
undefinedmeans a variable was declared but never given a valuenullis an explicit assignment that means "no value" or "no object"- In TypeScript, both have their own types:
undefinedandnull - With
strictNullChecksenabled, you must handle these types explicitly
Optional parameters and properties — a ? after a parameter or property name makes it optional, which adds undefined to its type.
Nullish coalescing and optional chaining — ?? supplies a default only when a value is null or undefined, and ?. reads nested properties without crashing when something in the chain is missing.
Important: These types are most useful when strictNullChecks is enabled in your tsconfig.json. Then null and undefined can only be assigned to themselves and to any.
To enable strict null checks, add "compilerOptions": { "strictNullChecks": true } to your tsconfig.json.
Basic Usage
y has the type undefined and z has the type null.
undefined and null are their own types in TypeScript.
Declare y with the type undefined holding undefined, and z with the type null holding null. Nothing is printed.
Output: TypeScript file and captured output
let y: undefined = undefined;
let z: null = null;
Optional parameters and properties
An optional parameter is implicitly string | undefined, and an optional interface property is the same as number | undefined.
// Optional parameter (implicitly `string | undefined`)
function greet(name?: string) {
return `Hello, ${name || 'stranger'}`;
}
// Optional property in an interface
interface User {
name: string;
age?: number; // Same as `number | undefined`
}
Nullish coalescing and optional chaining
?? falls back to 'default' only for null or undefined, and ?. safely reads user.address.street.
// Nullish coalescing (??) - only uses default if value is null or undefined
const value = input ?? 'default';
// Optional chaining (?.) - safely access nested properties
const street = user?.address?.street;