Utility Types: Functions and Advanced Utilities (Part 2)

Learn the function- and class-oriented utility types built into TypeScript — Extract, ReturnType, Parameters, ConstructorParameters, InstanceType, ThisParameterType, OmitThisParameter, ThisType, Awaited, and NoInfer — and when each one belongs in real code.

Where the first set of utility types reshapes plain object types, this second set is about deriving types from functions, classes, and asynchronous values — pulling a return type or parameter list out of a function signature, extracting a class's constructor arguments or instance shape, and correctly unwrapping the layers of a Promise. These are the utilities that let generic, reusable code stay perfectly in sync with whatever functions or classes it's wrapping.

Extract: the inverse of Exclude

Extract<T, U> takes a union T and keeps only the members that are assignable to U, discarding the rest — the mirror image of Exclude, which drops matching members instead of keeping them. It's implemented as T extends U ? T : never;, using the same distributive conditional type pattern as Exclude but with the branches swapped.

Extract is most useful when you have a large union and want to isolate a related subset that shares some structural trait, such as pulling only the object-shaped members out of a union that also contains primitives, or isolating every variant of a discriminated union that matches a particular tag type.

• type Value = string | number | (() => void); • type Callable = Extract<Value, Function>; // () => void

ReturnType, Parameters, and function introspection

ReturnType<T> takes a function type and produces the type of its return value, implemented with infer as T extends (...args: any[]) => infer R ? R : any;. It's the standard way to derive a value type from a function without duplicating the return type by hand — especially valuable when the function's return type is complex or comes from a third-party library.

Parameters<T> takes a function type and produces a tuple type of its parameter types, implemented as T extends (...args: infer P) => any ? P : never;. This is commonly used to build a wrapper function that must accept exactly the same arguments as the function it's wrapping, without retyping the parameter list.

Both operate purely on the function's type signature, not its implementation — they work identically whether the function is a simple arrow function, an overloaded function (using the last, most general overload), or a method reference.

• type Fn = (a: number, b: string) => boolean; • type Result = ReturnType<Fn>; // boolean • type Args = Parameters<Fn>; // [a: number, b: string]

Class-oriented utilities: ConstructorParameters, InstanceType, ThisParameterType, OmitThisParameter, ThisType

ConstructorParameters<T> takes a class's constructor type and produces a tuple of the constructor's parameter types, the constructor equivalent of Parameters. InstanceType<T> takes a class's constructor type and produces the type of the object created by calling new on it — the type of an instance, not the class itself. Both are useful when writing generic factory functions or dependency-injection code that needs to accept any class and correctly type both what arguments it needs and what it produces.

ThisParameterType<T> extracts the type of a function's explicit this parameter (the special first parameter used to type what this must be when the function is called as a plain function rather than as a method), or unknown if the function has no this parameter. OmitThisParameter<T> does the reverse, producing a function type with the this parameter stripped out, useful after binding a function so its this context is no longer something the caller needs to supply.

ThisType<T> is unusual among the utilities: it has no effect as a type used directly on a value, and is only meaningful as a marker inside an object literal's contextual type, most often when defining a Vue-style or plugin-style object of methods where this inside every method should refer to a merged shape combining the object's own properties with T.

• class Point { constructor(public x: number, public y: number) {} } • type PointArgs = ConstructorParameters<typeof Point>; // [x: number, y: number] • type PointInstance = InstanceType<typeof Point>; // Point

Awaited and NoInfer

Awaited<T> recursively unwraps Promise types, mirroring exactly what the await keyword does at runtime. Because a Promise can resolve to another Promise (a 'thenable' chain), a single unwrap isn't always enough — Awaited<Promise<Promise<string>>> correctly resolves all the way down to string, not just to Promise<string>, because it's implemented as a recursive conditional type that keeps unwrapping until it hits a non-Promise.

NoInfer<T> is a marker utility that tells TypeScript's inference process not to use a particular usage of a type parameter as a source for inferring that parameter — it forces TypeScript to rely only on other usages instead. This solves a specific, subtle problem: without it, a generic function with a type parameter used in two places (like a default value and a list of allowed values) can end up with the type parameter incorrectly widened by the 'wrong' usage. Wrapping the usage that shouldn't drive inference in NoInfer<T> excludes it from consideration, keeping inference anchored to the intended usage only.

• type A = Awaited<Promise<Promise<string>>>; // string • function pick<T>(value: T, options: NoInfer<T>[]): T { return value; }

Function, class, and async utility types

ReturnType and Parameters both operate on a function's type signature only, never its implementation. InstanceType<T> requires T to be a constructor type, typically obtained with typeof ClassName. Awaited is recursive: it keeps unwrapping nested Promises until it reaches a non-Promise value type. NoInfer<T> excludes a specific usage of T from contributing to type inference, without changing T's declared constraint.

type Fn = (a: number, b: string) => boolean;

type Result = ReturnType<Fn>;   // boolean
type Args = Parameters<Fn>;     // [a: number, b: string]

class Point {
  constructor(public x: number, public y: number) {}
}

type PointArgs = ConstructorParameters<typeof Point>;  // [x: number, y: number]
type PointInstance = InstanceType<typeof Point>;       // Point

function greet(this: { name: string }) {
  return `Hi, ${this.name}`;
}

type GreetThis = ThisParameterType<typeof greet>;      // { name: string }
type GreetBound = OmitThisParameter<typeof greet>;     // () => string

type A = Awaited<Promise<Promise<string>>>;            // string

function pick<T>(value: T, options: NoInfer<T>[]): T {
  return value;
}

Code Examples

ReturnType<typeof fetchUser> captures the exact inferred object shape fetchUser returns without it being written out by hand, and Parameters<typeof fetchUser> captures its parameter tuple, which logCall reuses via a rest parameter to guarantee it always matches fetchUser's real signature, even if that signature changes later. Expected result: Compile error on logCall("1", true): Argument of type 'string' is not assignable to parameter of type 'number'.

type FetchUserArgs = Parameters<typeof fetchUser>;

Combining ReturnType with Awaited correctly unwraps the Promise that an async function's return type is always wrapped in, giving the real resolved value shape. Separately, NoInfer<T> in the second parameter's array type stops that array from contributing to inferring T, so T is inferred only from active, giving the narrower, more useful literal type "dark" instead of a wide union built from every option in the array.

type LoadedUser = Awaited<ReturnType<typeof loadUser>>;

Common mistake: Using only ReturnType<typeof loadUser> without wrapping it in Awaited leaves the type as Promise<{ id: number; name: string }>, which still needs manual unwrapping and misses the point of deriving a usable value type.

function fetchUser(id: number, includeEmail: boolean) {
  return { id, name: "Ada", email: includeEmail ? "[email protected]" : undefined };
}

type FetchUserReturn = ReturnType<typeof fetchUser>;
type FetchUserArgs = Parameters<typeof fetchUser>;

function logCall(...args: FetchUserArgs): void {
  console.log("Called with", args);
}

logCall(1, true);   // OK, matches fetchUser's exact parameter types
logCall("1", true); // Error: first argument must be number
async function loadUser(): Promise<{ id: number; name: string }> {
  return { id: 1, name: "Ada" };
}

type LoadedUser = Awaited<ReturnType<typeof loadUser>>;
// { id: number; name: string } — NOT Promise<{ id: number; name: string }>

function pickTheme<T extends string>(active: T, all: NoInfer<T>[]): T {
  return active;
}

const theme = pickTheme("dark", ["light", "dark", "system"]);
// theme: "dark" | "light" | "system" WITHOUT NoInfer,
// theme: "dark" WITH NoInfer, since inference is anchored to `active` only

Pulling types out of a function and an async chain

Parameters and ReturnType read straight off the function signature; Awaited then unwraps the Promise layer ReturnType leaves behind for an async function.

function fn(a: number): Promise<string> { ... }

  Parameters<typeof fn>        ReturnType<typeof fn>
         |                            |
      [number]                Promise<string>
                                      |
                              Awaited<...>
                                      |
                                   string

Important Rules

Extract<T, U> keeps only the union members of T assignable to U; Exclude<T, U> (from Part 1) does the opposite, dropping them.

ReturnType and Parameters read a function's type signature, not its runtime behavior, and both need typeof when applied to a named function rather than an already-existing function type.

InstanceType<T> and ConstructorParameters<T> both require a constructor type as input, typically obtained by writing typeof ClassName rather than ClassName itself.

Awaited<T> recursively unwraps nested Promises, matching exactly what the await keyword does at runtime — a single ReturnType on an async function is rarely enough on its own.

NoInfer<T> changes only how a particular usage participates in type inference; it does not change what values are ultimately allowed once T has been inferred from elsewhere.

Where this shows up

API client and hook libraries lean on ReturnType and Awaited together to derive a data type directly from an async fetch function, so a React hook like useUser() can expose exactly the shape that function resolves to without a parallel, hand-maintained interface. Dependency-injection and plugin systems use ConstructorParameters and InstanceType to write generic factories that accept any class and correctly type both the constructor arguments they must supply and the instance they hand back, and NoInfer shows up in configuration-style APIs (like state machines or fixed option lists) to stop a broad options array from accidentally widening the inferred type of the primary selected value.

type Args = Parameters<fetchUser>; // fetchUser is a value, not a type type Args = Parameters<typeof fetchUser>;

Parameters (like ReturnType, ConstructorParameters, and InstanceType) expects a type, not a value. typeof must be used to lift the named function or class into its type before these utilities can operate on it.

type Result = ReturnType<typeof loadUser>; // used directly as the resolved value type type Result = Awaited<ReturnType<typeof loadUser>>;

For an async function, ReturnType alone produces the Promise-wrapped type, not the resolved value; forgetting to wrap it in Awaited is a very common source of a type that's technically correct but practically useless without another manual unwrap.

[multiple-choice] What does Extract<T, U> do compared to Exclude<T, U>? A. They are identical B. Extract keeps only the members of T assignable to U; Exclude drops them C. Extract only works on object types D. Exclude requires a class, Extract requires a function

B. Extract keeps only the members of T assignable to U; Exclude drops them — Extract<T, U> is the inverse of Exclude<T, U> — it keeps the union members assignable to U and discards the rest, while Exclude does the opposite.

[predict-output] What does Awaited<Promise<Promise<number>>> resolve to? A. Promise<number> B. Promise<Promise<number>> C. number D. never

C. number — Awaited recursively unwraps nested Promises until it reaches a non-Promise type, so it fully resolves down to number rather than stopping after one layer.

[multiple-choice] Why is typeof needed when writing ReturnType<typeof myFunction> instead of ReturnType<myFunction>? A. It isn't needed, both work identically B. ReturnType expects a type, and typeof lifts the value myFunction into its corresponding function type C. typeof makes the function run at compile time D. ReturnType only works on arrow functions

B. ReturnType expects a type, and typeof lifts the value myFunction into its corresponding function type — myFunction is a runtime value, not a type, so it can't be passed directly to a generic type utility; typeof converts it into the type TypeScript inferred for it, which ReturnType can then operate on.

Given class Repository<T> { constructor(private table: string) {} async findAll(): Promise<T[]> { return []; } }, write a type RepoCtorArgs using ConstructorParameters, a type RepoInstance using InstanceType, and a type FindAllResult that uses ReturnType and Awaited together on the findAll method to get the resolved array type.

class Repository<T> { constructor(private table: string) {} async findAll(): Promise<T[]> { return []; } } type RepoCtorArgs = ConstructorParameters<typeof Repository<string>>; type RepoInstance = InstanceType<typeof Repository<string>>; type FindAllResult = Awaited<ReturnType<RepoInstance["findAll"]>>; // string[]

For FindAllResult, you'll need typeof on an instance method reference, or ReturnType<Repository<T>["findAll"]> combined with Awaited.