HuntExams Academy logo
JavaIntermediate#design-patterns

What is the Builder pattern and when should you use it?

Answer

A creational pattern that constructs complex objects step by step using a fluent API, avoiding telescoping constructors with many optional parameters and improving readability.

Example

Pizza pizza = new Pizza.Builder()
  .size("Large")
  .cheese(true)
  .pepperoni(true)
  .build();

Detailed explanation

The Builder pattern is a creational design pattern that separates an object's construction process from its final representation. It is especially useful when a Java class has several optional parameters or needs multiple configuration steps before it becomes valid. Instead of remembering the order of arguments in a long constructor, callers use named methods to describe their choices. In the Pizza example, size("Large"), cheese(true), and pepperoni(true) make the configuration clear, while build() marks the point at which the finished object is created.

In a common Java implementation, Pizza contains a static nested Builder class with fields corresponding to the desired configuration. Each configuration method updates the builder's state and returns this, enabling fluent chaining. The build() method validates the configuration and invokes a private Pizza constructor that copies values from the builder. Required values can be accepted by the builder's constructor or checked during build(); optional values can have documented defaults. Validation should reject invalid combinations, such as incompatible options, rather than allowing a partially valid object to escape.

Builder is often used to construct immutable objects: Pizza can have private final fields and expose no setters, while its builder remains mutable. However, the pattern does not automatically guarantee immutability. Mutable collections and other mutable inputs need defensive copies, and accessors must not expose internal mutable state. Builders are also generally not thread-safe unless explicitly designed for concurrent use. The fluent API shown here is a popular Java variation; fluent chaining is not essential to the broader Builder pattern, which can also use a director to coordinate construction steps.

Use Builder when telescoping constructors become confusing, configuration has many optional values, or construction requires coordinated validation. Compared with setters, it provides a clear completion boundary and lets the finished object enforce its invariants. For a class with two straightforward required fields, a constructor or static factory is usually simpler. Interviewers look for this trade-off: better readability and controlled construction versus extra code and a separate builder object, not a claim that every class needs a builder.

Key points to remember

  • Builder constructs an object step by step and provides an explicit completion method, commonly build().
  • Named configuration methods avoid confusing positional arguments and telescoping constructors.
  • A typical Java builder is a static nested class whose fluent methods return this.
  • Required values, defaults, and cross-field validation should be handled explicitly.
  • Use it for complex configuration; prefer constructors or factories for simple objects.

Common mistakes candidates make

  • Claiming that fluent method chaining alone makes an API a Builder pattern implementation.
  • Assuming a builder automatically makes the resulting object immutable or thread-safe.
  • Adding a builder to every class without considering the extra code and construction complexity.

Likely follow-up questions

How is Builder different from a static factory method?

A static factory typically returns an object through one named call, while a builder accumulates configuration across multiple calls before construction. They can work together: a static factory such as Pizza.builder() can return a builder.

How can a builder enforce required fields?

Accept required values in the builder constructor, or reject missing values in build(). A staged builder can enforce a sequence through interfaces at compile time, but it adds complexity.

Can the same builder create multiple objects?

Yes, if the implementation supports reuse; each build() call should create an independent result. Defensive copying is important so later changes to the builder or its mutable inputs do not alter previously built objects.

Interview tip: Explain the constructor problem first, walk through the Pizza chain and build() validation, then mention when a simple constructor is preferable.

Related Interview Questions

1
JavaIntermediate#design-patterns

What is the Observer pattern?

Open
2
JavaIntermediate#design-patterns

What is the Strategy pattern?

Open
3
JavaAdvanced#design-patterns

What is the difference between the Adapter and Decorator patterns?

Open