← Back to blog Post

Kotlin: did you know?

Hosted here as an English translation. Originally published on Lean Mind.

Kotlin is a modern, efficient programming language designed to be simple and safe. Since its introduction, it has gained popularity thanks to its interoperability with Java and its ability to express ideas with concise, readable code. Let’s review some of the features that make Kotlin stand out and improve the day-to-day development experience.

Variables: val and var

Kotlin has two main kinds of variables: val and var. The key difference is that val defines immutable variables, so once a value is assigned it cannot be changed, while var allows reassignment. Kotlin also introduces the lateinit keyword, which indicates that a var will be initialized later, something useful when immediate initialization is not possible.

You can check whether a lateinit variable has already been initialized, as long as it is not a primitive type:

lateinit var foo: String

if (::foo.isInitialized) {
}

Template strings

Method names can use template strings, which are especially helpful when writing readable tests:

@Test
fun `should do something`() {
  // ...
}

Raw strings

You can write raw strings by using triple quotes ("""), which makes multiline text easier to work with without relying on escape characters.

val text = """
  Hello World
  using
  multiline
"""

Data classes

Kotlin provides data class as a convenient way to model data. It generates methods such as equals, hashCode, and toString, making objects easier to compare and inspect without a lot of boilerplate.

data class BookingCustomData(
  val reference: String,
  val origin: String,
  val status: String,
  val killSlots: String,
  val noOfPackages: String,
  val cargoType: String,
  val weight: String,
  val contractRef: String,
)

Companion objects instead of static methods

Kotlin does not have static methods in the Java sense. Instead, it has companion object, which behaves like a singleton attached to the class and is used to host static-like members.

class Irrelevant {
  companion object {
    const val USERNAME1 = "USERNAME1"

    fun addTenTo(number: Int): Int {
      return number + 10
    }
  }
}

val aUserName = Irrelevant.USERNAME1
val newNumber = Irrelevant.addTenTo(10)

Inheritance

Classes are final by default, which means methods cannot be overridden in derived classes unless the class or method is marked with open. You can also stop deeper overriding by marking an override as final.

open class Parent {
  open fun foo(): String {
    return "Hello from Parent"
  }
}

class Child : Parent() {
  final override fun foo(): String {
    return "Hello from Child"
  }
}

Extensions on interfaces and objects

Both interfaces and companion objects can have extension properties and functions. These can be added from outside the original class without modifying its code.

val <T> List<T>.lastIndex: Int
  get() = size - 1

fun Irrelevant.Companion.print() {
  println("Irrelevant")
}

Generics

In template-based types, Kotlin lets you restrict how generic parameters are used in classes or interfaces through in and out.

  • out is used for covariant types: they can be returned but not accepted as input.
  • in is used for contravariant types: they can be accepted as input but not returned.
interface BaseUseCase<in I, out O> {
  fun execute(input: I): O
}

Operator overloading

You can overload operators using the operator keyword. For example, overloading invoke lets an instance be called directly as if it were a function.

interface BaseUseCase<in I, out O> {
  operator fun invoke(input: I): O
}

Scope functions

Methods can use let, also, and run, all part of Kotlin’s scope functions, to chain operations and avoid explicit control structures. A common use case is handling optional values.

customerRepository
  .findByCode(customers[0].code)
  ?.let {
    customerRepository.delete(it)
  }

Ranges

The range concept lets you define numeric or character intervals in a very simple way:

for (i in 1..3) { }
for (i in 'a'..'z') { }

Pattern matching

Kotlin supports a form of pattern matching through when, which acts as a more powerful replacement for the classic switch.

when (x) {
  1 -> print("value is 1")
  2 -> print("value is 2")
  else -> print("value is not 1 or 2")
}

It also supports ranges and grouped values inside the same structure:

val extractResult = when (id) {
  in 1..2 -> foo()
  3, 4, 5, 6, 30 -> bar()
  in 23..24 -> otherStuff()
  else -> differentStuff()
}

Sealed classes

Sealed classes extend hierarchical class design by restricting the possible subclasses to a known set at compile time. That is especially useful when the domain contains a fixed number of valid variants.

sealed class Subscription {
  data class Monthly(): Subscription()
  data class Yearly(): Subscription()
  data class Quarterly(): Subscription()
}

Combined with when, they help surface missing business cases.

TODO for unimplemented operations

Kotlin includes the reserved word TODO, which is actually a function returning Nothing and throwing an exception, making it useful for marking unfinished code paths.

fun TODO(): Nothing = throw NotImplementedError()

Contracts and smart casts

Kotlin has an experimental contract feature that gives the compiler additional information about program flow and type handling. For example, when we prove that something is not null, the compiler can apply a smart cast to a non-nullable type.

Type aliases

You can define typealias declarations to provide alternative or simplified names for complex types, making code easier to read and evolve.

class Irrelevant {
  class Foo
}

typealias SomethingRandom = Irrelevant.Foo

Property delegation

Kotlin includes a property delegation system that lets other classes or functions handle the logic behind property access and mutation. Common delegates include:

  • map, to back properties with keys in a map.
  • lazy, to initialize a property only on first access.
  • observable, to run code whenever the property value changes.
class User {
  var name: String by observable("<no name>") { _, old, new ->
    println("$old -> $new")
  }
}

Taken together, these features show how Kotlin provides many tools for writing expressive and efficient code.

Do you know any other Kotlin feature worth highlighting?

  • kotlin
  • programming-languages
  • language-features