When should you prefer a companion factory function over a secondary constructor in Kotlin? What can a factory do that a constructor cannot?
answer
- constructor: no name, always new, same type
- factory: named, can cache, can return subtype/null
- same-signature overloads need names → factory
- default args before secondary constructors
- factory often + private constructor
basics
~20 sUse a factory when you want a meaningful name, when you might return a cached or different-type object, or when creation can fail. A constructor must always return a brand-new instance of exactly that class and has no name.
solid answer
~40 sA secondary constructor (`constructor(...)`) must delegate to the primary one and always returns a fresh instance of exactly that class — it cannot be named, cannot return null, cannot return a cached instance, and cannot return a subtype. A companion factory function is just a function: it can have a descriptive name (`fromJson`, `ofSeconds`), be overloaded with the same parameter types but different names, return a cached/interned instance, return a `Result`/nullable on failure, return a subtype or interface, and run arbitrary logic before deciding what to return. Prefer the factory for naming, instance control, failable creation, and polymorphic return types. Prefer a secondary constructor for simple alternative parameter shapes where Kotlin default arguments aren't enough and you genuinely always want a new instance with no extra logic.
go deeper
Knows a factory has a name and a constructor doesn't, and can call a factory function.
Lists concrete factory powers (caching, nullable/Result return, subtype return, same-signature naming) vs constructor limits.
Weighs default args vs secondary constructor vs factory by trade-offs and ties factories to private constructors for a single entry point.
Reasons about binary/source compatibility and API surface: factories let return type and implementation evolve independently of the caller.
## The two construction mechanisms **Secondary constructor**: declared with the `constructor` keyword inside the class body; it must delegate to the primary constructor via `: this(...)`. ```kotlin class Point(val x: Int, val y: Int) { constructor(both: Int) : this(both, both) } ``` **Companion factory function**: an ordinary function on the `companion object` that returns an instance. ```kotlin class Point private constructor(val x: Int, val y: Int) { companion object { fun origin() = Point(0, 0) fun of(x: Int, y: Int) = Point(x, y) } } ``` ## What a factory can do that a constructor cannot - **Have a name** — `Point.origin()`, `Color.fromHex("#fff")`. Constructors are all called `<ClassName>` and disambiguate only by parameter types. - **Two factories with the same signature** — `fun fromId(s: String)` and `fun fromName(s: String)` both take a `String`; two constructors taking `String` would clash. - **Return a cached / interned instance** — e.g. return a pooled object instead of allocating. - **Return null or a `Result`** on failure instead of throwing (e.g. `fun parseOrNull(s: String): Point?`). - **Return a subtype or an interface** — the declared return type can be a supertype while the actual object is a concrete implementation. A constructor always produces exactly its own class. - **Run logic to decide what to build** — pick an implementation based on input. ## What only a constructor gives you - It is the canonical initialization path; `init` blocks and property initializers run. - Required when a framework or `data class copy()` needs a real constructor. - For trivial alternative parameter shapes, Kotlin **default and named arguments** often remove the need for both: `class Point(val x: Int = 0, val y: Int = 0)`. ## Rule of thumb Reach for **default arguments** first; a **secondary constructor** for a genuinely different new-instance shape; a **companion factory** when you need naming, instance control, failable creation, or polymorphic return types. Factories often pair with a `private constructor` so the factory is the only entry point.
- Why can't two constructors both take a single String for different meanings?Constructors are distinguished only by their parameter types; two `constructor(s: String)` declarations have identical signatures and won't compile. Named factories sidestep this.
- Does a factory bypass init blocks?No. The factory still calls a constructor internally, so init blocks and property initializers run as usual.
saying these in an interview costs you the question
- Claiming a secondary constructor can return a cached instance
- Saying a constructor can return a subtype
- Forgetting Kotlin default/named arguments often replace secondary constructors
- Not knowing constructors can't share an identical signature