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.
TypeScript Builder Pattern FAQ & Answers
20 expert TypeScript Builder Pattern answers researched from official documentation. Every answer cites authoritative sources you can verify.
unknown
20 questionsUse '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.
Phantom 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.
Conditional types disable methods once called. Example: class Builder {setName
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.
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.
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.
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).
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.
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.
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.
Phantom 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.
Conditional types disable methods once called. Example: class Builder {setName
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.
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.
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.
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).
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.