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 val
inside 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
KotlinThese 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 {
PIXEL, SAMSUNG, XIAOMI, ONEPLUS
}
// It's common practice to put the in uppercase the enum class members
Kotlin
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.
Considerations
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.