Singleton, factory and builder — mostly replaced by the language
Creational patterns are about how objects are made. The three you will meet most — Singleton, Factory, and Builder — are also the clearest demonstration of this module's theme: in Java they are real, sometimes elaborate structures; in Kotlin they mostly collapse into a keyword or a default argument. Learn the problem each solves, then see how little code Kotlin needs.
Singleton — one instance for the whole program
The problem: you want exactly one instance of something — a single configuration, a single database connection pool, a single logger.
The Java pattern is genuinely fiddly: a private constructor, a static instance, and careful double-checked locking to make it thread-safe — a dozen lines that are easy to get subtly wrong.
In Kotlin it is one keyword (the classes module covered this):
object AppConfig {
val apiUrl = "https://api.example.com"
var timeoutSeconds = 30
}
println(AppConfig.apiUrl) // used directly — there is exactly one
object declares the class and its single instance, created lazily and thread-safely by the
runtime. The entire Singleton pattern, correct and safe, in one word. This is the starkest example in
the module: a page of careful Java becomes object.
The caveat travels with the pattern, not the language: a singleton is global state, and global
mutable state (a var in an object) is hard to test and trace. Use object freely for constants
and stateless services; think twice before putting mutable state in one. The best-practices instinct —
prefer passing dependencies explicitly — applies here regardless of how easy object makes it.
Factory — a better way to create than a constructor
The problem: a plain constructor is limited — it cannot have a descriptive name, cannot return a cached instance or a subtype, and cannot fail cleanly with a clear message. A factory is a function whose job is to create an object, with more freedom than a constructor.
In Kotlin, a factory is just a function — usually a companion-object function or a top-level function, no "factory class" required:
sealed class Notification {
data class Email(val to: String) : Notification()
data class Sms(val phone: String) : Notification()
companion object {
// a factory: chooses the subtype and names the intent
fun of(contact: String): Notification =
if ("@" in contact) Email(contact) else Sms(contact)
}
}
val a = Notification.of("kavita@example.com") // Email
val b = Notification.of("9876500001") // Sms
Notification.of(...) reads better than a constructor and does something a constructor cannot —
choose which subtype to build from the input. Factories also shine for named construction, where
the constructor's argument would be ambiguous:
class Duration private constructor(val millis: Long) {
companion object {
fun ofSeconds(s: Long) = Duration(s * 1000)
fun ofMinutes(m: Long) = Duration(m * 60 * 1000)
}
}
val d = Duration.ofMinutes(5) // clearer than Duration(300000)
Making the constructor private and offering named factories (ofSeconds, ofMinutes) means the
call site says what it means. The factory is one pattern that genuinely earns its place in Kotlin —
just written as a function, not a class hierarchy.
Builder — and why Kotlin rarely needs it
The problem: constructing an object with many optional parameters. In Java, a constructor with ten
parameters is unusable (you cannot tell the arguments apart, and you cannot skip the middle ones), so
the Builder pattern was invented: a helper object with a method per field and a final build()
call.
// the Java-style Builder — a lot of machinery
val pizza = Pizza.Builder()
.size("large")
.cheese(true)
.toppings(listOf("mushroom"))
.build()
Kotlin makes this almost entirely unnecessary, because default and named arguments do exactly what the Builder was invented to provide:
data class Pizza(
val size: String,
val cheese: Boolean = true,
val toppings: List<String> = emptyList()
)
val pizza = Pizza(size = "large", toppings = listOf("mushroom")) // named args, defaults for the rest
One data class, and construction reads like the Builder did — each field named, the unwanted ones defaulted — with none of the builder machinery. When you have default and named arguments, you almost never need a Builder. This is the pattern Kotlin dissolves most completely.
There is one honest exception: a Builder can still be worth it for a very complex object built up
across several steps or conditionally (add a topping only if in stock, in a loop). Even then, Kotlin
often reaches for a small DSL (a pizza { ... } block using a builder lambda) rather than the
classic pattern — a technique the frameworks you will use (like Jetpack Compose) lean on heavily. But
for ordinary construction, named arguments win.
The scoreboard
| Pattern | Java | Kotlin |
|---|---|---|
| Singleton | Private constructor + static + locking | object (one keyword) |
| Factory | Often a factory class | A function (companion or top-level) — still useful |
| Builder | A builder class with a method per field | Default + named arguments (rarely a Builder) |
The lesson to carry: two of these three all but vanish, and the one that survives (Factory) does so as a plain function. When you catch yourself about to write a builder class or a thread-safe singleton idiom in Kotlin, stop — the language almost certainly has a one-line answer.
Check your work
What Singleton solves, and Kotlin's answer. Exactly one instance; object — one keyword, lazy
and thread-safe.
The caveat that travels with Singleton. It is global state; avoid mutable state in it and prefer passing dependencies explicitly.
What a Factory gives you over a constructor. A descriptive name, the ability to choose a subtype or return a cached instance, and cleaner failure.
How a Factory is written in Kotlin. As a function — a companion-object or top-level function, not a factory class.
The private constructor + named factories pattern. Hide the constructor, offer ofSeconds/
ofMinutes so call sites read clearly.
What Builder solves, and why Kotlin rarely needs it. Constructing objects with many optional parameters; default and named arguments do the same directly.
The one time a Builder (or DSL) still helps. A very complex object built up conditionally across steps.
The overall scoreboard. Singleton → object; Factory → a function (still useful); Builder →
default/named arguments.
Practice
- Write a Singleton as an
objectand confirm you cannot create a second instance. - Write a
Notification.of(contact)factory that returns anEmailorSmssubtype based on the input. Test both branches. - Write a
Durationclass with a private constructor andofSeconds/ofMinutesfactories. Confirm the constructor cannot be called directly. - Write a
Pizzadata class with defaults, and construct one with named arguments, skipping the defaulted fields. Compare with an imagined Builder. - Take a Java-style Builder you can find online and rewrite it as a Kotlin data class with default arguments.
- For a config object with eight optional fields, decide whether you need any pattern at all, and justify your answer.
Official documentation
- Kotlin — Object declarations — Singleton and companion objects.
- Kotlin — Functions: default and named arguments — What replaces the Builder.
- Kotlin — Classes: constructors — Private constructors for the factory pattern.
- Refactoring Guru — Creational patterns — What each pattern is, to implement the Kotlin way.
Next: structural patterns — Adapter, Decorator, and delegation with extensions and by.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship