DEV Community

137Foundry
137Foundry

Posted on

ReturnType and Parameters in TypeScript: Utility Types for Wrapper Functions

Two TypeScript utility types that do not get enough attention are ReturnType<T> and Parameters<T>. Both infer types from existing function signatures rather than requiring you to declare them separately. Once you understand what they do, you will find situations where they are the right tool several times per week.

What ReturnType Does

ReturnType<T> extracts the return type of a function type T. If the function returns a Promise<User>, ReturnType gives you Promise<User>. If it returns a simple string, you get string.

function fetchUser(id: string): Promise<User> {
  // ...
}

type FetchResult = ReturnType<typeof fetchUser>;
// => Promise<User>
Enter fullscreen mode Exit fullscreen mode

The practical value is in situations where the return type is complex or inferred. If a function returns an inferred type (TypeScript figures it out from the implementation), you can access that type without needing to import it, write it out, or even know its exact shape.

function buildQueryParams(filters: FilterOptions) {
  return {
    page: filters.page ?? 1,
    limit: filters.limit ?? 25,
    sort: filters.sort ?? 'createdAt',
    order: filters.order ?? 'desc',
    ...filters.extra,
  };
}

// The return type is inferred by TypeScript -- complex to write out manually
type QueryParams = ReturnType<typeof buildQueryParams>;
Enter fullscreen mode Exit fullscreen mode

Now QueryParams is exactly whatever TypeScript infers as the return type of buildQueryParams. If you add or remove a field from the return object, QueryParams updates automatically.

What Parameters Does

Parameters<T> extracts the parameter types of a function as a tuple.

function createProduct(
  name: string,
  price: number,
  category: ProductCategory,
  options?: Partial<ProductOptions>
): Promise<Product> {
  // ...
}

type CreateProductArgs = Parameters<typeof createProduct>;
// => [name: string, price: number, category: ProductCategory, options?: Partial<ProductOptions>]
Enter fullscreen mode Exit fullscreen mode

This is useful when you need to refer to a function's argument types in another context -- without re-declaring them or importing each type individually.

The Core Use Case: Typed Wrapper Functions

Both ReturnType<T> and Parameters<T> really earn their place in wrapper functions. A wrapper that delegates to another function needs to accept the same arguments and return the same type. Without these utility types, you have to repeat the signature.

With them, you can write a wrapper that stays accurate automatically when the wrapped function changes:

function withLogging<T extends (...args: unknown[]) => unknown>(fn: T) {
  return (...args: Parameters<T>): ReturnType<T> => {
    console.log(`[${fn.name}] called with`, args);
    const result = fn(...args) as ReturnType<T>;
    console.log(`[${fn.name}] returned`, result);
    return result;
  };
}

const loggedFetch = withLogging(fetchUser);
// loggedFetch has the same signature as fetchUser
Enter fullscreen mode Exit fullscreen mode

The withLogging function does not know anything about what T accepts or returns. It captures those types through Parameters<T> and ReturnType<T> and forwards them. If fetchUser changes its signature, loggedFetch updates to match automatically.

Async Functions and ReturnType

One thing to know: for async functions, ReturnType<T> returns Promise<something>, not something. If you want the resolved value type, wrap it with Awaited:

async function fetchUser(id: string): Promise<User> {
  // ...
}

type A = ReturnType<typeof fetchUser>;
// => Promise<User>

type B = Awaited<ReturnType<typeof fetchUser>>;
// => User
Enter fullscreen mode Exit fullscreen mode

Awaited<T> recursively unwraps the promise. Awaited<Promise<Promise<User>>> gives you User. This combination -- Awaited<ReturnType<typeof fn>> -- is a common pattern when building caches, response stores, or any place where you work with the resolved value of an async function.

Middleware and Higher-Order Functions

Parameters<T> is useful for middleware patterns where multiple functions receive the same set of arguments.

type RequestHandler = (req: Request, res: Response, next: NextFunction) => void;

// All middleware shares the same parameter tuple
function requireAuth(...args: Parameters<RequestHandler>): void {
  const [req, res, next] = args;
  if (!req.headers.authorization) {
    res.status(401).json({ error: 'Unauthorized' });
    return;
  }
  next();
}
Enter fullscreen mode Exit fullscreen mode

In Express-style APIs, this means you can define one canonical handler type and derive every middleware signature from it. Changing RequestHandler propagates to all derived functions.

ConstructorParameters and InstanceType

Two related utility types follow the same pattern for classes:

ConstructorParameters<T> extracts the parameter types of a class constructor. InstanceType<T> extracts the instance type (what new T() returns).

class ApiClient {
  constructor(
    public baseUrl: string,
    public apiKey: string,
    public timeout: number = 5000
  ) {}
}

type ClientArgs = ConstructorParameters<typeof ApiClient>;
// => [baseUrl: string, apiKey: string, timeout?: number]

type ClientInstance = InstanceType<typeof ApiClient>;
// => ApiClient
Enter fullscreen mode Exit fullscreen mode

InstanceType<T> is useful in generic factory functions where you want to express "whatever this class constructor produces":

function createService<T extends new (...args: unknown[]) => unknown>(
  Ctor: T,
  ...args: ConstructorParameters<T>
): InstanceType<T> {
  return new Ctor(...args) as InstanceType<T>;
}
Enter fullscreen mode Exit fullscreen mode

Combining with Other Utility Types

ReturnType<T> and Parameters<T> compose naturally with other utility types:

// Get the first parameter type of a function
type FirstParam<T extends (...args: unknown[]) => unknown> = Parameters<T>[0];

// Make all parameters optional (for curried/partial application)
type OptionalParams<T extends (...args: unknown[]) => unknown> =
  Partial<Parameters<T>>;

// Unwrap promise from return type
type Resolved<T extends (...args: unknown[]) => Promise<unknown>> =
  Awaited<ReturnType<T>>;
Enter fullscreen mode Exit fullscreen mode

These patterns come up in testing (partial mocks that only implement some parameters), dependency injection (factory functions), and middleware chains.

Typed Mocks in Testing

ReturnType<T> and Parameters<T> are useful in test setups where you want mock functions that match the real implementation's signature exactly.

function createMock<T extends (...args: unknown[]) => unknown>(fn: T) {
  return {
    mock: jest.fn() as jest.MockedFunction<T>,
    calls: [] as Parameters<T>[],
    returnValues: [] as ReturnType<T>[],
  };
}
Enter fullscreen mode Exit fullscreen mode

More commonly, when you have a service class and want a typed mock object, ReturnType lets you extract the instance shape:

// Real service
class EmailService {
  async send(to: string, subject: string, body: string): Promise<boolean> {
    // ...
  }
}

// Mock has the same interface shape as the real service
type EmailServiceMock = {
  [K in keyof EmailService]: jest.MockedFunction<EmailService[K]>;
};
Enter fullscreen mode Exit fullscreen mode

This approach means the mock type updates automatically when EmailService gains new methods. If you add a sendBatch method, any test that uses EmailServiceMock without implementing sendBatch becomes a type error. This catches gaps in test setup before runtime.

When Not to Use Them

ReturnType<T> and Parameters<T> are most useful when the function signature is the single source of truth and you want other types to derive from it. If you are writing a function specifically to be wrapped, you may want to define the input and output types independently and annotate the function with them -- that way the types are explicit and importable without using typeof fn.

The utility types are best for external functions (library code, framework APIs, existing code you do not control) where you cannot easily export the underlying types, and for generic wrappers where the whole point is type-agnostic delegation. In those cases, ReturnType<T> and Parameters<T> let you write wrappers that stay accurate without depending on implementation details that may not be exported.

Where to Learn More

The TypeScript official documentation covers both utility types with their type-level implementations. MDN Web Docs covers the JavaScript typeof operator that these utility types build on at the type level. For advanced patterns combining ReturnType and Parameters with conditional types, the ts-toolbelt library has extensive examples.

For the complete utility type picture -- Partial, Pick, Omit, Record, Extract, Exclude, NonNullable, ReturnType, and Parameters together -- see the 137Foundry article on TypeScript Utility Types: Practical Code Snippets and Patterns. It covers each type with real production code and shows how they compose across API layers, wrappers, and test fixtures.

Top comments (0)