skip to content

Objects & Companions

Kotlin makes singletons and per-class members first-class through object declarations and companion objects, plus anonymous object expressions for one-off implementations. Since Kotlin has no static keyword, this is where those questions land.

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

explore

questions

21

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

What is a companion object in Kotlin, and how do you call its members?

level: juniorimportance: must knowfreq 80%

basics

~10 s

It is a special object declared inside a class with the 'companion object' keywords. Its properties and functions can be called using the class name directly, without creating an instance.

open as a page

What does an `object` declaration create in Kotlin, and how is it different from a regular `class`?

level: juniorimportance: must knowfreq 80%

basics

~10 s

An object declaration creates a singleton: exactly one instance that Kotlin makes for you. You never call a constructor. You use it by its name, like Counter.next().

open as a page

What is a Kotlin object expression (anonymous object), and when would you use one instead of a named class?

level: juniorimportance: must knowfreq 70%

basics

~20 s

An object expression creates a one-off, unnamed instance on the spot, often to implement an interface like a click listener. You use it when you need a single instance and don't want to write a whole named class.

open as a page

When is an `object` declaration initialized, and what guarantees does Kotlin give about thread-safety of that initialization?

level: middleimportance: must knowfreq 65%

basics

~10 s

The object is created the first time something accesses it, not at program start. Kotlin makes sure this happens only once even if many threads hit it at the same time.

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

What is a named companion object, when would you name it, and how does naming affect call syntax from Kotlin and Java?

level: middleimportance: should knowfreq 45%

basics

~20 s

You can give the companion a name, like 'companion object Factory'. From Kotlin you still call members on the class name. The name lets you reference the companion explicitly and is used by Java callers.

open as a page

How does a Kotlin companion object differ from Java static members at the bytecode/runtime level?

level: middleimportance: should knowfreq 65%

basics

~10 s

Companion members are not real static methods by default. They live on a hidden singleton instance, so from Java you call them through a generated 'Companion' field. Adding @JvmStatic makes them true statics.

open as a page

Show how a Kotlin `object` replaces the classic Java singleton idiom, and explain what the compiler generates for JVM/Java interop.

level: middleimportance: should knowfreq 55%

basics

~20 s

In Java you wrote a private constructor and a static getInstance method. In Kotlin you just write object Name. Behind the scenes Kotlin creates one static INSTANCE field, which is how Java code reaches it.

open as a page

How does a Kotlin object expression capture variables from its enclosing scope, and how does this differ from Java anonymous classes?

level: middleimportance: should knowfreq 55%

basics

~20 s

An anonymous object can read and use variables defined around it, like a local variable in the enclosing function. Unlike Java, those variables don't have to be final, and Kotlin even lets the object change them.

open as a page

What are the rules for supertypes in a Kotlin object expression, including supplying constructor arguments and combining multiple types?

level: middleimportance: should knowfreq 45%

basics

~10 s

After the colon you can list one class plus any number of interfaces, separated by commas. If the class has a constructor, you pass its arguments right after the class name, like calling it.

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

Explain @JvmStatic on companion members: what it generates, its interaction with @JvmField/const, and the interop trade-offs.

level: seniorimportance: should knowfreq 40%

basics

~10 s

@JvmStatic tells the compiler to also emit a real static method or accessor on the outer class, so Java can call MyClass.foo() instead of MyClass.Companion.foo(). The companion still exists underneath.

open as a page

What are the design and concurrency pitfalls of putting mutable state in an `object` singleton, and how do you mitigate them?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A singleton object is shared by the whole app, so mutable data in it is global state. Many threads can change it at once, causing bugs, and it makes tests harder. Keep objects stateless or guard their state.

open as a page

Explain how the inferred type of an object expression with no declared supertype behaves when it leaks out of a function, and why extra members may become inaccessible.

level: seniorimportance: should knowfreq 35%

basics

~20 s

If an anonymous object has no named supertype, its special members are only usable nearby. Once you return it from a public function, the visible type drops to the supertype (or Any), so the extra members disappear from the caller's view.

open as a page

When should you reach for an object expression versus a lambda / SAM conversion when supplying an interface implementation in Kotlin?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Use a lambda when the interface has a single method and you only need that one method. Use an object expression when you must implement several methods, keep state, or override members of a class.

open as a page

When is a companion object initialized, and what concurrency/initialization pitfalls arise from putting mutable state in it?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

The companion is created once, lazily, the first time the class or a companion member is used. Because it is shared by everything, any mutable state in it is global and must be made thread-safe.

open as a page

What can an `object` declaration do that constructors-and-classes can't, and where can object declarations be placed (top-level vs nested)? Cover interface implementation, inheritance, and nesting.

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

An object can be declared at the top level or nested inside a class. It can implement interfaces and extend a class, but it can't have a constructor. A nested object is still a single shared instance.

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