TypeScript

ReturnType, Parameters, and Readonly in TypeScript

Three more TypeScript utility types that I reach for regularly: ReturnType, Parameters, and Readonly. Each solves a specific problem.

23 Apr 2024

ReturnType, Parameters, and Readonly in TypeScript

Three more TypeScript utility types that I reach for regularly: ReturnType, Parameters, and Readonly. Each solves a specific problem.

ReturnType — extract what a function returns

Instead of manually duplicating a function's return type, let TypeScript infer it:

Text
function greet(name: string): string {
  return `Hello, ${name}!`;
}

type GreetReturnType = ReturnType<typeof greet>;

GreetReturnType is string. If I later change greet to return an object, the type updates automatically. No manual sync needed.

This shines when you're wrapping third-party functions and need their return type without importing internal types.

Parameters — extract a function's argument types

Same idea, but for inputs:

Text
type GreetParameters = Parameters<typeof greet>;

GreetParameters is [string] — a tuple of the argument types. Useful when building wrapper functions or middleware that forwards arguments to another function.

Readonly — prevent mutation

Readonly makes all properties of a type immutable:

Text
interface Config {
  host: string;
  port: number;
}

const config: Readonly<Config> = { host: 'localhost', port: 3000 };
// config.port = 8080; // Compile error

Once set, you can't reassign any property. This is great for configuration objects, Redux-style state, or any data that should not change after creation.

The trade-off

Readonly only enforces immutability at the type level. At runtime, JavaScript doesn't care. If someone casts to any, they can still mutate. For true runtime immutability, pair it with Object.freeze.

These utility types reduce boilerplate and keep your types in sync with your actual code. But don't chain them into unreadable nesting. If a type gets complex, give it a name.

Keep reading