Advanced Utility Types

Reviewed & published by Brayan K

TypeScript ships extraction utility types — Parameters, ReturnType, ConstructorParameters, InstanceType, Awaited, and NonNullable — that pull useful types out of existing functions, classes, and promises, all built on the infer keyword you can use yourself.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

These utilities are like X-ray glasses for your types. Point them at a function and read off its inputs (Parameters) and output (ReturnType); point them at a class and see what it's built from (ConstructorParameters) and what it becomes (InstanceType); point them at a promise and see what's inside (Awaited). And infer is grinding your own lens — once you understand it, you can X-ray any structure you like.

1. Extracting From Functions & Classes

Four utilities pull types out of callables and classes: Parameters<F> (args tuple), ReturnType<F> (result), ConstructorParameters<C> (constructor args), and InstanceType<C> (the new result). Combined with typeof, they derive types straight from your code so nothing drifts out of sync.

function createUser(name: string, age: number) {
  return { id: 1, name, age };
}
class Point { constructor(public x: number, public y: number) {} }

type Args   = Parameters<typeof createUser>;            // [string, number]
type User   = ReturnType<typeof createUser>;            // { id: number; name: string; age: number }
type PArgs  = ConstructorParameters<typeof Point>;      // [number, number]
type PInst  = InstanceType<typeof Point>;               // Point

// ThisParameterType<F> grabs the 'this' parameter's type:
function describe(this: { label: string }): string { return this.label; }
type Ctx = ThisParameterType<typeof describe>;          // { label: string }
// These utilities EXTRACT pieces of other types. Compile-time only, but the
// runtime intuition is concrete:
//   Parameters<F>             = a function's args as a tuple
//   ReturnType<F>             = what it returns
//   ConstructorParameters<C>  = a class's constructor args
//   InstanceType<C>           = the object 'new C()' produces

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() { return "(" + this.x + ", " + this.y + ")"; }
}

// ConstructorParameters<typeof Point> would be [number, number] — model as a tuple:
const ctorArgs = [3, 4];
const p = new Point(...ctorArgs);  // forward the tuple to the constructor
// InstanceType<typeof Point> would be Point — exactly what 'new' gives us:
console.log(p.toString()); // (3, 4)
console.log(p.x, p.y);     // 3 4

2. Awaited<T> & NonNullable<T>

Awaited<T> recursively unwraps promise layers to the resolved value type — the type-level version of await. NonNullable<T> strips null and undefined from a type, mirroring the runtime act of filtering out empty values.

// Awaited<T> recursively unwraps Promises to the resolved value type:
type A = Awaited<Promise<string>>;           // string
type B = Awaited<Promise<Promise<number>>>;  // number  (fully flattened)

// NonNullable<T> strips null and undefined from a type:
type C = NonNullable<string | null | undefined>; // string
type D = NonNullable<number | null>;             // number

// Compose utilities to get an async function's resolved result:
async function loadUser() { return { id: 1, name: "Ada" }; }
type LoadedUser = Awaited<ReturnType<typeof loadUser>>; // { id: number; name: string }
// Awaited<T> peels Promise layers; NonNullable<T> removes null and undefined.
// Runtime intuition: awaiting a value, and filtering out null/undefined.

// NonNullable<T> in spirit — drop the empty values from a list:
const values = ["a", null, "b", undefined, "c"];
const cleaned = values.filter((v) => v !== null && v !== undefined);
console.log(cleaned); // [ 'a', 'b', 'c' ]   (NonNullable in spirit)

// Awaited<T> in spirit — fully resolve, even nested promises:
async function main() {
  const nested = Promise.resolve(Promise.resolve(42));
  const value = await nested; // Awaited<Promise<Promise<number>>> = number
  console.log(value);         // 42
  console.log(value + 8);     // 50
}
main();

3. Build Your Own With infer

Every utility above is built from conditional types plus the infer keyword, which captures part of a type into a variable. T extends (...args: any[]) => infer R ? R : never is exactly how ReturnType works — and you can write your own (recursive!) utilities the same way.

// 'infer R' captures a type in a conditional type — this is how the built-ins
// are implemented. Here are simplified versions you could write yourself:

type MyReturnType<T> =
  T extends (...args: any[]) => infer R ? R : never;

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

type MyAwaited<T> =
  T extends Promise<infer V> ? MyAwaited<V> : T; // recurse to flatten

type R = MyReturnType<() => string>;   // string
type P = MyParameters<(a: number) => void>; // [number]
type W = MyAwaited<Promise<Promise<boolean>>>; // boolean
// Build your OWN utility with 'infer'. MyReturnType<T> uses
//   T extends (...args: any[]) => infer R ? R : never
// to capture the return type. We can't read a type at runtime, but we CAN observe
// the returned VALUE — which is what that type describes.

function describe(fn, ...args) {
  const result = fn(...args);  // call it...
  return typeof result;         // ...and report the runtime type of the result
}

const makeUser = (name) => ({ name, active: true });
const getCount = () => 42;
const getName = () => "Ada";

console.log(describe(makeUser, "Ada")); // object   (return type { name, active })
console.log(describe(getCount));        // number   (MyReturnType would be number)
console.log(describe(getName));         // string   (MyReturnType would be string)

🎯 Your Turn

Mirror NonNullable<T> across an array by filtering out both null and undefined. Fill in the two blanks marked ___, then run it.

// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
// This mirrors NonNullable<T> applied across an array: drop null AND undefined.
// (x != null is true ONLY when x is neither null nor undefined.)

function compact(arr) {
  return arr.filter((x) => x ___ null);  // 👉 the loose "not equal" operator: !=
}

console.log(compact([1, null, 2, undefined, 3])); // [ 1, 2, 3 ]
console.log(compact(["a", ___]));                  // 👉 pass undefined -> [ 'a' ]

// ✅ Expected output:
//    [ 1, 2, 3 ]
//    [ 'a' ]

Common Errors (and the fix)

Pro Tips

📚 Keep Learning

Mini-Challenge: Compose ReturnType + Awaited

No blanks this time — just a brief and a starting outline. Build the async loader and read its resolved config, run it, and check your output against the example in the comments.

// 🎯 MINI-CHALLENGE: compose ReturnType + Awaited.
// In .ts the config type is:  Awaited<ReturnType<typeof loadConfig>>
// 1. async loadConfig() resolves to { theme: "dark", retries: 3 }.
// 2. In main(): await loadConfig(), then log config.theme,
//    config.retries * 2, and how many keys the config has.
//
// ✅ Expected output:
//    dark
//    6
//    2

// your code here

🎉 Lesson Complete

Practice quiz

ReturnType<F> gives you:

  • A function's arguments as a tuple
  • A class instance type
  • What a function returns
  • The 'this' type

Answer: What a function returns. ReturnType<F> extracts the type a function returns.

Parameters<F> produces:

  • A tuple of the function's argument types
  • The return type
  • A union of arguments
  • The first argument only

Answer: A tuple of the function's argument types. Parameters<F> gives the function's argument list as a tuple type.

To use ReturnType on a function value named createUser you write:

  • ReturnType<createUser>
  • ReturnType(createUser)
  • ReturnType[createUser]
  • ReturnType<typeof createUser>

Answer: ReturnType<typeof createUser>. These utilities take a type, so pair them with typeof: ReturnType<typeof createUser>.

ConstructorParameters<typeof Point> extracts:

  • The instance type
  • The constructor's arguments as a tuple
  • The class methods
  • The prototype

Answer: The constructor's arguments as a tuple. ConstructorParameters<C> gives the constructor's arguments as a tuple.

InstanceType<typeof Point> is:

  • The type 'new Point()' produces
  • The constructor args
  • A union of methods
  • Always never

Answer: The type 'new Point()' produces. InstanceType<C> is the type of object that calling new on the class produces.

Awaited<Promise<Promise<number>>> resolves to:

  • Promise<number>
  • Promise<Promise<number>>
  • number
  • unknown

Answer: number. Awaited recursively unwraps every Promise layer, flattening to number.

NonNullable<string | null | undefined> is:

  • string | null
  • string
  • null | undefined
  • never

Answer: string. NonNullable<T> strips both null and undefined from the type, leaving string.

Awaited<number> (a non-promise) is:

  • An error
  • Promise<number>
  • never
  • number

Answer: number. Awaited is a no-op on non-promise types by design, so Awaited<number> is just number.

The keyword that captures a type inside a conditional type is:

  • keyof
  • infer
  • typeof
  • extends

Answer: infer. 'infer' introduces a type variable, e.g. T extends (...args: any[]) => infer R ? R : never.

NonNullable only changes the type, so to actually remove null values at runtime you:

  • Need nothing more
  • Must use Awaited
  • Still need a real guard like filter
  • Must use Parameters

Answer: Still need a real guard like filter. Utility types are compile-time only; a runtime guard such as filter(x => x != null) removes the values.

Continue this course

Frequently asked questions

What do Parameters, ReturnType, ConstructorParameters, and InstanceType extract?

Parameters<F> gives a function type's arguments as a tuple; ReturnType<F> gives what it returns; ConstructorParameters<C> gives a class constructor's arguments as a tuple; and InstanceType<C> gives the type of object that 'new C()' produces. Used with 'typeof', they derive these types directly from existing functions and classes so your code stays in sync.

What does Awaited<T> do, and how is it different from just T?

Awaited<T> recursively unwraps Promise layers to the final resolved value type. Awaited<Promise<string>> is string, and even Awaited<Promise<Promise<number>>> is number. It models the 'await' keyword in the type system, so it's perfect with ReturnType to get the resolved result of an async function.

When do I reach for NonNullable<T>?

Use NonNullable<T> to strip null and undefined from a type — for example after you've filtered them out at runtime, or to describe a value you've already null-checked. NonNullable<string | null | undefined> is just string. It's the type-level counterpart of guarding against empty values.

What is 'infer' and why does it matter?

'infer' introduces a type variable inside a conditional type, letting you capture part of another type. It's how ReturnType, Parameters, and Awaited are implemented under the hood: 'T extends (...args: any[]) => infer R ? R : never' captures the return type R. Learning infer lets you build your own custom utility types when the built-ins don't fit.

Related lessons