FrontendPrep
Menu
Topics
Questions
Guides
Challenges
Soon
Back to TypeScript Questions
typescriptMedium

TypeScript: Recreating Built-in Utility Types

Learn how to recreate built-in TypeScript utility types like Pick, Omit, Exclude, ReturnType, and Parameters using generic constraints and conditional type inference.

TypeScript: Recreating Built-in Utility Types

A common medium-to-hard TypeScript interview question is:

How do built-in utility types like Pick, Omit, Exclude, ReturnType, and Parameters work under the hood? Write custom generic types to recreate their behaviors from scratch.

Recreating these types demonstrates a clear understanding of generic constraints, mapped types, conditional types, and the infer keyword.


1. Recreating Exclude

Exclude takes a union type T and removes any members assignable to U.

Mechanics

It leverages distributive conditional types. When a generic parameter is a union, conditional types distribute over the union automatically:

type MyExclude<T, U> = T extends U ? never : T;
 
// Verification
type Status = "success" | "loading" | "error";
type FinalStatus = MyExclude<Status, "loading">; 
// Result: "success" | "error"

How it works:

  • "success" extends "loading" ? never : "success" -> "success"
  • "loading" extends "loading" ? never : "loading" -> never
  • "error" extends "loading" ? never : "error" -> "error"
  • The resulting union is "success" | never | "error", which resolves to "success" | "error".

2. Recreating Pick

Pick constructs a type by picking a subset of properties K from an object type T.

Mechanics

We use generic constraints (extends keyof T) to ensure that K only contains valid keys of T, and then map over K:

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};
 
// Verification
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = MyPick<Todo, "title" | "completed">;
// Result: { title: string; completed: boolean; }

3. Recreating Omit

Omit constructs a type by picking all properties from T and then removing K.

Mechanics

We can implement this by combining our Pick and Exclude types:

type MyOmit<T, K extends keyof T> = MyPick<T, MyExclude<keyof T, K>>;
 
// Verification
type TodoWithoutDesc = MyOmit<Todo, "description">;
// Result: { title: string; completed: boolean; }

4. Recreating ReturnType

ReturnType extracts the return type of a function signature.

Mechanics

We use conditional types combined with the infer keyword. The infer keyword lets us declare a type variable inside the condition to capture the return type:

type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
 
// Verification
const getUser = () => ({ id: 1, name: "Bob" });
type UserResult = MyReturnType<typeof getUser>;
// Result: { id: number, name: string }

5. Recreating Parameters

Parameters extracts the argument types of a function as a tuple.

Mechanics

Similar to ReturnType, we use infer but place it on the arguments list instead of the return type:

type MyParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
 
// Verification
function saveProfile(name: string, age: number) {}
type ProfileArgs = MyParameters<typeof saveProfile>;
// Result: [name: string, age: number]

Senior-Level Interview Answer

TypeScript utility types leverage generic rules to automate structure compilation. Pick uses key constraints to map a filtered subset of properties from a source object. Exclude leverages distributive conditional types, distributing a union over a condition and substituting matching entries with never. Omit combines these operations by picking the exclusion of keys from the key union of the object. Utility types like ReturnType and Parameters use the conditional infer keyword to dynamically capture types at declaration points (such as function parameters or return values) and extract them at compile time.


Common Interview Mistakes

❌ Omitting generic constraints

For example, writing type MyPick<T, K> = { [P in K]: T[P] } without specifying K extends keyof T. This will cause a compilation error because TypeScript cannot guarantee that K represents valid keys of T, meaning T[P] might be invalid.

❌ Forgetting how never behaves in unions

Thinking that never remains part of the union. In TypeScript, never represents the empty set, so "success" | never automatically simplifies to "success".


Key Takeaways

  • Generic Constraints: Constraints like extends keyof T ensure that type operations only reference valid keys of an object.
  • Distributive Unions: Union parameters in conditional types distribute automatically, applying the logic to each member.
  • Dynamic Capture: The infer keyword lets you capture and name anonymous types (like return values or function parameters).
  • Omission Strategy: Omit is constructed by picking the keys that remain after excluding the target keys from keyof T.
  • Union Simplification: Evaluating a union member to never removes it entirely from the resulting union type.

Share this Resource

Help other developers level up by sharing this study guide.

More Technical Questions

Expand your mastery. Deep dive into other frontend interview challenges in this category.