Objects & Companion Objects

Reviewed & published by Brayan K

Kotlin is a modern, concise language that builds the singleton pattern into the language with object, and replaces Java's static members with companion objects.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll create singletons, add class-level members with companion objects, and write factory functions.

What You'll Learn in This Lesson

1️⃣ Singletons with object

The object keyword declares a singleton — a single shared instance with no constructor. You access its members directly through its name. It's the built-in, thread-safe way to do the singleton pattern.

// 'object' declares a SINGLETON: one shared instance, created lazily.
object Database {
    private val records = mutableListOf<String>()

    fun add(record: String) { records.add(record) }
    fun count(): Int = records.size
}

fun main() {
    // No constructor — access members directly on the object name.
    Database.add("alice")
    Database.add("bob")
    println("Records: ${Database.count()}")   // -> Records: 2

    // It's the SAME instance everywhere:
    fun logCount() = println("From elsewhere: ${Database.count()}")
    logCount()                                 // -> From elsewhere: 2
}

Every reference to Database points at the same instance, so its state is shared across your whole program — exactly what a singleton should do.

2️⃣ Companion Objects and Factories

A companion object holds members tied to the class itself — Kotlin's answer to static. It's the natural home for constants and factory functions that build instances (often with a private constructor).

class User private constructor(val name: String) {

    // A companion object holds members tied to the CLASS, not instances —
    // similar to 'static' in Java. There's one per class.
    companion object {
        const val MAX_NAME_LENGTH = 20      // compile-time constant

        // A factory function (constructor is private, so use this):
        fun create(name: String): User {
            require(name.length <= MAX_NAME_LENGTH) { "Name too long" }
            return User(name.trim())
        }
    }

    fun greet() = "Hi, $name"
}

fun main() {
    // Call companion members via the class name:
    println(User.MAX_NAME_LENGTH)               // -> 20

    val user = User.create("  Ada  ")           // factory, not 'new'
    println(user.greet())                       // -> Hi, Ada
}

By making the constructor private and exposing User.create, the class fully controls how instances are made — validating and trimming input first.

Your turn. Replace the TODO, then run and compare.

// 🎯 YOUR TURN — replace each TODO then run main()

// 1) Make IdGenerator a singleton so it shares one counter.
TODO IdGenerator {                  // 👉 replace TODO with 'object'
    private var nextId = 1
    fun next(): Int = nextId++
}

fun main() {
    println(IdGenerator.next())     // expect 1
    println(IdGenerator.next())     // expect 2
    println(IdGenerator.next())     // expect 3
}

Mini-Challenge: A Circle Factory

Combine a companion object's constant and factory function with a private constructor.

// 🎯 MINI-CHALLENGE: a Circle with a factory.
// Save as Circle.kt and run main().
//
// 1. class Circle private constructor(val radius: Double).
// 2. Add a companion object with:
//    - const val PI = 3.14159
//    - fun ofDiameter(d: Double): Circle that returns Circle(d / 2)
//      (you'll need to make the constructor accessible to the
//       companion — a private constructor is fine since the companion
//       is inside the class).
// 3. Add fun area(): Double = PI * radius * radius   // PI from companion
// 4. In main(), create Circle.ofDiameter(10.0) and print its area.
//
// ✅ Example output:
//    78.53975

fun main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Objects

ConceptKotlin Syntax
Singletonobject Database { ... }
Use a singletonDatabase.add(x)
Companion objectcompanion object { ... }
Constantconst val MAX = 20
Factory callUser.create("Ada")
Private constructorclass U private constructor(...)

🎉 Lesson Complete!

Practice quiz

What does the object keyword create in Kotlin?

  • A new class instance each time
  • An abstract class
  • A singleton with exactly one shared instance
  • A data class

Answer: A singleton with exactly one shared instance. object declares a singleton, created lazily, with one shared instance.

How do you access a member of an object singleton named Database?

  • Directly through its name: Database.add(...)
  • By calling new Database()
  • Database().add(...)
  • Database::add()

Answer: Directly through its name: Database.add(...). Singletons have no constructor; access members via the object's name.

What is a companion object closest to in Java?

  • an inner class
  • a constructor
  • an interface
  • static members

Answer: static members. A companion object holds members tied to the class, like Java's static.

How many companion objects can a class have?

  • Exactly two
  • At most one
  • Unlimited
  • None

Answer: At most one. Each class may have at most one companion object.

How do you call a companion member like create on class User?

  • Through the class name: User.create(...)
  • On an instance: user.create(...)
  • With new User.create()
  • User::Companion()

Answer: Through the class name: User.create(...). Companion members are called via the class name, not an instance.

Why use a factory function instead of a public constructor?

  • It is always faster
  • Constructors cannot take parameters
  • It can have a name, validate input, or return a cached instance
  • It avoids using the class

Answer: It can have a name, validate input, or return a cached instance. Factories add naming, validation, caching, or subtype returns.

What is required for a const val in a companion object?

  • Any runtime-computed value
  • A compile-time constant: a primitive or String literal
  • A nullable type
  • A lambda expression

Answer: A compile-time constant: a primitive or String literal. const val must be a compile-time constant inlined at use sites.

What is the difference between const val and a regular val in a companion?

  • They are identical
  • const val is mutable
  • A regular val must be primitive
  • const val is inlined at compile time; a regular val is computed at runtime

Answer: const val is inlined at compile time; a regular val is computed at runtime. const val needs a compile-time literal; a regular val can be any runtime value.

What enables User.create to call a private constructor in the example?

  • The constructor is actually public
  • The companion object is inside the class, so it can access it
  • Reflection is used
  • Kotlin ignores private on constructors

Answer: The companion object is inside the class, so it can access it. A companion object lives inside the class and can use its private constructor.

When is an object singleton instance created?

  • At program startup always
  • Once per call site
  • Lazily, the first time it is accessed
  • Never until you call new

Answer: Lazily, the first time it is accessed. The singleton is created lazily on first access.

Continue this course

Frequently asked questions

What does the 'object' keyword create?

object declares a singleton — a class that has exactly one instance, created lazily the first time it's accessed, with no constructor you can call. You access its members directly through the object's name, like Database.add(...). Singletons are ideal for stateless utilities, shared registries, caches, or any concept where having more than one instance would be wrong. Kotlin builds the single-instance guarantee into the language, so you don't write the boilerplate singleton pattern yourself.

What is a companion object and how does it relate to 'static'?

A companion object is an object declared inside a class with the companion keyword, holding members that belong to the class itself rather than to any instance — the closest Kotlin equivalent to Java's static members. You call them through the class name (User.create(...)). Each class can have at most one companion object. It's commonly used for factory functions, constants, and class-level state that all instances share.

Why use a factory function in a companion object instead of a constructor?

Factory functions give you flexibility a constructor can't: they can have descriptive names (User.fromJson, Circle.ofDiameter), validate or transform inputs before building the object, return a cached instance instead of always creating a new one, or even return a subtype. By making the constructor private and exposing a companion factory, you control exactly how instances are created. Constructors are perfect for the simple case; factories shine when creation needs logic.

What's the difference between 'const val' and a regular val in a companion object?

const val defines a compile-time constant: its value is inlined at every use site and must be a primitive or String known at compile time, declared at the top level or inside an object/companion object. A regular val is computed at runtime and can hold any value, including the result of a function call. Use const val for fixed configuration like MAX_LENGTH = 20; use a regular val when the value isn't a simple compile-time literal.