How do you create a type-safe builder pattern in TypeScript?

Why Interviewers Ask This

Senior TypeScript engineers are expected to reason about architecture, performance, and edge cases. This question separates mid-level from senior candidates by testing deep system-level understanding.

Answer

A type-safe builder pattern uses TypeScript's type system to track what properties have been set and enforce that required properties are provided before the object is built. Using phantom types and conditional types: type HasName = { name: true }; type HasAge = { age: true }; class UserBuilder<T = {}> { private data: Partial<User> = {}; setName(name: string): UserBuilder<T & HasName> { this.data.name = name; return this as any; } setAge(age: number): UserBuilder<T & HasAge> { this.data.age = age; return this as any; } build(this: UserBuilder<HasName & HasAge>): User { return this.data as User; } }. With this, calling build() before setName() and setAge() is a compile-time error: new UserBuilder().setName("Alice").build(); — error, missing age. new UserBuilder().setName("Alice").setAge(30).build(); — valid. Each setter returns a more specific type that tracks progress, and build() requires the intersection of all required phantom types.

Common Mistake

A common mistake is memorizing definitions without understanding implications. When asked this question, go one level deeper — explain what happens when this concept is misused or ignored.