skip to content

Companion as Factory

Pairing a private constructor with companion functions gives you named factories, and the companion can even implement a Factory interface. It is the standard answer to 'how do you do multiple constructors with meaningful names'.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

6

How do you create a class whose only way to be constructed is through a named factory function on its companion object, hiding the real constructor?

level: juniorimportance: must knowfreq 70%

answer

  1. private constructor + companion object
  2. companion shares the class's private scope
  3. User.create instead of User(...)
  4. validate/normalize before constructing
  5. named, descriptive entry point

basics

~10 s

Mark the constructor private and add a function in the companion object that creates and returns the instance. Callers use the named function instead of calling the constructor directly.

solid answer

~40 s

Declare the primary constructor private with `class User private constructor(...)`. Add a `companion object` with a named function (e.g. `fun create(...)` or `of(...)`) that can call that private constructor because the companion is a member of the class and shares its private scope. Callers then write `User.create(...)` instead of `User(...)`. This gives you a named, descriptive entry point, lets you run validation or normalization before construction, allows returning cached or subtype instances, and prevents anyone from bypassing the factory. Companion members are accessed via the class name, so `User.create` reads naturally. You can have several differently named factories for different construction scenarios.

code

kotlin · 10 lines
kotlin
class User private constructor(val name: String, val age: Int) {
    companion object {
        fun create(name: String, age: Int): User {
            require(age >= 0) { "age must be >= 0" }
            return User(name.trim(), age)
        }
    }
}

val u = User.create(" Ada ", 36) // User(...) is not callable from here

go deeper

for a junior

Knows the private constructor syntax and that a companion function returns the instance via User.create.

for a middle

Explains why the companion can reach the private constructor (shared private scope) and names benefits like validation and naming.

for a senior

Contrasts factory vs secondary constructor, discusses instance control (caching, subtypes), and when each is appropriate.

for a principal

Frames it as an API-design boundary: stable named entry points decouple callers from construction details and enable future evolution without breaking source/binary compatibility.

## What this is A **factory function** is a function whose job is to build and return an object, instead of constructing it directly with the constructor. In Kotlin the idiomatic place for such a function is the class's **companion object** — a single object tied to the class itself (like Java `static` members) accessed through the class name. ## Private constructor The primary constructor is made private with the `private constructor` modifier: ```kotlin class User private constructor(val name: String, val age: Int) { companion object { fun create(name: String, age: Int): User { require(age >= 0) { "age must be non-negative" } return User(name.trim(), age) } } } ``` Because the **companion object is a member of the class**, it sits inside the class's private scope and is therefore allowed to call the private constructor. Code outside the class cannot write `User("a", 1)` — it must call `User.create(...)`. ## Why do this - **A descriptive name**: `User.create`, `Color.fromHex`, `Duration.ofSeconds` read better than an overloaded constructor. - **Validation / normalization** before the object exists (here `require` and `trim`). - **Control over instances**: you can cache, return a cached singleton, or return a subtype. - **Single entry point**: nobody can bypass the factory. ## How callers use it ```kotlin val u = User.create(" Ada ", 36) // name becomes "Ada" ``` There is no `User(...)` available to outside code. `User.create` works because companion members are resolved through the class name without an instance. ## Note The constructor is still *called* inside the factory — the factory does not replace construction, it wraps it behind a named, validated gateway.

  • Can the companion call the private constructor even though it's private?
    Yes. The companion object is a member of the class, so it has access to the class's private members, including a private constructor.
  • Why use a factory function instead of a secondary constructor?
    A factory can have a meaningful name, can return cached or subtype instances, and can fail by returning null or a Result; a constructor must always return a new instance of exactly that type and cannot be named.

A private constructor with a companion factory is like a building with no public door: you must go through the reception desk (the factory), which checks your ID before letting you in.

saying these in an interview costs you the question

  • Thinking a private constructor blocks the companion too
  • Calling it 'static' without understanding companion is a real object instance
  • Believing the factory replaces the constructor rather than wrapping it
  • Saying you must use reflection to call a private constructor from the companion

context

open as a page

How do you implement a Factory interface on a companion object, and why is that useful?

level: middleimportance: should knowfreq 45%

basics

~20 s

A companion object is a real object, so it can implement an interface. You write companion object : Factory<T> and override the interface's create function. Now the class's companion can be passed around as a Factory value.

open as a page

When should you prefer a companion factory function over a secondary constructor in Kotlin? What can a factory do that a constructor cannot?

level: middleimportance: should knowfreq 60%

basics

~20 s

Use 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.

open as a page

Design a companion factory that validates input and can fail gracefully and/or return different subtypes. Compare returning null, Result, throwing, and sealed-result types.

level: seniorimportance: should knowfreq 30%

basics

~20 s

A factory can check the input before building. If the input is bad it can return null, return a Result with the error, or throw. It can also decide which subtype to create based on the input and return them all as the common supertype.

open as a page

Show how a companion factory with a private constructor can intern/cache instances so equal inputs return the same object. What are the concurrency and lifecycle concerns?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Keep a cache (a map) in the companion. The factory looks up the key; if found it returns the existing object, otherwise it builds one, stores it, and returns it. With a private constructor, the factory is the only way in, so the cache can never be bypassed.

open as a page

When you expose a companion factory function to Java callers, what does the generated bytecode look like, and how do @JvmStatic and @JvmName change the call site?

level: principalimportance: nice to knowfreq 20%

basics

~10 s

By default Java must call the factory through a Companion field, like User.Companion.create(...). Adding @JvmStatic generates a real static method so Java can call User.create(...). @JvmName lets you rename the generated method.

open as a page