🚀 Enrollments Open for 1:1 Mentorship Scheduled as per your availability 💚Book Now
KotlinIntermediate4 min
Builder Pattern in Kotlin?

Answer

Builder Pattern in Kotlin = “construct a complex object step-by-step, without a 12-parameter constructor turning into a crime scene.”

In interviews, they’re checking 3 things:

  1. you know why builders exist
  2. you can implement one in Kotlin idiomatically
  3. you know when not to use it (because Kotlin has better tools)

What is Builder Pattern?

A Builder separates how you build an object from the object itself.

Use it when:

  • Object has many optional parameters
  • There are validation rules
  • You want readable, named configuration
  • Construction is multi-step (e.g., build request → add headers → add body → validate → create)

Classic Java-style builders were created to avoid:

```java new User("Akshay", null, null, null, 0, false, null, ...) ```


Kotlin’s “default args” vs Builder (important interview point)

Kotlin often removes the need for builders because we can do:

```kotlin data class User( val name: String, val age: Int = 0, val city: String? = null, val isPro: Boolean = false ) ```

So: Builder is NOT always needed in Kotlin.

But builder still wins when:

  • You must enforce rules (e.g. if `isPro == true` then `subscriptionId` must exist)
  • You want a fluent DSL for configuration
  • You’re creating immutable objects with many “optional but not really optional” fields
  • You are interacting with Java libs / APIs that expect builder style

1) Classic Builder in Kotlin (Interview-Friendly)

Scenario: building a `NetworkRequest`

You want:

  • required: url
  • optional: method, headers, body, timeout
  • validation: GET can’t have body, timeout must be positive

```kotlin class NetworkRequest private constructor( val url: String, val method: String, val headers: Map<String, String>, val body: String?, val timeoutMs: Long ) { class Builder(private val url: String) { private var method: String = "GET" private val headers: MutableMap<String, String> = mutableMapOf() private var body: String? = null private var timeoutMs: Long = 10_000

    fun method(value: String) = apply {
        method = value.uppercase()
    }

    fun addHeader(key: String, value: String) = apply {
        headers[key] = value
    }

    fun body(value: String?) = apply {
        body = value
    }

    fun timeoutMs(value: Long) = apply {
        timeoutMs = value
    }

    fun build(): NetworkRequest {
        require(url.startsWith("http")) { "Invalid url: $url" }
        require(timeoutMs > 0) { "Timeout must be > 0" }
        if (method == "GET") require(body == null) { "GET request can't have a body" }

        return NetworkRequest(
            url = url,
            method = method,
            headers = headers.toMap(), // immutable copy
            body = body,
            timeoutMs = timeoutMs
        )
    }
}

} ```

Usage:

```kotlin val request = NetworkRequest.Builder("https://api.masterly.app/sessions") .method("POST") .addHeader("Authorization", "Bearer token") .addHeader("Content-Type", "application/json") .body("""{"skillId":"compose","minutes":30}""") .timeoutMs(15_000) .build() ```

Interview points to say out loud:

  • `private constructor` forces creation only via Builder (immutability control)
  • `apply` returns the builder itself → fluent chain
  • `build()` is where validation lives
  • immutable copy of `headers` prevents accidental mutation after build

2) Kotlin DSL Builder (Idiomatic + Sexy)

Kotlin lets you write builders that feel like config blocks.

```kotlin class NetworkRequest private constructor( val url: String, val method: String, val headers: Map<String, String>, val body: String?, val timeoutMs: Long ) { class Builder { lateinit var url: String var method: String = "GET" var body: String? = null var timeoutMs: Long = 10_000 private val headers: MutableMap<String, String> = mutableMapOf()

    fun header(key: String, value: String) {
        headers[key] = value
    }

    fun build(): NetworkRequest {
        check(::url.isInitialized) { "url is required" }
        require(timeoutMs > 0) { "Timeout must be > 0" }
        val m = method.uppercase()
        if (m == "GET") require(body == null) { "GET request can't have a body" }

        return NetworkRequest(
            url = url,
            method = m,
            headers = headers.toMap(),
            body = body,
            timeoutMs = timeoutMs
        )
    }
}

}

fun networkRequest(block: NetworkRequest.Builder.() -> Unit): NetworkRequest { return NetworkRequest.Builder().apply(block).build() } ```

Usage:

```kotlin val request = networkRequest { url = "https://api.masterly.app/sessions" method = "POST" header("Authorization", "Bearer token") header("Content-Type", "application/json") body = """{"skillId":"compose","minutes":30}""" timeoutMs = 15_000 } ```

Interview one-liner: “This is Builder Pattern using Kotlin DSL: the builder is configured via a lambda with receiver.”


3) Builder vs `copy()` (data class style)

For immutable data classes, `copy()` is often the “micro-builder”:

```kotlin data class User( val name: String, val city: String? = null, val age: Int = 0 )

val u1 = User(name = "Akshay") val u2 = u1.copy(city = "Bangalore", age = 26) ```

So you say: “If it’s just optional params and immutability, Kotlin default args + data class `copy()` often beats builders.”


Real Android examples you can mention in interview

  • OkHttp: `Request.Builder()`, `OkHttpClient.Builder()`
  • NotificationCompat.Builder
  • AlertDialog.Builder
  • WorkManager: `OneTimeWorkRequestBuilder`, constraints builder-ish patterns

These are builders because configuration has many options + readable setup.


Common interview questions + crisp answers

Q: Why Builder Pattern? A: To construct complex objects step-by-step, avoid telescoping constructors, enforce validation, and keep objects immutable.

Q: Why not always use Builder in Kotlin? A: Kotlin has named params + default args + data class `copy()`, so builder is only needed when validation / multi-step construction / DSL readability is important.

Q: How do you make sure the built object is immutable? A: Private constructor + builder holds mutable state + `build()` returns object with `val` fields + defensive copies for collections (`toMap()`).

Q: Where do you put validation? A: In `build()`. Builders are like “form filling”; `build()` is “submit & validate”.

Want to master these concepts?

Join our live cohorts and build production-ready Android apps.

1:1 Mentorship

Get personalized guidance from a Google Developer Expert. Accelerate your career with dedicated support.

Personalized Learning Path
Mock Interviews & Feedback
Resume & Career Guidance

Limited slots available each month