Skip to content
JL

Home

About

Blog

Contact

Shop

Portfolio

Privacy

TOS

Click to navigate

  1. Home
  2. Joshua R. Lehman's Blog
  3. Utility Types Part 5: ReturnType and Parameters

Table of contents

  • Share on X
  • Discuss on X

Related Articles

Utility Types Part 4: Exclude and Extract
TypeScript
9m
Sep 6, 2026

Utility Types Part 4: Exclude and Extract

Rather than operating on object types like Pick and Omit, Exclude and Extract operate on union types — filtering members in or out by assignability. Exclude removes every union member assignable to a given type. Extract keeps only those members. Together they give you precise control over union composition without restating individual members.

#Utility Types#Exclude+5
Utility Types Part 3: Pick and Omit
TypeScript
9m
Aug 30, 2026

Utility Types Part 3: Pick and Omit

Rather than transforming every property of a type — as Partial, Required, and Readonly do — Pick and Omit restructure a type by including or excluding specific named properties. Pick produces a type containing only the properties you name. Omit produces a type containing every property except the ones you name.

#Utility Types#Pick+5
Utility Types Part 2: Readonly and Record
TypeScript
10m
Aug 23, 2026

Utility Types Part 2: Readonly and Record

Two utility types that occupy opposite ends of the structural transformation space: Readonly locks an object's properties against mutation at the type level, while Record builds typed dictionary structures from a set of keys and a value type. Neither modifies optionality — they operate on shape and mutability instead.

#Utility Types#Readonly+5
Ask me anything! 💬

© Joshua R. Lehman

Full Stack Developer

Crafted with passion • Built with modern web technologies

2026 • All rights reserved

Contents

  • Function Types as First-Class Citizens
  • What ReturnType Does
  • How ReturnType Is Implemented
  • What Parameters Does
  • How Parameters Is Implemented
  • Practical Patterns for ReturnType
  • Practical Patterns for Parameters
  • Combining ReturnType and Parameters
  • What Is Next
  • Key Takeaways
TypeScript

Utility Types Part 5: ReturnType and Parameters

September 13, 2026•9 min read
Joshua R. Lehman
Joshua R. Lehman
Author
TypeScript ReturnType and Parameters utility types extracting function return and argument types
Utility Types Part 5: ReturnType and Parameters

The utility types covered so far — Partial, Required, Readonly, Record, Pick, Omit, Exclude, and Extract — all operate on object types or union types. ReturnType and Parameters operate on something different: function types. They extract the type information embedded in a function's signature — what it returns and what arguments it accepts. This matters because in TypeScript, functions are values, and their types carry all the information needed to derive related types without restating them.

Function Types as First-Class Citizens

In TypeScript, a function has a type that includes its parameter types and its return type:

function fetchUser(id: number, options: { cache: boolean }): Promise<User> {
  // ...
}
 
// The type of fetchUser is:
// (id: number, options: { cache: boolean }) => Promise<User>

If you want to use User as a standalone type elsewhere, you could import it. But if you want the return type of fetchUser without importing User directly — perhaps because User is an internal type, or because the function's return type is complex and defined inline — you need a way to extract it from the function type. That is what ReturnType does. Similarly, if you want the parameter types so you can build a wrapper with the same signature, that is what Parameters does.

Both utility types let you derive types from functions without repeating yourself, keeping derived types in sync with the function signature automatically.

What ReturnType Does

ReturnType<F> takes a function type F and returns the type of the value that function returns.

function getUser(): { id: number; name: string; email: string } {
  return { id: 1, name: "Alice", email: "[email protected]" };
}
 
type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string }

typeof getUser extracts the function type from the getUser value. ReturnType<typeof getUser> then extracts the return type from that function type.

For functions that return promises, ReturnType extracts the Promise wrapper — not the resolved type:

async function fetchPost(
  id: number
): Promise<{ title: string; content: string }> {
  // ...
}
 
type FetchPostResult = ReturnType<typeof fetchPost>;
// Promise<{ title: string; content: string }>
 
// To get the resolved type, use Awaited:
type PostData = Awaited<ReturnType<typeof fetchPost>>;
// { title: string; content: string }

Awaited<T> (available since TypeScript 4.5) unwraps Promise types. The combination Awaited<ReturnType<F>> is a common pattern for extracting the resolved result type of an async function.

Awaited Unwraps Promises Recursively

Awaited<T> recursively unwraps promise-like types: Awaited<Promise<Promise<string>>> is string. This handles scenarios where functions return nested promises or thenable objects. When extracting the final resolved type of an async function, always use Awaited<ReturnType<F>> rather than manually unwrapping the Promise wrapper, since Awaited handles all edge cases correctly.

How ReturnType Is Implemented

ReturnType<F> is defined as:

type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;

The constraint T extends (...args: any) => any ensures T is a callable type. The conditional type then uses infer R to capture the return type in the position where it appears in the function signature. If T matches the function shape, the result is R. If T does not match (which cannot happen given the constraint), the result is any as a fallback.

The infer keyword here is doing the key work: it extracts the return type from the function type structurally, without the function needing to declare its return type explicitly in a separate type alias.

ReturnType Requires a Function Type Not a Function Value

ReturnType takes a function type, not a function value. ReturnType<getUser> is an error — getUser is a value. ReturnType<typeof getUser> is correct because typeof getUser produces the type of the value. This is a consistent pattern across all type-level operations on runtime values: you always need typeof to bridge from the value world to the type world.

What Parameters Does

Parameters<F> takes a function type F and returns a tuple type containing the types of its parameters, in order.

function createPost(title: string, content: string, authorId: number): Post {
  // ...
}
 
type CreatePostArgs = Parameters<typeof createPost>;
// [title: string, content: string, authorId: number]

The result is a labelled tuple — each element has the name from the original parameter, which improves readability in IDE tooltips. You can index into the tuple to get individual parameter types:

type TitleArg = Parameters<typeof createPost>[0]; // string
type ContentArg = Parameters<typeof createPost>[1]; // string
type AuthorIdArg = Parameters<typeof createPost>[2]; // number

For functions with no parameters, Parameters returns an empty tuple []. For rest parameters, the tuple ends with an array type:

function log(level: string, ...messages: string[]): void {
  // ...
}
 
type LogArgs = Parameters<typeof log>;
// [level: string, ...messages: string[]]

How Parameters Is Implemented

Parameters<F> is defined as:

type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;

Like ReturnType, it constrains T to a function type and uses infer to capture the type at a specific structural position — here, the parameter list. The ...args: infer P syntax captures all parameters as a tuple. The result is that tuple, or never if the constraint is somehow not met.

The parallelism between the two implementations is instructive:

type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;
type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;

ReturnType puts infer in the return position; Parameters puts infer in the parameter position. Both use the same conditional type mechanism with infer to extract the type from its structural context.

Practical Patterns for ReturnType

Deriving types from factory functions. When a factory function constructs a complex object, ReturnType extracts the constructed type without a separate type declaration:

function createAppContext() {
  return {
    db: createDatabase(),
    cache: createCache(),
    logger: createLogger(),
    config: loadConfig(),
  };
}
 
type AppContext = ReturnType<typeof createAppContext>;

If the factory function changes — adding a new service, changing an existing one — AppContext updates automatically. No manual synchronisation needed.

Extracting types from libraries. When using a library that exports functions but not their return types:

import { createStore } from "some-library";
 
// createStore returns a complex object type not exported from the library
type Store = ReturnType<typeof createStore>;
 
// Now you can use Store as a parameter type in your own functions
function connectComponent(store: Store): void {
  // ...
}

This is particularly useful when library types are internal or when the return type is generated dynamically.

Typed mocks in tests. When mocking functions in tests, the mock must match the real function's return type. ReturnType ensures the mock stays in sync:

function createMockUser(): ReturnType<typeof fetchUser> {
  return {
    id: 999,
    name: "Mock User",
    email: "[email protected]",
  };
}

If fetchUser's return type changes, the mock function's return type annotation updates automatically and any incompatibilities surface at compile time rather than at runtime in tests.

ReturnType With Generic Functions

ReturnType works on generic functions by resolving them with any for unconstrained type parameters. ReturnType<typeof identity> where identity is <T>(x: T) => T produces unknown (since T becomes unknown when instantiated with any). If you need the return type for a specific instantiation, use ReturnType<typeof identity<string>> — but this syntax requires TypeScript 5.1+. For earlier versions, define a specialised wrapper: (x: string) => string.

Practical Patterns for Parameters

Building wrapper functions with identical signatures. A wrapper function that adds logging, timing, or error handling often needs to accept the same arguments as the wrapped function:

function withLogging<F extends (...args: any[]) => any>(
  fn: F,
  label: string
): (...args: Parameters<F>) => ReturnType<F> {
  return (...args: Parameters<F>): ReturnType<F> => {
    console.log(`[${label}] called with`, args);
    const result = fn(...args);
    console.log(`[${label}] returned`, result);
    return result;
  };
}
 
const loggedFetchUser = withLogging(fetchUser, "fetchUser");
// loggedFetchUser has the same parameter types as fetchUser

The wrapper withLogging accepts any function F and returns a new function with exactly the same parameter types (Parameters<F>) and return type (ReturnType<F>). This is the canonical higher-order function typing pattern in TypeScript.

Forwarding arguments through middleware. API middleware layers that receive and forward arguments:

function withAuth<F extends (userId: number, ...rest: any[]) => any>(
  fn: F
): (...args: Parameters<F>) => ReturnType<F> {
  return (...args: Parameters<F>): ReturnType<F> => {
    const [userId] = args;
    validateUser(userId);
    return fn(...args);
  };
}

Partially applied functions. When building a partial application utility, Parameters allows you to type the remaining arguments:

function partial<
  F extends (...args: any[]) => any,
  FirstArg extends Parameters<F>[0],
>(
  fn: F,
  first: FirstArg
): (
  ...rest: Parameters<F> extends [any, ...infer Rest] ? Rest : never
) => ReturnType<F> {
  return (...rest) => fn(first, ...rest);
}

This is more complex, but the principle is the same: Parameters<F> gives you the full parameter tuple, and you can slice and manipulate it as a type-level tuple to express partial application correctly.

Combining ReturnType and Parameters

ReturnType and Parameters are often used together to describe a function that proxies or wraps another. The pattern appears frequently in:

  • Decorator implementations that must preserve the wrapped function's signature
  • Memoisation wrappers that cache calls with the same arguments
  • Rate-limiting or debouncing wrappers
  • Type-safe middleware pipelines

The core pattern:

function memoize<F extends (...args: any[]) => any>(fn: F): F {
  const cache = new Map<string, ReturnType<F>>();
 
  return ((...args: Parameters<F>): ReturnType<F> => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key)!;
    const result = fn(...args);
    cache.set(key, result);
    return result;
  }) as F;
}

memoize accepts any function F and returns a function of type F — preserving the exact signature including parameter types and return type. The internal implementation uses Parameters<F> and ReturnType<F> to type the argument spreading and cache storage correctly.

The Signature Preservation Pattern

The pattern (fn: F): (...args: Parameters<F>) => ReturnType<F> appears in nearly every higher-order function in typed TypeScript. It is the idiomatic way to say "I return a function with the same shape as the input function." Memorising this pattern — and understanding that Parameters produces a tuple you can spread with ...args — removes one of the most common typing challenges when building function utilities.

What Is Next

ReturnType and Parameters complete this survey of TypeScript's built-in utility types. You have now seen utility types that operate on object properties (Partial, Required, Readonly, Pick, Omit), on union members (Exclude, Extract), and on function signatures (ReturnType, Parameters). The patterns that underlie all of them — mapped types, conditional types, infer, and distributive evaluation — are the same tools you use to build your own custom utility types. The next post applies these foundations to building custom utility types from scratch, combining the mechanisms explored throughout this series into purpose-built type transformations.

Key Takeaways

  • ReturnType<F> extracts the return type of a function type using infer R in the return position; combine with Awaited<T> to get the resolved type of async functions
  • Parameters<F> extracts the parameter types as a labelled tuple using infer P in the parameter position; index into the tuple to access individual argument types
  • Both require a function type as input — use typeof functionName to get the type from a function value
  • The signature preservation pattern (fn: F): (...args: Parameters<F>) => ReturnType<F> is the idiomatic way to type higher-order functions, wrappers, and decorators
  • ReturnType keeps derived types synchronised with function signatures: if the function's return type changes, all types derived from it update automatically
  • Both utility types are implemented with infer in conditional types — ReturnType infers in the return position, Parameters infers in the parameter position; the parallelism makes them easy to remember and extend