ProgrammingBackend Developer

Describe the features of constant declarations and working with companion objects in Kotlin, including limitations and nuances.

Pass interviews with Hintsage AI assistant

Answer.

Constant declaration and the use of companion objects are important Kotlin concepts that replace the familiar static members from Java and the associated difficulties when crossing OOP and functional programming paradigms.

Background: In Java, static final fields are typically used for constants, and static methods for utility or factory functions. In Kotlin, instead of static, the concepts of object and companion object were introduced, and for compile-time constants — the keyword const.

Problem: There is a need to declare values that are independent of class instances, as well as to organize factory methods and static state without violating OOP integrity.

Solution: Companion objects are declared within a class and allow placing members common to all instances:

Code example:

class MyClass { companion object { const val DEFAULT_LIMIT = 10 fun create(): MyClass = MyClass() } } val limit = MyClass.DEFAULT_LIMIT val instance = MyClass.create()

Key features:

  • Everything inside a companion object behaves like static members in Java but maintains OOP integration and the possibility of inheritance/interfaces
  • For compile-time constants inside a companion object, the const modifier is mandatory
  • The companion object itself is accessible as an object (the reference can be stored) and can implement interfaces

Tricky Questions.

Can a companion object have multiple instances in a class?

No, a class can have only one companion object. An attempt to declare a second one will lead to a compilation error. However, any number of methods/properties are allowed inside the companion object.

Can lateinit variables be initialized inside a companion object?

No, because properties with const or variables inside a companion object must be initialized immediately or be val/var with explicit initialization. lateinit is not allowed for properties inside the companion object.

Can a companion object have its own name and when is this required?

Yes, the name of the companion object can be specified explicitly if you need to refer to it by name or, for example, implement interfaces. In other cases, it is optional. Example:

class Foo { companion object Factory { fun create(): Foo = Foo() } } val instance = Foo.Factory.create()

Typical Mistakes and Anti-Patterns

  • Using mutable static variables in companion objects may lead to race conditions in multithreaded code
  • Mixing compile-time constants (const) and runtime values in the same object
  • Unnecessary companion objects when file-level functions/properties suffice

Real-Life Example

Negative Case

All auxiliary functionality and global variables of the program are placed in a companion object, using var instead of val/const:

Pros:

  • All "static" functions and variables in one place, easy to find.

Cons:

  • Difficulties with testing, the state is global and can be accidentally changed elsewhere in the code.

Positive Case

Only compile-time constants (const val) and pure functions are used inside the companion object, all mutable items are either localized or passed through DI:

Pros:

  • Predictable and safe code, clear architecture, increased level of encapsulation.

Cons:

  • Sometimes it is necessary to create file-level objects for global utilities, requiring a bit more boilerplate.