Skip to main content

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.

Prompt
Show the errors you get without 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

Example with any

Giving v the type any disables type checking, so the same code compiles without errors.

Prompt
Show that the 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

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:

  • unknown must be type-checked before use
  • You cannot read properties of an unknown value 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.

Prompt
Show that 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

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.

script.ts
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 switch statements
  • 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.

Prompt
Show that no value can be assigned to the 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

Function that never returns

A function that always throws can declare the return type never.

script.ts
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.

script.ts
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:

  • undefined means a variable was declared but never given a value
  • null is an explicit assignment that means "no value" or "no object"
  • In TypeScript, both have their own types: undefined and null
  • With strictNullChecks enabled, 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.

Prompt
Show that 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

Optional parameters and properties

An optional parameter is implicitly string | undefined, and an optional interface property is the same as number | undefined.

script.ts
// 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.

script.ts
// 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;