Bird
Raised Fist0
Typescriptprogramming~30 mins

Type-safe builder pattern in Typescript - Mini Project: Build & Apply

Choose your learning style10 modes available
Type-safe builder pattern
📖 Scenario: You are creating a configuration builder for a user profile setup in a web app. The builder should ensure that required fields are set before building the final profile object.
🎯 Goal: Build a type-safe builder pattern in TypeScript that enforces setting required properties name and age before allowing the build() method to be called.
📋 What You'll Learn
Create an interface UserProfile with name (string), age (number), and optional email (string).
Create a builder class UserProfileBuilder that allows setting name, age, and email.
Use TypeScript generics and conditional types to enforce that name and age must be set before build() is callable.
Demonstrate building a user profile with all required fields set and print the result.
💡 Why This Matters
🌍 Real World
Builder patterns help create complex objects step-by-step, ensuring required data is set before use, common in UI forms and configuration setups.
💼 Career
Understanding type-safe builders improves code safety and maintainability, valuable for frontend and backend TypeScript developers.
Progress0 / 4 steps
1
Define the UserProfile interface
Create an interface called UserProfile with these exact properties: name of type string, age of type number, and optional email of type string.
Typescript
Hint

Use the interface keyword and mark email as optional with ?.

2
Create the UserProfileBuilder class with generic flags
Create a class called UserProfileBuilder with two generic boolean parameters NameSet and AgeSet both defaulting to false. Add private properties _name (string or undefined), _age (number or undefined), and _email (string or undefined). Add methods setName(name: string) returning UserProfileBuilder<true, AgeSet> and setAge(age: number) returning UserProfileBuilder<NameSet, true>. Also add setEmail(email: string) returning this. Do not implement build() yet.
Typescript
Hint

Use generics with default false and return this casted to the new generic type for setName and setAge.

3
Add the type-safe build() method
Add a method build() that returns UserProfile. Use a conditional type to allow build() only if both NameSet and AgeSet are true. Otherwise, it should cause a TypeScript error. Inside build(), return an object with name, age, and email from the private properties.
Typescript
Hint

Use build(this: UserProfileBuilder<true, true>) to restrict calling build() only when both flags are true.

4
Build and print a user profile
Create a constant profile by calling new UserProfileBuilder(), then chaining setName("Alice"), setAge(30), and setEmail("alice@example.com"), and finally calling build(). Then print profile using console.log(profile).
Typescript
Hint

Chain the methods exactly as new UserProfileBuilder().setName("Alice").setAge(30).setEmail("alice@example.com").build() and print the result.

Practice

(1/5)
1.

What is the main benefit of using a type-safe builder pattern in TypeScript?

easy
A. It ensures all required properties are set before creating the object.
B. It makes the code run faster at runtime.
C. It automatically generates UI forms.
D. It removes the need for interfaces.

Solution

  1. Step 1: Understand the purpose of type-safe builder pattern

    The pattern helps build objects step-by-step while ensuring correctness.
  2. Step 2: Identify the key benefit

    It uses TypeScript's type system to check that all required properties are set before final object creation.
  3. Final Answer:

    It ensures all required properties are set before creating the object. -> Option A
  4. Quick Check:

    Type safety = required properties checked [OK]
Hint: Remember: type-safe means catching errors before running [OK]
Common Mistakes:
  • Thinking it improves runtime speed
  • Confusing it with UI generation
  • Assuming it removes interfaces
2.

Which of the following is the correct way to declare a generic builder class in TypeScript?

class Builder<T> {
private data: T;
constructor() {
this.data = {} as T;
}
build(): T {
return this.data;
}
}
easy
A. class Builder<T> { private data: T; constructor() { this.data = undefined; } build(): T { return this.data; } }
B. class Builder { private data: any; constructor() { this.data = {}; } build(): any { return this.data; } }
C. class Builder<T> { private data: T; constructor() { this.data = {} as T; } build(): T { return this.data; } }
D. class Builder<T> { private data: T; constructor() { this.data = null; } build(): T { return this.data; } }

Solution

  1. Step 1: Check generic class declaration

    class Builder<T> { private data: T; constructor() { this.data = {} as T; } build(): T { return this.data; } } correctly declares a generic class with <T> and initializes data as {} as T.
  2. Step 2: Verify build method return type

    The build() method returns data typed as T, matching the generic type.
  3. Final Answer:

    class Builder<T> { private data: T; constructor() { this.data = {} as T; } build(): T { return this.data; } } -> Option C
  4. Quick Check:

    Generic class with typed data = class Builder<T> { private data: T; constructor() { this.data = {} as T; } build(): T { return this.data; } } [OK]
Hint: Look for generic <T> and proper initialization [OK]
Common Mistakes:
  • Using any instead of generic type
  • Initializing data with null or undefined
  • Missing generic parameter in class
3.

Consider this TypeScript code using a type-safe builder pattern:

interface User {
name: string;
age: number;
}

class UserBuilder {
private user = {} as Partial<User>;
setName(name: string) { this.user.name = name; return this; }
setAge(age: number) { this.user.age = age; return this; }
build(): User {
if (!this.user.name || !this.user.age) throw new Error('Missing fields');
return this.user as User;
}
}

const user = new UserBuilder().setName('Alice').setAge(30).build();
console.log(user);

What will be the output?

medium
A. { name: 'Alice', age: 30 }
B. Error: Missing fields
C. undefined
D. { name: 'Alice' }

Solution

  1. Step 1: Trace method calls on UserBuilder

    The builder sets name to 'Alice' and age to 30, so both required fields are set.
  2. Step 2: Check build method behavior

    Since both fields exist, no error is thrown, and the object is returned as User.
  3. Final Answer:

    { name: 'Alice', age: 30 } -> Option A
  4. Quick Check:

    All fields set = object returned [OK]
Hint: Check if all required fields are set before build [OK]
Common Mistakes:
  • Assuming error thrown even when fields set
  • Expecting undefined output
  • Missing age property in output
4.

Identify the error in this type-safe builder pattern code and select the fix:

interface Car {
make: string;
year: number;
}

class CarBuilder {
private car = {} as Partial<Car>;
setMake(make: string) { this.car.make = make; return this; }
setYear(year: number) { this.car.year = year; return this; }
build(): Car {
if (!this.car.make || !this.car.year) throw new Error('Missing fields');
return this.car;
}
}
medium
A. Initialize car as Car instead of Partial<Car>
B. Change return type of build() to Partial<Car>
C. Remove the if check for missing fields
D. Cast return value as Car: return this.car as Car;

Solution

  1. Step 1: Identify the type mismatch in build()

    The method returns this.car which is typed as Partial<Car>, but the return type is Car.
  2. Step 2: Fix by casting to Car

    To satisfy the return type, cast this.car as Car after checking required fields.
  3. Final Answer:

    Cast return value as Car: return this.car as Car; -> Option D
  4. Quick Check:

    Return type must match method signature [OK]
Hint: Cast partial object to full type after validation [OK]
Common Mistakes:
  • Changing return type to Partial<Car>
  • Removing validation checks
  • Initializing with full Car without setting fields
5.

You want to create a type-safe builder for a Product with required id and name, and optional description. Which approach best enforces setting required fields before build?

interface Product {
id: number;
name: string;
description?: string;
}

// Which builder pattern enforces required fields at compile time?
hard
A. Use a single class with optional properties and runtime checks in build()
B. Use generics with flags tracking set fields and conditional types for build()
C. Use a function that returns a partial Product object without checks
D. Use inheritance to extend a base builder class without generics

Solution

  1. Step 1: Understand compile-time enforcement

    Only generics with flags and conditional types can enforce required fields before build at compile time.
  2. Step 2: Compare options

    Use generics with flags tracking set fields and conditional types for build() uses TypeScript generics and conditional types to track which fields are set, preventing build() call until required fields are set.
  3. Final Answer:

    Use generics with flags tracking set fields and conditional types for build() -> Option B
  4. Quick Check:

    Compile-time safety = generics + conditional types [OK]
Hint: Use generics and conditional types to enforce required fields [OK]
Common Mistakes:
  • Relying only on runtime checks
  • Using inheritance without generics
  • Returning partial objects without enforcement
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard