Kotlin
Programming
Software Development
Debugging
Bug Fixing

Is this a bug in Kotlin or I missing something?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Most "is this a Kotlin bug?" moments turn out to be Kotlin behaving exactly as designed, just in a way that is easy to miss. The fastest way to tell the difference is to reduce the code to a tiny example and check a few recurring language rules: nullability, smart casts, read-only versus immutable collections, and structural versus referential equality.

Start By Shrinking The Example

Before assuming a compiler bug, make the example as small as possible. Remove frameworks, Android lifecycle code, and unrelated abstractions until the behavior still reproduces.

That immediately answers an important question:

  • is this Kotlin itself
  • or is it my surrounding environment

A tiny reproducible example also makes language rules much easier to see.

A Common Surprise: Read-Only Is Not Immutable

Kotlin's List<T> interface is read-only, but that does not mean the underlying object cannot change.

kotlin
1val items: List<Int> = mutableListOf(1, 2, 3)
2println(items)
3
4(items as MutableList).add(4)
5println(items)

This can look surprising if you expected List to guarantee deep immutability. It does not. It only prevents mutation through that particular reference type.

So if code changes "through a read-only list," that is usually not a compiler bug. It is a misunderstanding of the API contract.

Another Common Surprise: == Versus ===

Kotlin uses:

  • '== for structural equality'
  • '=== for referential identity'
kotlin
1data class User(val id: Int)
2
3val a = User(1)
4val b = User(1)
5
6println(a == b)   // true
7println(a === b)  // false

If you expected both comparisons to behave the same way, the result can look buggy even though it is correct.

Smart Casts Depend On Stability

Smart casts work only when Kotlin can prove a value will not change between the type check and the use.

kotlin
1fun printLength(value: String?) {
2    if (value != null) {
3        println(value.length)
4    }
5}

That works because value is stable inside the function.

But this may fail for a mutable property:

kotlin
1class Example {
2    var text: String? = "hello"
3
4    fun printLength() {
5        if (text != null) {
6            // text.length may not smart cast here
7        }
8    }
9}

Kotlin is protecting you from the possibility that text could change before use. Again, not a bug, just a stricter safety rule than some developers expect.

Platform Types Can Feel Inconsistent

Java interop introduces platform types, which are one of the biggest sources of "why did Kotlin allow that" confusion.

If a Java API does not clearly express nullability, Kotlin may let you treat the value more flexibly than a pure Kotlin type. That can lead to runtime null problems that feel inconsistent with the language's safety story.

The fix is usually to add explicit checks or annotations, not to assume the Kotlin compiler forgot its own rules.

How To Decide Whether It Might Really Be A Bug

It may be a compiler or tooling bug if:

  • the behavior changes unexpectedly across Kotlin versions
  • the generated bytecode disagrees with the source semantics
  • a tiny pure-Kotlin example reproduces something clearly impossible by the language rules

It is more likely a misunderstanding if:

  • the issue involves Java interop
  • the issue involves mutability or smart-cast stability
  • the issue disappears once the code is reduced to a minimal example

That is why reduction is the most important debugging step.

A Good Debugging Checklist

When Kotlin looks wrong, check:

  1. Am I mixing == and ===
  2. Am I assuming read-only means immutable
  3. Is smart-cast blocked because the value is mutable
  4. Is Java interop introducing platform-type ambiguity
  5. Can I reproduce this in a tiny self-contained file

That checklist catches a large share of "maybe Kotlin is broken" situations.

Common Pitfalls

The biggest mistake is posting a large framework example and trying to reason about it as if it were a language bug. Reduce it first.

Another mistake is assuming Kotlin collection interfaces guarantee immutability. They do not; they describe what operations are available through that reference.

People also misread smart-cast failures as compiler inconsistency when the real issue is mutability or thread safety.

Finally, Java interop can weaken Kotlin's safety guarantees in subtle ways. If a value comes from Java, be extra cautious before concluding that Kotlin itself is inconsistent.

Summary

  • Most "Kotlin bug" moments are actually Kotlin language rules surfacing in surprising ways.
  • Reduce the code to a minimal example before drawing conclusions.
  • Check read-only versus immutable, == versus ===, and smart-cast stability first.
  • Java interop is a frequent source of confusing behavior.
  • A tiny reproducible example is the best way to distinguish a real bug from a misunderstanding.

Course illustration
Course illustration

All Rights Reserved.