Articles
Technical thoughts, tutorials, and insights
Technical thoughts, tutorials, and insights
Published on June 1, 2025

Kotlin is packed with features that make coding cleaner and more intuitive. Two concepts that often spark confusion — even among seasoned developers — are companion objects and singletons. Let’s demystify them with a fresh perspective, zero jargon, and a quirky example you won’t find anywhere else.

A companion object is like a loyal sidekick tied to a specific class. It can access the class’s private members (yes, even the secrets!) and is ideal for housing methods or properties that belong to the class but don’t need a separate instance.
Declare it inside a class with the companion keyword:
class Spaceship(private val engineSerial: String) {
// Companion object
companion object BlackBox {
fun logLaunch(serial: String) {
println("Spaceship with engine $serial launched! 🚀")
}
}
fun launch() {
BlackBox.logLaunch(engineSerial) // Access private engineSerial
}
}
Here, BlackBox is a companion object inside the Spaceship class. It logs launches using the private engineSerial, something only a companion can do.
Access Private Data: It’s the only “object” that can peek into a class’s private properties.
Class-Specific Logic: Perfect for factory methods (Spaceship.create()) or constants tied to the class.
No Duplicate Instances: There’s only one companion per class, so no memory bloat.
A singleton in Kotlin is a globally accessible, single instance of an object. Declare it using object (not inside a class!), and it lives independently.
object CentralComputer {
private val spaceships = mutableListOf<String>()
fun trackSpaceship(serial: String) {
spaceships.add(serial)
println("Now tracking ${spaceships.size} spaceships.")
}
}
The CentralComputer singleton tracks all spaceships across your app. Any class can call CentralComputer.trackSpaceship(...), and there’s only one instance managing the list.
Global Access: Need a shared resource (like a database connection)? Singletons are your friend.
Lazy Initialization: Created only when first used, saving memory.
Stateless Services: Great for logging, analytics, or hardware control.
Aspect | Companion Object | Singleton
---------------------|-------------------------------------------|-----------------------------------------
Scope | Tied to a class (like Spaceship.BlackBox) | Standalone (CentralComputer)
Private Access | Can access private members of its class | Can’t unless nested inside the class
Initialization | Created when the class is loaded | Created lazily on first use
Use Case | Class-specific factories, constants | App-wide resources, shared state
You need a factory method for a class (e.g., Spaceship.fromConfig()).
Storing constants specific to a class (e.g., Spaceship.MAX_SPEED).
Accessing private properties of the class (like logging internal data).
2. Singletons Shine When…
Managing app-wide resources (e.g., a network client or database).
You need a single source of truth (e.g., a user session manager).
You want lazy initialization to optimize performance.
Let’s combine both concepts in a real-world scenario:
// Singleton: Manages all spaceships in the universe
object UniverseRegistry {
private val allShips = mutableListOf<Spaceship>()
fun register(ship: Spaceship) {
allShips.add(ship)
println("Universe now has ${allShips.size} spaceships!")
}
}
class Spaceship(private val engineSerial: String) {
init {
UniverseRegistry.register(this) // Singleton in action!
}
// Companion: Handles launch logic for individual ships
companion object BlackBox {
fun launch(ship: Spaceship) {
println("Engine ${ship.engineSerial} firing!")
}
}
fun launch() {
BlackBox.launch(this)
}
}
// Example Usage
val ship1 = Spaceship("X-101")
val ship2 = Spaceship("X-202")
ship1.launch() // Output: Engine X-101 firing!
Here, UniverseRegistry (singleton) tracks all ships globally, while BlackBox (companion) handles ship-specific launches.
Companion objects are your class’s trusted ally, handling intimate tasks.
Singletons are the overseers, managing cross-cutting concerns.
Next time you’re coding in Kotlin, ask: “Does this belong to a single class, or is it a global service?” The answer will guide you to the right tool.