Separate interface per step, each method returns next interface. Example: interface INameStep {setName(n: string): IAge Step}, interface IAgeStep {setAge(a: number): IBuildStep}, interface IBuildStep {build(): User}. class Builder implements INameStep {setName(n) {return this as any as IAgeStep;}}. Enforces strict linear workflow. No optional steps.
TypeScript Builder Pattern FAQ & Answers
10 expert TypeScript Builder Pattern answers researched from official documentation. Every answer cites authoritative sources you can verify.
unknown
10 questionsPhantom types: type parameters that don't appear in class fields, used only for type checking. Example: type BuilderState = {hasName: boolean, hasAge: boolean}. class Builder<S extends BuilderState = {hasName: false, hasAge: false}>. Methods update state type: setName(): Builder<S & {hasName: true}>. Enforces workflow at compile time with zero runtime cost.
Use discriminated unions for method availability. Required methods return builder with flag set, optional methods don't affect flags. Example: type OptionalMethods = S extends {hasRequired: true} ? {setOptional(v: string): Builder} : {}. class Builder extends OptionalMethods. Optional methods only available after required ones.
Compile-time: complex generic types slow IDE autocomplete (>5 type parameters = laggy). Runtime: zero overhead (types erased). Optimization: limit generic parameter count, use type aliases to reduce complexity, avoid deeply nested conditional types. Benchmark with tsc --diagnostics. Most builders: acceptable compile performance (<100ms per file).
Use 'this' as return type for chainability. Example: class QueryBuilder {where(condition: string): this {this.conditions.push(condition); return this;} limit(n: number): this {this._limit = n; return this;}}. Benefits: subclass methods return subclass type (not base), preserves type through chain. Works with inheritance.
Use phantom types (generic type parameters tracking state). Example: class Builder<T extends {name?: true, age?: true} = {}> {setName(n: string): Builder<T & {name: true}>, setAge(a: number): Builder<T & {age: true}>, build(): T extends {name: true, age: true} ? User : never}. build() only allowed when both name and age set. Compile error otherwise.
Fluent APIs with method chaining that enforce compile-time constraints. Pattern: each method returns 'this' with evolving type to track state. Example: builder.setName('').setAge(30).build(). Type system ensures: required methods called, no duplicate calls, correct method order. Implemented with generics, conditional types, 'this' return type.
Conditional types disable methods once called. Example: class Builder {setName
Use cases: (1) Query builders (TypeORM, Prisma, Knex), (2) HTTP request builders (Axios, Fetch wrappers), (3) Validation schemas (Zod, Yup), (4) Test builders (Jest matchers, Playwright), (5) Configuration builders (Webpack, Vite), (6) Form builders (React Hook Form). Benefits: prevent runtime errors, IDE autocomplete, refactoring safety.
Type testing with @ts-expect-error for negative cases. Example: const builder = new UserBuilder(); builder.build(); // @ts-expect-error - should fail, name not set. Use type testing libraries: tsd (assertType), expect-type (expectTypeOf). Runtime tests: verify method chaining works, final object correct. Both type and value tests required.