object, companion object and singletons
Kotlin has a keyword that does something no other mainstream language does with one word: object
creates a singleton — a class with exactly one instance — directly. Combined with companion object, it replaces Java's static members and the boilerplate singleton pattern. This lesson
closes the classes module with these two.
object — a singleton in one word
Sometimes you want exactly one of something for the whole program: a single configuration, a single logger, a single registry. In Java this is the "singleton pattern" — a private constructor, a static instance, careful thread-safety — several lines and easy to get subtly wrong. Kotlin makes it a keyword:
object AppConfig {
val apiUrl = "https://api.example.com"
var requestTimeout = 30
fun describe() = "API at $apiUrl, timeout ${requestTimeout}s"
}
object AppConfig declares a class and creates its single instance, named AppConfig. You use it
directly — no construction, because there is exactly one:
println(AppConfig.apiUrl) // https://api.example.com
AppConfig.requestTimeout = 60
println(AppConfig.describe()) // API at https://api.example.com, timeout 60s
There is no AppConfig() — you cannot create another; the one instance is created lazily the first
time you touch it, and Kotlin handles the thread-safety of that for you. This is the cleanest
singleton in any mainstream language, and it is genuinely useful for a stateless service, a
configuration holder, or a single coordinator.
A word of caution, the same one every singleton carries: a singleton is global mutable state if
its properties are var. Global state is convenient and dangerous — anything can change it, and that
makes behaviour hard to trace and test. Use object freely for constants and stateless helpers;
think twice before putting mutable state in one, and prefer passing dependencies explicitly where you
can. That judgement is part of the best-practices module.
companion object — Kotlin's answer to static
Kotlin has no static keyword. Instead, members that belong to the class itself (not to an
instance) go in a companion object inside the class:
class Customer(val name: String, val id: Int) {
companion object {
const val MAX_NAME_LENGTH = 50
fun create(name: String): Customer {
require(name.length <= MAX_NAME_LENGTH) { "name too long" }
return Customer(name, nextId++)
}
private var nextId = 1
}
}
The companion object holds things that belong to Customer-the-class rather than to any one
customer: a constant, and a factory function create that builds instances with some logic (and
manages a shared id counter). You call them on the class name:
val c = Customer.create("Kavita")
println(Customer.MAX_NAME_LENGTH) // 50
println(c.id) // 1
Customer.create(...) and Customer.MAX_NAME_LENGTH read exactly like Java's static members, but
they live in a named companion object that is a real object (it can implement interfaces, unlike
Java statics). Every class has at most one companion object.
The factory-function pattern
That create function is worth calling out, because it is a common and clean use of companion
objects. A factory function is an alternative to a constructor that can do more — validate,
choose a subtype, cache, or give a clearer name than the constructor:
class Temperature private constructor(val celsius: Double) {
companion object {
fun fromCelsius(c: Double) = Temperature(c)
fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
}
}
val a = Temperature.fromCelsius(25.0)
val b = Temperature.fromFahrenheit(98.6)
println(b.celsius) // 37.0
Making the constructor private and offering named factory functions is a tidy pattern: the call
site reads Temperature.fromFahrenheit(98.6) — clearer than a bare constructor argument whose unit
you cannot see. The design-patterns module returns to this as the Kotlin form of the "factory"
pattern.
Companion object versus top-level function
One honest note, because it is easy to over-use companion objects. If a function does not need access to the class's private members and is not conceptually "a static method of this class", it does not need to be in a companion object — a top-level function (functions module) is often cleaner:
// probably better as a top-level function than a companion member:
fun formatRupees(paise: Int) = "₹${paise / 100}.${paise % 100}"
Use a companion object for factory functions, class-level constants, and anything that genuinely belongs to the class. Use a top-level function for a general utility that merely happens to relate to the class. Not everything needs to live inside a class in Kotlin — that Java habit is one to unlearn.
Check your work
What object declares. A singleton — a class with exactly one instance, created and named in one
keyword.
How you use an object. Directly by name — no construction, because there is only one.
The caution with a singleton. Mutable properties make it global mutable state — convenient but hard to trace and test; prefer it for constants and stateless helpers.
What replaces static in Kotlin. A companion object inside the class, holding class-level
members.
How you call companion members. On the class name — Customer.create(...),
Customer.MAX_NAME_LENGTH.
What a factory function is, and why it is useful. A companion function that builds instances — it can validate, choose a subtype, or give a clearer name than the constructor.
The private constructor + factory pattern. Hide the constructor and offer named factories
(fromCelsius, fromFahrenheit) so call sites read clearly.
When to use a top-level function instead of a companion member. For a general utility that does not need the class's private members or belong conceptually to the class.
Practice
- Write
object AppConfigwith a constant and a function. Access both directly and confirm there is no way to construct a second one. - Add a
varproperty to the object, change it from two places, and reflect on why global mutable state is risky. - Add a
companion objectto a class with a constant and a factorycreatefunction. Call both on the class name. - Write
Temperaturewith a private constructor andfromCelsius/fromFahrenheitfactories. Confirm you cannot call the constructor directly. - Have the factory validate its input with
requireand trigger the failure. - Take a function currently in a companion object that does not use any private members, and move it to a top-level function. Decide which is clearer.
- Model a single
Loggeras anobjectwith alog(message)function and use it from two different functions.
Official documentation
- Kotlin — Object declarations and expressions —
objectsingletons and anonymous objects. - Kotlin — Companion objects — The replacement for
static. - Kotlin — Coding conventions — Guidance on top-level versus companion members.
Next module — Coroutines: asynchronous code that reads like ordinary code, and essential for Android.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship