Creational PatternsBuilder PatternHard⏱️ ~3 min

Builder Pattern: Interview Deep Dive and Variations

Common Interview Questions:
Q1: How do you enforce required parameters in Builder?

Three approaches exist:

First, Required parameters in builder constructor. This is the standard and recommended approach. Builder(requiredA, requiredB) ensures clients cannot forget them at compile time.

Second, Separate build() method that throws an exception if required fields are missing. This delays validation to runtime. Use this only when required fields are context-dependent and cannot be determined at compile time. For example, a form builder where required fields vary by form type.

Third, Step Builder pattern (see variation below). This uses interfaces to enforce construction order, ensuring required steps are called before optional ones.

In interviews, use the first approach unless the problem explicitly requires dynamic validation.
Q2: Should Builder be nested or a separate class?

Nested (Recommended): Builder as a static nested class inside Product gives it access to the product's private constructor without widening visibility. This is the standard approach in Java and C++. The builder and product are tightly coupled, so co-locating them improves maintainability.

Separate (Special Cases): Use a separate builder class when you need to build multiple related products from the same builder, or when the builder itself has complex inheritance hierarchies. For example, a VehicleBuilder might produce Car, Truck, or Motorcycle depending on configuration. However, this is less common in interviews focused on single-product builders.

In interviews, default to nested unless asked about building polymorphic products.
Q3: How do you handle inheritance with Builder?

This is tricky because each subclass might add its own optional parameters. Consider a Vehicle hierarchy with Car and Truck subclasses.

Approach 1: Separate Builders per Subclass
CarBuilder extends VehicleBuilder and adds car-specific methods like setTrunkCapacity(). Each builder returns its own product type. The challenge is preserving fluent chaining: setEngine() from the base builder returns VehicleBuilder, not CarBuilder, breaking the chain. Solve this with generics and self-types (complex, often avoided in interviews).

Approach 2: Single Builder with Type Parameter
One builder with a type parameter creates the appropriate subclass in build(). This centralizes construction but mixes concerns if subclasses have very different attributes.

Interview Advice: Acknowledge the complexity and suggest starting with separate builders for each concrete class unless the interviewer pushes for a unified hierarchy. Most LLD problems avoid deep inheritance.
Variation 1: Step Builder (Enforcing Construction Order)

Use interfaces to enforce that certain methods are called before others. Example: building a SQL query requires SELECT before WHERE.

Define interfaces for each step:

SelectStep with select(columns): FromStep
FromStep with from(table): WhereStep
WhereStep with where(condition): BuildStep and build(): Query

The builder implements all interfaces but returns the next interface type at each step, preventing clients from calling methods out of order. This is powerful but verbose. Use it only when construction order is critical and errors should be caught at compile time.
Variation 2: Fluent Builder with Method Overloading

Provide multiple versions of setter methods for convenience. Example: setTimeout(int seconds) and setTimeout(Duration duration). This improves usability but increases the builder's surface area. In interviews, mention this as a UX consideration if time permits.
Variation 3: Builder with Defaults

Initialize optional fields with sensible defaults in the builder constructor or field declarations. Clients override only what differs. Example: HttpRequestBuilder defaults timeout to 30 seconds and retries to 3. Clients call setTimeout(60) only if they need a different value. This reduces boilerplate in common cases.
Machine Coding Considerations:

First, Implement build() validation carefully. Check for required fields, validate parameter ranges, and verify business rules. Throw meaningful exceptions (e.g., InvalidTicketStateException) rather than generic IllegalArgumentException.

Second, Consider defensive copying for mutable parameters. If a client passes a List to setAmenities(List amenities), copy it in the builder to prevent external modification: this.amenities = new ArrayList(amenities).

Third, Decide on null vs absent. Should an unset optional field be null or use Optional (in languages that support it)? In Java interviews, using Optional for return types is idiomatic: getDiscountCode(): Optional<String>.

Fourth, Thread safety is usually NOT a concern because builders are not shared across threads. Each thread creates its own builder instance. However, if asked, mention that the product should be immutable, eliminating post-construction concurrency issues.
Interview Tip: When asked to code a builder in a machine coding round, start by clarifying required vs optional parameters with the interviewer. Then write the builder constructor with required parameters, add fluent setters for optional ones, and implement build() with validation. Explain your decisions as you code: "I am putting spot assignment in a setter because valet parking assigns spots after ticket creation."
Common Pitfalls to Avoid:

First, Forgetting to return this in setter methods breaks method chaining. Each setter must return the builder instance.

Second, Allowing the product to be modified after build() is called. Ensure the product has no public setters, only getters.

Third, Over-validating in setters rather than build(). Individual setters can perform simple checks (e.g., non-null), but complex cross-field validation belongs in build() where all fields are available.

Fourth, Not resetting the builder after build(). If the builder is reused (uncommon but possible), decide whether build() should reset fields or throw an exception on subsequent calls. Most implementations treat builders as single-use.
💡 Key Takeaways
Enforce required parameters via builder constructor, not runtime checks
Nested builder is standard, separate builder for multi-product scenarios
Step Builder uses interfaces to enforce construction order at compile time
Validate complex business rules in build(), simple checks in setters
Defensive copying prevents external mutation of mutable parameters
📌 Examples
1Step Builder enforces SQL query construction order (SELECT, FROM, WHERE)
2Builder with defaults provides sensible timeout and retry values
3Inheritance with builders requires careful design to preserve fluent chaining
← Back to Builder Pattern Overview
Builder Pattern: Interview Deep Dive and Variations | Builder Pattern - System Overflow