skip to content

Explain how Kotlin maps types to the JVM: mapped types, primitives vs boxing, and what happens to Kotlin-only types like Unit and Nothing.

level: seniorimportance: should knowfreq 50%

answer

  1. Mapped types: String=java.lang.String, Any=Object, List=java.util.List
  2. Int=int, but Int?/List<Int> box to Integer (erasure/nullable)
  3. IntArray=int[] vs Array<Int>=Integer[]
  4. Unit return -> void; Unit value -> singleton INSTANCE
  5. Nothing = bottom type, no instances, no JVM equivalent

basics

~20 s

Many Kotlin types are just renamed JVM types (Kotlin String is Java String). Numbers use fast primitives when possible and box only when they must be nullable or generic. Unit becomes void-like; some types like Nothing have no real runtime value.

solid answer

~40 s

Kotlin defines **mapped types**: at runtime `kotlin.String` is `java.lang.String`, `kotlin.Any` is `java.lang.Object`, and `List`/`MutableList` map to `java.util.List`. Numeric types map to JVM **primitives** (`Int`→`int`) when used directly, but **box** to wrappers (`Integer`) when nullable (`Int?`) or used as a **generic type argument** (`List<Int>` holds `Integer`s due to erasure). `Unit` — Kotlin's 'no meaningful value' type with a single instance — compiles to `void` for function returns (a function returning `Unit` is a Java `void` method), though as a value it's `kotlin.Unit.INSTANCE`. `Nothing` (the type with no instances, e.g. for functions that always throw) has no JVM equivalent and surfaces roughly as `Void`/nothing-returns; you can't have a real `Nothing` value. Kotlin's `Array<Int>` is `Integer[]` while `IntArray` is the primitive `int[]`. Understanding these mappings prevents boxing surprises and signature mismatches at the boundary.

code

kotlin · 8 lines
kotlin
fun a(): Int = 1                 // returns primitive int
fun b(): Int? = null            // returns Integer (boxed)
fun unitFun(): Unit { }          // Java sees: void unitFun()
fun fail(): Nothing = throw IllegalStateException()  // never returns

val prim: IntArray = intArrayOf(1, 2)     // int[]
val boxed: Array<Int> = arrayOf(1, 2)     // Integer[]
val list: List<Int> = listOf(1)          // List<Integer> via erasure

go deeper

for a junior

Knows Kotlin String/numbers correspond to Java types.

for a middle

Explains primitive vs boxed and that nullable numbers box.

for a senior

Covers erasure-driven boxing in generics, IntArray vs Array<Int>, Unit->void, Nothing as bottom type.

for a principal

Uses mapping knowledge to design allocation-light APIs and avoids boxing in hot paths/cross-language contracts.

## Mapped types Kotlin doesn't ship a parallel universe of classes; for many types it **maps** its name onto an existing JVM type. At runtime the object *is* the JVM type: - `kotlin.Any` ⇄ `java.lang.Object` - `kotlin.String` ⇄ `java.lang.String` - `kotlin.Throwable` ⇄ `java.lang.Throwable` - `kotlin.collections.List<T>` ⇄ `java.util.List` (read-only view); `MutableList<T>` also ⇄ `java.util.List` (the read-only/mutable split is a *Kotlin-compile-time* distinction, not a runtime one) - Numeric: `Int`⇄`int`, `Long`⇄`long`, `Double`⇄`double`, `Boolean`⇄`boolean`, `Char`⇄`char`, etc. ## Primitives vs boxing The JVM has **primitive** types (`int`, `long`, unboxed, on the stack, no object header) and **boxed** wrappers (`Integer`, `Long`, heap objects). Kotlin uses primitives where it can for performance, but **boxes** when: - The type is **nullable** (`Int?` must be `Integer`, because primitives can't be null). - It's a **generic type argument** — due to **type erasure**, `List<Int>` stores `Integer` objects (`int` can't be a type parameter). - It's used where an `Object`/`Any` is expected. ```kotlin val a: Int = 1 // int (primitive) val b: Int? = 1 // Integer (boxed, can be null) val xs: List<Int> = listOf(1) // List<Integer> at runtime (erasure) ``` ## Arrays Kotlin distinguishes: - `IntArray` ⇄ `int[]` (primitive array — no boxing) - `Array<Int>` ⇄ `Integer[]` (boxed) Use `IntArray`/`LongArray`/etc. for primitive-array interop and performance. ## Unit `Unit` is Kotlin's analogue of 'returns nothing meaningful' — a real type with exactly **one** instance (`Unit`/`Unit.INSTANCE`). As a **return type** it compiles to JVM `void`: a Kotlin `fun f(): Unit` appears to Java as `void f()`. As a *value* (e.g. a generic `T = Unit`), it's the singleton `kotlin.Unit`. ## Nothing `Nothing` is the **bottom type**: it has **no instances**. It types expressions that never return normally — a function that always `throw`s, or `TODO()`. The JVM has no bottom type, so `Nothing` has no faithful representation; a `Nothing`-returning function compiles with a `void`/throwing shape and Java can't meaningfully obtain a `Nothing` value. `Nothing?` (only value `null`) is sometimes used for typed nulls. ## Why it matters at the boundary - **Boxing surprises:** a hot `List<Int>` boxes every element; switching to `IntArray` avoids it. - **Signature mismatch:** a Kotlin `fun f(): Unit` is `void` in Java — don't expect a `Unit` object. - **Generics + primitives:** you can't have `List<int>`; it's always boxed. - **Nullability + boxing interact:** making a number nullable silently turns it into a heap object. ```kotlin // Performance contrast fun sumBoxed(xs: List<Int>): Int = xs.sum() // elements are Integer fun sumPrim(xs: IntArray): Int = xs.sum() // pure int[], no boxing ``` The model to remember: **Kotlin types are a source-level lens over JVM types**; the compiler picks primitive vs boxed and maps names so the result is ordinary, idiomatic bytecode.

  • Why does List<Int> box its elements even though Int maps to int?
    Generic type arguments are erased to objects on the JVM, and primitives can't be type parameters, so each element is an Integer.
  • How does a Kotlin function returning Unit appear to Java?
    As a void method. Unit-as-return compiles to void; only as a generic value does it become the kotlin.Unit singleton.

saying these in an interview costs you the question

  • Saying Kotlin String is a distinct class wrapping java.lang.String
  • Claiming List<Int> stores primitive ints
  • Treating Unit as always a real object even for returns (it's void)
  • Thinking Nothing has a runtime instance you can pass around

context