RizTech Academy logo
RizTech Academy
Classes and ObjectsLesson 5 of 520 min

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

  1. Write object AppConfig with a constant and a function. Access both directly and confirm there is no way to construct a second one.
  2. Add a var property to the object, change it from two places, and reflect on why global mutable state is risky.
  3. Add a companion object to a class with a constant and a factory create function. Call both on the class name.
  4. Write Temperature with a private constructor and fromCelsius/fromFahrenheit factories. Confirm you cannot call the constructor directly.
  5. Have the factory validate its input with require and trigger the failure.
  6. 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.
  7. Model a single Logger as an object with a log(message) function and use it from two different functions.

Official documentation

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