Kotlin makes singletons a first-class language feature with `object`. Describe object declarations, companion objects, and object expressions, and how each is initialized.
answer
- object = lazy thread-safe singleton (INSTANCE field)
- companion = the class's one 'static' holder, no static keyword
- object expression = new anonymous instance each time, not a singleton
- @JvmStatic / const val for Java-visible statics
- object expr can capture+mutate locals; lambda for SAM
basics
~20 sobject Foo { } declares a single, lazily created instance — a built-in singleton. A companion object is the one singleton attached to a class for 'static-like' members. An object : Type { } expression creates a one-off anonymous object on the spot.
solid answer
~40 sKotlin has three `object` forms. An **object declaration** (`object Config { ... }`) is a thread-safe, **lazily-initialized singleton** — the instance is created on first access, backed by the JVM class-loading guarantee (it compiles to a class with a static `INSTANCE` field). A **companion object** is a named-or-anonymous singleton tied to its enclosing class, used for factory methods and constants in place of Java `static`; members are accessed as `MyClass.member`. An **object expression** (`val x = object : Listener { override fun ... }`) creates an **anonymous, non-singleton** instance inline — Kotlin's replacement for Java anonymous classes, and unlike them it can implement multiple interfaces and capture+mutate enclosing variables. Use `@JvmStatic` on companion members and `const val` for true static fields/constants when Java interop matters.
code
kotlin · 13 linesobject AppConfig { // singleton
const val VERSION = "2.0" // compile-time constant
}
class Parser {
companion object { // 'statics'
@JvmStatic fun of(text: String) = Parser()
}
}
fun listener() = object : Runnable { // anonymous, new each call
override fun run() { /* ... */ }
}go deeper
Knows object declares a singleton and companion holds static-like members.
Distinguishes the three object forms and their instance/initialization semantics.
Uses companion factories, @JvmStatic/const val for interop, and flags global-mutable-state hazards of object singletons.
Weighs object singletons against DI-managed singletons, reasons about classloader scope, lazy-init thread safety, and testability/coupling tradeoffs.
## 1. Object declaration — the singleton ```kotlin object Registry { private val items = mutableListOf<String>() fun add(x: String) { items += x } } Registry.add("a") // access by the object's name ``` - Exactly **one instance** exists per classloader. - **Lazy & thread-safe**: created on first access, guaranteed by JVM class-init semantics (no double-checked-locking boilerplate). - Compiles to a class with a `public static final INSTANCE` field. - Can extend classes and implement interfaces: `object DefaultLogger : Logger { ... }`. - Cannot have a constructor (nothing to pass) and cannot be local. ## 2. Companion object — Kotlin's 'statics' Kotlin has **no `static` keyword**. Instead, one special object lives inside a class: ```kotlin class User private constructor(val id: Int) { companion object { fun create() = User(nextId()) private var seq = 0 private fun nextId() = ++seq } } User.create() // looks static, is the companion singleton ``` - A class may have **at most one** companion object; it may be named (`companion object Factory`) or anonymous (accessed as `Companion`). - It is also lazily initialized (on first access to the companion or the enclosing class init). - It can implement interfaces, enabling factory patterns and typeclass-like designs. - For Java callers: `@JvmStatic` promotes a member to a real static method; `const val` produces a compile-time constant; otherwise companion members are reached via `MyClass.Companion.x`. ## 3. Object expression — anonymous instance ```kotlin val handler = object : MouseAdapter() { override fun onClick() { /* ... */ } } ``` - Creates a **fresh instance every time** the expression is evaluated — **not** a singleton. - Replaces Java anonymous classes; can implement **multiple** interfaces and supertypes at once. - Can capture and even **mutate** variables from the enclosing scope (Java requires effectively-final). - If you only need a single-method interface, prefer a **lambda** with SAM conversion instead. ## Initialization summary | Form | Instances | Init timing | |------|-----------|-------------| | `object Foo` | 1 | lazy, on first access (thread-safe) | | `companion object` | 1 per class | lazy, on first companion/class access | | `object : T {}` expr | new each eval | when the expression runs | ## Common pitfalls - An object declaration holding mutable state is global mutable state — a hidden coupling and a testing/concurrency hazard. - Companion objects are **not** free statics: without `@JvmStatic` they incur an `INSTANCE` indirection, which matters in hot Java-interop paths.
- Is a companion object eagerly or lazily initialized?Lazily — it initializes on first access to the companion or to the enclosing class's static init, backed by JVM class-init thread safety.
- Why might you add `@JvmStatic` to a companion member?So Java sees a genuine static method/field on the class rather than going through `MyClass.Companion.x`, removing an indirection.
An object declaration is the single town hall everyone shares; an object expression is a sticky note you write fresh each time and throw away.
saying these in an interview costs you the question
- Saying object expressions are singletons
- Claiming Kotlin has a `static` keyword
- Thinking object declarations are eagerly initialized at program start
- Believing a class can have multiple companion objects
- Ignoring that object declarations create global mutable state risks