How do I create and use companion objects for static-like behavior in Kotlin?

In Kotlin, a companion object is an object declared inside a class that can hold members callable on the class itself, giving you static-like behavior.

Kotlin does not have Java-style static members directly. Instead, you usually use companion object.

Basic example

class User(val name: String) {
    companion object {
        const val DEFAULT_NAME = "Guest"

        fun createDefault(): User {
            return User(DEFAULT_NAME)
        }
    }
}

You can access companion object members using the class name:

fun main() {
    println(User.DEFAULT_NAME)

    val user = User.createDefault()
    println(user.name)
}

Output:

Guest
Guest

Why it feels like static

This:

User.createDefault()

is similar to calling a static method in Java:

User.createDefault();

But internally, Kotlin’s companion object is an actual singleton object associated with the class.

Companion object properties

You can put properties inside a companion object:

class Counter {
    companion object {
        var count = 0

        fun increment() {
            count++
        }
    }
}

Usage:

fun main() {
    Counter.increment()
    Counter.increment()

    println(Counter.count)
}

Output:

2

Named companion objects

A companion object can have a name:

class Database {
    companion object Factory {
        fun connect(): Database {
            return Database()
        }
    }
}

You can still access members through the class name:

val db = Database.connect()

Or through the companion object name:

val db = Database.Factory.connect()

Factory method example

A common use case is creating factory methods:

class Person private constructor(
    val name: String,
    val age: Int
) {
    companion object {
        fun of(name: String, age: Int): Person {
            require(age >= 0) { "Age cannot be negative" }
            return Person(name, age)
        }
    }
}

Usage:

fun main() {
    val person = Person.of("Alice", 30)
    println(person.name)
}

Constants in companion objects

For compile-time constants, use const val:

class ApiConfig {
    companion object {
        const val BASE_URL = "https://api.example.com"
        const val TIMEOUT_SECONDS = 30
    }
}

Usage:

println(ApiConfig.BASE_URL)

const val can only be used with primitive types and String.

Java interoperability

From Java, companion object members are normally accessed through Companion:

class MathUtils {
    companion object {
        fun double(x: Int): Int = x * 2
    }
}

Java usage:

int result = MathUtils.Companion.double(5);

If you want Java callers to use it like a real static method, add @JvmStatic:

class MathUtils {
    companion object {
        @JvmStatic
        fun double(x: Int): Int = x * 2
    }
}

Then Java can call:

int result = MathUtils.double(5);

For constants:

class Constants {
    companion object {
        const val APP_NAME = "MyApp"
    }
}

Java can access this as:

String appName = Constants.APP_NAME;

Companion objects can implement interfaces

Because companion objects are real objects, they can implement interfaces:

interface Parser<T> {
    fun parse(value: String): T
}

class User(val name: String) {
    companion object : Parser<User> {
        override fun parse(value: String): User {
            return User(value)
        }
    }
}

Usage:

fun main() {
    val user = User.parse("Alice")
    println(user.name)
}

You can also pass the companion object where the interface is expected:

fun <T> parseWith(parser: Parser<T>, value: String): T {
    return parser.parse(value)
}

val user = parseWith(User, "Bob")

Here, User refers to the companion object when used as a value.

Key points

  • Use companion object for static-like members.
  • Access members as ClassName.member.
  • Use const val for compile-time constants.
  • Use @JvmStatic if Java callers need static-style access.
  • Companion objects are real singleton objects.
  • Companion objects can have names and implement interfaces.

How do I use inner and nested classes in Kotlin?

In Kotlin, classes can be declared inside other classes in two main ways:

  1. Nested classes — default behavior
  2. Inner classes — declared with the inner keyword

Nested classes

A class declared inside another class is nested by default.

class Outer {
    class Nested {
        fun message(): String {
            return "Hello from Nested"
        }
    }
}

You create an instance of the nested class using the outer class name:

fun main() {
    val nested = Outer.Nested()
    println(nested.message())
}

Important point

A nested class does not have access to members of the outer class.

class Outer {
    private val name = "Outer"

    class Nested {
        fun printName() {
            // println(name) // Error: cannot access outer class member
        }
    }
}

This is similar to a static nested class in Java.


Inner classes

If you want the nested class to access members of the outer class, mark it with inner.

class Outer {
    private val name = "Outer"

    inner class Inner {
        fun message(): String {
            return "Hello from $name"
        }
    }
}

You create an inner class instance from an instance of the outer class:

fun main() {
    val outer = Outer()
    val inner = outer.Inner()

    println(inner.message())
}

Output:

Hello from Outer

Difference between nested and inner classes

Feature Nested class Inner class
Keyword No keyword needed Uses inner
Has reference to outer class No Yes
Can access outer members No Yes
Instantiation Outer.Nested() Outer().Inner()
Similar to Java static nested class non-static inner class

Accessing this from an inner class

Inside an inner class, this refers to the inner class instance.

To refer to the outer class instance, use this@Outer.

class Outer {
    private val value = "Outer value"

    inner class Inner {
        private val value = "Inner value"

        fun printValues() {
            println(value)
            println(this.value)
            println([email protected])
        }
    }
}

Output:

Inner value
Inner value
Outer value

Example with state

class ShoppingCart {
    private val items = mutableListOf<String>()

    fun addItem(item: String) {
        items.add(item)
    }

    inner class Summary {
        fun printSummary() {
            println("Cart has ${items.size} items")
            println(items.joinToString())
        }
    }
}

fun main() {
    val cart = ShoppingCart()
    cart.addItem("Book")
    cart.addItem("Pen")

    val summary = cart.Summary()
    summary.printSummary()
}

Output:

Cart has 2 items
Book, Pen

Here, Summary is an inner class because it needs access to items from ShoppingCart.


When to use each

Use a nested class when:

  • The class is logically grouped inside another class
  • It does not need access to the outer class instance
  • You want a namespace-like structure
class ApiResponse {
    class Error(val code: Int, val message: String)
}

Use an inner class when:

  • The class needs access to the outer class’s properties or functions
  • Each inner class instance is tied to a specific outer class instance
class Form {
    private val fields = mutableListOf<String>()

    inner class Validator {
        fun validate(): Boolean {
            return fields.isNotEmpty()
        }
    }
}

Summary

class Outer {
    class Nested {
        // No access to Outer instance
    }

    inner class Inner {
        // Has access to Outer instance
    }
}

Use nested classes by default, and use inner only when the inner class needs to access the outer class instance.

How do I use abstract classes and methods in Kotlin?

In Kotlin, an abstract class is a class that cannot be instantiated directly. It is meant to be subclassed.

An abstract method is a method declared without an implementation. Subclasses must override it.

Basic example

abstract class Animal {
    abstract fun makeSound()

    fun sleep() {
        println("Sleeping...")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        println("Woof!")
    }
}

fun main() {
    val dog = Dog()
    dog.makeSound()
    dog.sleep()
}

Output:

Woof!
Sleeping...

Key points

1. Use abstract before the class

abstract class Shape

You cannot create an instance of it:

val shape = Shape() // Error

2. Abstract methods have no body

abstract fun area(): Double

A subclass must implement them:

class Circle(val radius: Double) : Shape() {
    override fun area(): Double {
        return Math.PI * radius * radius
    }
}

3. Abstract classes can have regular methods

abstract class Shape {
    abstract fun area(): Double

    fun describe() {
        println("This is a shape")
    }
}

4. Abstract properties are allowed

abstract class Vehicle {
    abstract val maxSpeed: Int
}

class Car : Vehicle() {
    override val maxSpeed: Int = 200
}

5. Abstract classes can have constructors

abstract class Person(val name: String) {
    abstract fun work()
}

class Developer(name: String) : Person(name) {
    override fun work() {
        println("$name writes code")
    }
}

Complete example

abstract class Shape(val name: String) {
    abstract fun area(): Double

    fun printInfo() {
        println("$name has area ${area()}")
    }
}

class Rectangle(
    name: String,
    val width: Double,
    val height: Double
) : Shape(name) {
    override fun area(): Double {
        return width * height
    }
}

class Circle(
    name: String,
    val radius: Double
) : Shape(name) {
    override fun area(): Double {
        return Math.PI * radius * radius
    }
}

fun main() {
    val shapes = listOf(
        Rectangle("Rectangle", 5.0, 3.0),
        Circle("Circle", 2.0)
    )

    for (shape in shapes) {
        shape.printInfo()
    }
}

Abstract class vs interface

Use an abstract class when you want to share state or constructor logic:

abstract class BaseRepository(val tableName: String) {
    abstract fun findAll(): List<String>
}

Use an interface when you mainly want to define behavior:

interface Drawable {
    fun draw()
}

A class can extend only one abstract class, but it can implement multiple interfaces:

abstract class Animal

interface Runnable {
    fun run()
}

interface Swimmable {
    fun swim()
}

class Duck : Animal(), Runnable, Swimmable {
    override fun run() {
        println("Duck runs")
    }

    override fun swim() {
        println("Duck swims")
    }
}

In short: use abstract class for a shared base with common implementation/state, and use abstract fun or abstract val for members subclasses must provide.

How do I use visibility modifiers (private, internal, protected, public) in Kotlin classes?

In Kotlin, visibility modifiers control where classes, properties, functions, and constructors can be accessed from.

The main visibility modifiers are:

  • public
  • private
  • protected
  • internal

If you do not specify a visibility modifier, Kotlin uses public by default.


1. public

public means the declaration can be accessed from anywhere.

class User {
    public val name: String = "Alice"

    public fun sayHello() {
        println("Hello, $name")
    }
}

Because public is the default, this is equivalent:

class User {
    val name: String = "Alice"

    fun sayHello() {
        println("Hello, $name")
    }
}

Use public when you want something to be part of the normal external API of a class.


2. private

Inside a class, private means the member can only be accessed inside that same class.

class BankAccount {
    private var balance: Double = 0.0

    fun deposit(amount: Double) {
        if (amount > 0) {
            balance += amount
        }
    }

    fun getBalance(): Double {
        return balance
    }
}

Usage:

fun main() {
    val account = BankAccount()

    account.deposit(100.0)

    println(account.getBalance())

    // Error:
    // println(account.balance)
}

Here, balance is hidden from outside code. The class controls access through deposit() and getBalance().

Private class members

class UserService {
    private fun validateName(name: String): Boolean {
        return name.isNotBlank()
    }

    fun createUser(name: String) {
        if (validateName(name)) {
            println("User created: $name")
        }
    }
}

validateName() is an implementation detail and cannot be called from outside UserService.


3. protected

protected means the member is visible inside:

  1. The class where it is declared
  2. Its subclasses

It is not visible to general outside code.

open class Animal {
    protected val name: String = "Animal"

    protected fun makeSound() {
        println("Some sound")
    }
}

class Dog : Animal() {
    fun bark() {
        println(name)
        makeSound()
        println("Woof!")
    }
}

Usage:

fun main() {
    val dog = Dog()

    dog.bark()

    // Error:
    // println(dog.name)

    // Error:
    // dog.makeSound()
}

Dog can access name and makeSound() because it inherits from Animal, but outside code cannot.

Important note about protected

In Kotlin, protected is mainly for class inheritance. Unlike Java, Kotlin does not make protected members visible to the whole package.


4. internal

internal means the declaration is visible within the same module.

A module is usually something like:

  • a Gradle source set
  • a Maven project
  • an IntelliJ IDEA module
  • a set of files compiled together
internal class InternalLogger {
    fun log(message: String) {
        println("LOG: $message")
    }
}

Code in the same module can use it:

fun main() {
    val logger = InternalLogger()
    logger.log("Application started")
}

But code from another module cannot access InternalLogger.

Use internal when you want something available across your project/module but not exposed as a public API to other modules.


Visibility in class members

You can apply visibility modifiers to properties and functions:

class Profile {
    public val username: String = "guest"
    private val passwordHash: String = "abc123"
    internal val accountId: Int = 42
    protected val role: String = "user"

    public fun showUsername() {
        println(username)
    }

    private fun checkPassword() {
        println(passwordHash)
    }
}

However, protected only makes sense in classes that may be inherited from:

open class BaseController {
    protected fun authorize() {
        println("Checking permissions")
    }
}

class UserController : BaseController() {
    fun getUser() {
        authorize()
        println("Returning user")
    }
}

Visibility for constructors

You can also control constructor visibility.

Private constructor

Useful when you want to restrict object creation.

class DatabaseConnection private constructor() {
    companion object {
        fun create(): DatabaseConnection {
            return DatabaseConnection()
        }
    }
}

Usage:

fun main() {
    val connection = DatabaseConnection.create()

    // Error:
    // val direct = DatabaseConnection()
}

Visibility for property setters

A common Kotlin pattern is to expose a property for reading but restrict writing.

class Counter {
    var count: Int = 0
        private set

    fun increment() {
        count++
    }
}

Usage:

fun main() {
    val counter = Counter()

    counter.increment()
    println(counter.count)

    // Error:
    // counter.count = 10
}

Here:

  • count can be read from outside the class
  • count can only be changed inside the class

You can also use protected set or internal set:

open class Document {
    var status: String = "draft"
        protected set
}

Subclasses can change status, but outside code cannot.


Top-level visibility

Kotlin also allows functions, properties, and classes outside classes.

private fun helper() {
    println("Only visible in this file")
}

internal fun moduleHelper() {
    println("Visible in the same module")
}

public fun publicApi() {
    println("Visible everywhere")
}

For top-level declarations:

Modifier Meaning
public Visible everywhere
private Visible only in the same file
internal Visible in the same module
protected Not allowed at top level

Quick comparison

Modifier Class member visibility
public Visible everywhere
private Visible only inside the declaring class
protected Visible inside the declaring class and subclasses
internal Visible inside the same module

For top-level declarations:

Modifier Top-level visibility
public Visible everywhere
private Visible only in the same file
internal Visible inside the same module
protected Not allowed

Practical example

open class Account(
    public val owner: String,
    private var balance: Double
) {
    internal val accountNumber: String = "ACC-123"

    protected fun canWithdraw(amount: Double): Boolean {
        return amount <= balance
    }

    fun deposit(amount: Double) {
        if (amount > 0) {
            balance += amount
        }
    }

    fun currentBalance(): Double {
        return balance
    }
}

class SavingsAccount(owner: String, balance: Double) : Account(owner, balance) {
    fun withdraw(amount: Double) {
        if (canWithdraw(amount)) {
            println("Withdrawal allowed")
        } else {
            println("Insufficient funds")
        }
    }
}

Usage:

fun main() {
    val account = SavingsAccount("Alice", 500.0)

    println(account.owner)
    println(account.currentBalance())

    account.deposit(100.0)
    account.withdraw(50.0)

    // Error: private
    // println(account.balance)

    // Error: protected
    // account.canWithdraw(100.0)

    // Accessible only in the same module:
    // println(account.accountNumber)
}

Rule of thumb

Use the most restrictive visibility that still works:

  • Use private for implementation details.
  • Use protected for behavior intended only for subclasses.
  • Use internal for code shared inside a module but hidden from external modules.
  • Use public for APIs that other code is expected to use.

In most Kotlin classes, you will commonly use private for internal state and leave only the necessary functions or properties public.

How do I implement interfaces and use multiple inheritance in Kotlin?

In Kotlin, interfaces define a contract that classes can implement. Kotlin supports multiple inheritance of interfaces, but not multiple inheritance of classes.

1. Defining an interface

An interface can declare:

  • abstract properties
  • abstract functions
  • functions with default implementations
interface Drivable {
    val maxSpeed: Int

    fun drive()

    fun stop() {
        println("Stopping the vehicle")
    }
}

Here:

  • maxSpeed must be implemented by any class using the interface.
  • drive() has no body, so it must be implemented.
  • stop() has a default implementation, so overriding it is optional.

2. Implementing an interface

Use : after the class name to implement an interface.

class Car : Drivable {
    override val maxSpeed: Int = 180

    override fun drive() {
        println("The car is driving at up to $maxSpeed km/h")
    }
}

Usage:

fun main() {
    val car = Car()

    car.drive()
    car.stop()
}

Output:

The car is driving at up to 180 km/h
Stopping the vehicle

3. Implementing multiple interfaces

A class can implement more than one interface by separating them with commas.

interface Flyable {
    fun fly() {
        println("Flying")
    }
}

interface Swimmable {
    fun swim() {
        println("Swimming")
    }
}

class Duck : Flyable, Swimmable

Usage:

fun main() {
    val duck = Duck()

    duck.fly()
    duck.swim()
}

4. Handling conflicting default implementations

If two interfaces provide a function with the same signature, the implementing class must override it.

interface Printer {
    fun print() {
        println("Printing from Printer")
    }
}

interface Scanner {
    fun print() {
        println("Printing from Scanner")
    }
}

class AllInOneMachine : Printer, Scanner {
    override fun print() {
        super<Printer>.print()
        super<Scanner>.print()
        println("Printing from AllInOneMachine")
    }
}

Usage:

fun main() {
    val machine = AllInOneMachine()

    machine.print()
}

Output:

Printing from Printer
Printing from Scanner
Printing from AllInOneMachine

The syntax:

super<Printer>.print()

means “call the print() implementation from the Printer interface.”

5. Interfaces with properties

Interfaces can declare properties, but they do not store state directly like classes do.

interface Identifiable {
    val id: String
}

class User(
    override val id: String,
    val name: String
) : Identifiable

Usage:

fun main() {
    val user = User("u123", "Alice")

    println(user.id)
    println(user.name)
}

6. Kotlin and multiple inheritance

Kotlin allows:

class Duck : Flyable, Swimmable

But Kotlin does not allow multiple class inheritance:

open class Animal
open class Machine

// Not allowed in Kotlin
class RobotDog : Animal(), Machine()

Instead, Kotlin allows:

  • one superclass
  • multiple interfaces
open class Animal {
    fun eat() {
        println("Eating")
    }
}

interface Runnable {
    fun run()
}

interface Trainable {
    fun train()
}

class Dog : Animal(), Runnable, Trainable {
    override fun run() {
        println("Dog is running")
    }

    override fun train() {
        println("Dog is training")
    }
}

Key syntax

class ClassName : InterfaceName
class ClassName : InterfaceOne, InterfaceTwo
class ClassName : SuperClass(), InterfaceOne, InterfaceTwo

Summary

  • Use interface to define behavior.
  • Use : to implement interfaces.
  • Use override to implement interface members.
  • Kotlin supports multiple interface inheritance.
  • Kotlin does not support inheriting from multiple classes.
  • If interfaces have conflicting default methods, override the method and choose which parent implementation to call using super<InterfaceName>.