When I need to define constants, avoid “magical numbers” and model data in Kotlin and other languages, I’ve usually default to enum classes.

I was told this was a good practice when I first started coding, and it seemed reasonable, but there are always alternatives to resolve each problem.

Today I want to share what choices exist in Kotlin when we need this kind of behaviour in our code.

Hey, can’t we just use compile-time constants?

In Kotlin, we have two options when it comes to define compile-time constants.

Both depend on the const val attributes, but can be defined in two different ways.

We can declare a const val inside a companion object, or as a top-level variable. Declarations inside classes aren’t allowed.

If you already have a companion object, I always like to declare my const valinside it, otherwise, I define it as top-level variable.

This is because when you declare a companion object you are adding overhead to your code as now you are creating a Singleton object just to declare a constant variable.

But to be honest, I don’t see how this is actually going to affect performance or any other parameters, as it’s such a small thing, even for enormous codebases.

I try to follow this as a “good practice”, but my opinion is that you do whatever suits better your code style and forget about the performance “drawback”.

Be aware that declaring a variable inside a companion object doesn’t mean that is a compile-time one, you need to explicitly mark it as a const val, otherwise, its value can be changed at runtime.

This is because here we would be creating a variable associated to an object that has been instantiated, is not exactly like a static variable in Java, but it has the same behaviour.

class Test {
    companion object {
        var mutable: Int = 2

fun main() {
    println("Before: ${Test.mutable}")
    Test.mutable = 3
    println("After: ${Test.mutable}")

// Result
// Before: 2
// After: 3

These approaches have some limitations, which are mainly about flexibility, as they are intended to be super simple.

You can’t define a custom getter for the property, but you can create a function to replicate that behaviour or think about a different approach.

One of them is that const val are limited to primitive types only. If you need more complexity, you are going to need to use sealed class or enums.

These classes not only allow the developer to define more complex objects that can work as constants. Now you can implement functions to expand their functionality with extensions functions even so if you like.

Now let’s talk a little more about these two alternatives, but be aware that these are not compile-time like const val where.

Sealed classes

Let’s start with a brief piece of code of how a sealed class looks.

They have the property that once they are compiled, they prevent third-party actors from extending them, and they cannot be instantiated, only their children can.

Subclasses used to be only available for declaration in the same file as where the sealed class was, but now you can do this as long as it’s in the same package.

I think it’s a good idea to keep it all in the same file to be organized, but now you can choose whatever you prefer.

Enum classes

Let’s start again with a simple example.

enum class Android {

// It's common practice to put the in uppercase the enum class members

With this kind of classes, the developer is restricted to just the instances that are defined in the declaration of the enum class. This means that we only have one instance of each item declared inside the class.

Although this might give the impression that it could be just a compile-time constant, as the set of possibilities of these classes is limited when we compile the program, these classes can also have mutable properties which values could be modified at runtime.

Although it can be done, this is a dangerous approach, as changing the value of a property of an enum class it’s going to affect its value in others parts of the code where the enum class is being used, as we are working with Singleton objects as stated before.

Note also that they are compiled at runtime as they are needed, like in Java.


In Kotlin, a enum class is a Singleton, where a sealed class can have the option to be defined as a traditional class which can be instantiated several times, meaning each of these classes can have their own state.

As we saw earlier, you can define a sealed class like an object, and then you would have the same behaviour as with a enum class.

Enum and sealed classes work great with the Kotlin when keyword. You can program it so that it’s exhaustive, this is, when you combine them with when, the IDE is going to offer you to complete the branches for you, and if one of the options is not inserted, the compiler will fail and you will avoid runtime errors.

It’s also interesting to know that both can implement interfaces if you need to.

Featured image by Mitchell Luo on Unsplash

If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.

Categorized in: