Builder — fundamentals of the builder pattern in mobile development

Author: IT Sectr Published: 2026-02-17 Reading time: 7 min

Builder — a creational pattern that allows creating complex objects step by step. Unlike a constructor with a dozen parameters, Builder assembles an object through a chain of calls, each configuring one field. The pattern is especially useful for objects with many optional parameters: network client configuration, database settings, alert and navigation builders. Learn more at Refactoring Guru: Builder.

Key Takeaways

  • Builder — step-by-step object construction separating process and result
  • Fluent interface — chain of set()/with() calls for convenient configuration
  • Immutability — Builder creates a ready-made object that does not require setters
  • Backward compatibility — new fields can be added to Builder without breaking clients
  • Kotlin DSL vs Builder — Kotlin offers type-safe builders as an alternative

What is Builder: the essence of the builder pattern?

Builder — a creational GoF pattern that separates the construction of a complex object from its representation. The same construction process can create different representations. Builder is useful when an object has many optional parameters and a constructor with ten fields is unreadable and inflexible. The pattern also solves the Telescoping Constructor anti-pattern, where the number of constructor overloads grows exponentially.

Builder structure includes an inner static Builder class with fields mirroring the main class fields. Each set-method returns Builder (this) for fluent chaining. The final build() method creates the target object by passing field values to a private constructor. The main class has a private constructor that accepts a Builder. Client: Object.builder().setField1(val1).setField2(val2).build().

When to use Builder — objects with 5+ fields where only 2-3 are required. Configuration objects (RequestConfig, DatabaseConfig). Objects with complex validation logic during creation. Objects that must be immutable after creation. In Android, Builder is actively used in the SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.

Builder in Kotlin: classic and DSL implementation

Kotlin Builder has two approaches: classic Java-style Builder (via a nested class) and Kotlin-style DSL builder (via a lambda with receiver). Java-style Builder is preferable for Android compatibility and when used with Java code. DSL builder is the idiomatic Kotlin way: a function accepts a lambda inside which this is the Builder context where fields can be assigned directly.

kotlin
// Classic Builder
data class HttpConfig private constructor(
    val baseUrl: String,
    val timeout: Long = 30_000,
    val retries: Int = 3,
    val headers: Map<String, String> = emptyMap()
) {
    class Builder {
        private var baseUrl: String = ""
        private var timeout: Long = 30_000
        private var retries: Int = 3
        private var headers: MutableMap<String, String> = mutableMapOf()

        fun baseUrl(url: String) = apply { this.baseUrl = url }
        fun timeout(ms: Long) = apply { this.timeout = ms }
        fun retries(n: Int) = apply { this.retries = n }
        fun header(key: String, value: String) = apply { headers[key] = value }

        fun build(): HttpConfig {
            require(baseUrl.isNotBlank()) { "baseUrl is required" }
            return HttpConfig(baseUrl, timeout, retries, headers)
        }
    }
}

// Usage
val config = HttpConfig.Builder()
    .baseUrl("https://api.example.com")
    .timeout(15_000)
    .header("Authorization", "Bearer token")
    .build()

Kotlin DSL builder — an alternative without a nested class. A builder function accepts a lambda in the context of a builder object. This is idiomatic for Kotlin and does not require write-fields. DSL builders are actively used in Ktor Client, kotlinx.serialization, Compose (Modifier). DSL builder is incompatible with Java and is not suitable for libraries with a Java API.

Builder in Swift: result builders and chains

Swift Builder — Swift does not have a built-in Builder pattern, but a fluent interface is easily implemented through methods returning Self. Each method configures a property and returns self. Unlike Kotlin, Swift does not require a separate Builder class — you can return the object itself if it is mutable during assembly. For immutable objects, a nested Builder class is used similarly to Kotlin.

swift
struct NetworkRequest {
    let url: String
    let method: HTTPMethod
    let headers: [String: String]
    let body: Data?
    let timeout: TimeInterval

    final class Builder {
        private var url: String = ""
        private var method: HTTPMethod = .get
        private var headers: [String: String] = [:]
        private var body: Data? = nil
        private var timeout: TimeInterval = 30

        func withURL(_: String) -> Self { /* self */ }
        func withMethod(_: HTTPMethod) -> Self { /* self */ }
        func withHeader(key: String, value: String) -> Self { /* self */ }
        func withBody(_: Data) -> Self { /* self */ }
        func withTimeout(_: TimeInterval) -> Self { /* self */ }

        func build() throws -> NetworkRequest {
            guard !url.isEmpty else { throw BuilderError.missingURL }
            return NetworkRequest(
                url: url, method: method, headers: headers,
                body: body, timeout: timeout
            )
        }
    }
}

Result Builders — Swift 5.4 introduced @resultBuilder — a language mechanism for declarative structure building. SwiftUI, AttributedString, SceneBuilder use result builders. This is an alternative to the classic Builder: instead of a chain of set-methods, result builder uses a code block with elements that the compiler assembles into an array or tree. @ViewBuilder in SwiftUI is the most famous example: inside body you can write if, switch, ForEach, and the compiler builds a View from conditions.

Builder vs Telescoping Constructor: approach comparison

Telescoping Constructor — an anti-pattern where a class has many overloaded constructors with different sets of parameters. For example, three constructors: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). As parameters grow, the number of constructors grows exponentially — for n optional fields you need n! combinations. Builder solves this problem by allowing you to specify only the needed fields.

CharacteristicTelescoping ConstructorBuilderKotlin named args
Code volumeExponential growthLinear growthMinimal
ReadabilityLow (which parameter is which?)High (method + name)High (name = value)
ImmutabilityImmutableImmutableImmutable
Java compatibilityFullFullNone (Kotlin only)
ValidationIn each constructorIn build() — onceIn init()

Kotlin named arguments + default values — an elegant alternative to Builder in pure Kotlin projects. Constructor parameters have default values, the client passes only what is needed: HttpConfig(baseUrl = url, timeout = 15_000). The drawback is the inability to validate required fields at compile time. Builder provides required fields through the Builder constructor (baseUrl is required). For Java libraries, Builder remains the de facto standard.

Builder in Android SDK: AlertDialog, Retrofit, OkHttp

Builder in Android SDK — one of the most common patterns in the standard library. AlertDialog.Builder: new AlertDialog.Builder(context).setTitle().setMessage().setPositiveButton().create(). Retrofit.Builder: new Retrofit.Builder().baseUrl().addConverterFactory().build(). OkHttpClient.Builder: new OkHttpClient.Builder().connectTimeout().addInterceptor().build(). NotificationCompat.Builder: setContentTitle().setContentText().setSmallIcon().build().

Why Google uses Builder — backward compatibility. Adding a new method to Builder does not break existing code. If Google used a constructor with 20 parameters, each new field would require a new overload. Builder allows adding set-methods over years without breaking changes. For example, NotificationCompat.Builder added setBubbleMetadata() in Android 11 without affecting existing code.

Builder in Kotlin libraries — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()), Navigation (NavOptionsBuilder). In Kotlin projects, Builder is often combined with DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). The pattern remains relevant for public APIs where backward compatibility and Java interop are important.

Frequently Asked Questions

When is Builder overkill?

Builder is overkill for objects with 1-3 fields — a regular constructor or data class is clearer. It is also overkill in Kotlin projects without Java interop, where named arguments + default values solve the same task more simply. Builder is justified for 5+ fields, complex validation, or Java APIs where named arguments are not available.

How does Builder differ from Factory?

Builder creates one complex object step by step (field configuration), Factory creates an object entirely by type or parameters. Builder answers the question «how to assemble?», Factory answers «what to create?». Builder is often combined with Factory: Factory selects the type, Builder configures the fields.

Is Builder needed in SwiftUI?

In SwiftUI, the role of Builder is performed by result builders (@ViewBuilder, @SceneBuilder) and View modifiers (.font(), .padding()). Classic Builder is not needed because SwiftUI uses a declarative approach and fluent modifiers. For UIKit components, Builder is useful: UIAlertController, URLRequest, NSAttributedString.

How to make Builder thread-safe?

Builder typically does not require thread safety since it is used in a single thread to assemble an object. If Builder is used in a multithreaded environment (a rare case), synchronize each set-method and build(). An alternative — Immutable Builder: each set-method returns a new Builder instance with the modified field.

Why does Retrofit use Builder instead of DI?

Retrofit.Builder is a public library API that must work without a DI container. Builder provides configuration flexibility (baseUrl, converters, interceptors, custom call adapters) without dependencies on Dagger or other DI frameworks. Inside an application, DI can create Retrofit once through Builder, but Builder itself remains part of the Retrofit public API.

Summary

  • Builder — step-by-step object construction with fluent interface
  • Kotlin Builder — classic (nested class) and DSL (lambda with receiver)
  • Swift Builder — nested class or @resultBuilder for declarative code
  • Immutability — Builder creates immutable objects through a private constructor
  • Android SDK — AlertDialog, Retrofit, OkHttp, NotificationCompat — industry standard
  • Backward compatibility — adding fields to Builder does not break existing code
  • Kotlin alternative — named arguments + default values simpler for pure Kotlin projects

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also