Kotlin numbers like Int are classes, not language primitives. How does the compiler represent them on the JVM, and when does boxing into java.lang wrappers happen?
answer
- no int keyword: Int is a class
- non-null Int -> primitive int
- Int?, generics, Any, Number -> boxed
- IntArray=int[], Array<Int>=Integer[]
- === unreliable on boxed; use ==
basics
~20 sIn source you always write the class name Int. On the JVM the compiler uses the cheap primitive int where it can, but switches to the boxed java.lang.Integer when the value must be nullable (Int?) or used as a generic type argument, like in a List<Int>.
solid answer
~40 sKotlin presents `Int`, `Long`, etc. as ordinary classes — there is no `int` keyword. On the JVM the compiler optimizes: a non-nullable `Int` becomes the primitive `int`, a `Long` becomes `long`, and so on, so plain arithmetic carries no boxing cost. Boxing to `java.lang.Integer`/`Long`/`Double` happens when a reference type is required: a **nullable** `Int?` (because primitives can't be null), a **generic** type argument (`List<Int>`, `Map<String, Int>` — generics erase to `Object`), storing in an `Any`/`Any?` reference, or upcasting to `Number`/`Comparable`. A subtle consequence: boxed identity is not guaranteed — `===` on two boxed `Int?` outside the JVM's `Integer` cache range (`-128..127`) can be false even when `==` is true, so always use `==` (structural) for values. `IntArray` stores primitives (`int[]`), whereas `Array<Int>` boxes (`Integer[]`).
code
kotlin · 7 linesval a: Int = 1000
val b: Int = 1000
println(a === b) // true: compared as primitive int
val x: Int? = 1000
val y: Int? = 1000
println(x === y) // false: two boxed Integer objects
println(x == y) // true: structural value equalitygo deeper
Knows Int is written as a class and that arithmetic is normal; may not know the boxing details.
States that nullable/generic uses box to java.lang wrappers while plain Int stays primitive.
Enumerates all boxing triggers, contrasts IntArray vs Array<Int>, and explains the === Integer-cache pitfall.
Reasons about allocation cost in hot paths, API design to avoid boxing, and the value-class/primitive-array tradeoffs at scale.
## Classes in source, primitives in bytecode Kotlin has **no primitive keywords**; you always write `Int`, `Long`, `Double`. These are classes in the `kotlin` package with methods (`toLong()`, `coerceIn(...)`, etc.). But the compiler is free to represent a **non-nullable** numeric type as a JVM primitive: - `Int` -> `int` - `Long` -> `long` - `Double` -> `double` So `val x: Int = 5; val y = x + 1` compiles to primitive `int` arithmetic — zero allocation, no boxing. ## When boxing happens Boxing converts the primitive into its `java.lang.*` wrapper object. It occurs when a **reference type** is needed: 1. **Nullability:** `Int?` can be `null`; primitives cannot, so `Int?` is represented as `java.lang.Integer`. 2. **Generics:** JVM generics erase to `Object`, so type arguments must be reference types. `List<Int>` actually holds `Integer` objects; `Map<String, Long>` holds `Long` wrappers. 3. **Supertype references:** assigning to `Any`, `Number`, or `Comparable<Int>` boxes the value. 4. **Arrays:** `Array<Int>` is `Integer[]` (boxed), while `IntArray` is `int[]` (primitive, no boxing). ```kotlin val a: Int = 1 // primitive int val b: Int? = 1 // boxed Integer (nullable) val list: List<Int> = listOf(1, 2) // boxed Integers val prim = IntArray(3) // int[] -- no boxing val boxed = Array(3) { 0 } // Integer[] -- boxed ``` ## Identity vs equality of boxed numbers The JVM caches small `Integer` instances in `-128..127` (Integer cache). So `===` (referential identity) on boxed values can surprise you: ```kotlin val x: Int? = 127 val y: Int? = 127 println(x === y) // true (cached) val p: Int? = 1000 val q: Int? = 1000 println(p === q) // false (separate boxes) println(p == q) // true (structural equality) ``` **Rule:** use `==` for numeric value comparison; reserve `===` for genuine identity checks. Non-nullable `Int` `===` is fine because it compares primitives. ## Performance implications Boxing allocates objects and adds indirection. Hot loops over `List<Int>` box/unbox repeatedly; prefer `IntArray`/`LongArray` or sequences of primitives, or the unsigned/primitive specializations, when this matters. The compiler does not auto-specialize generic collections. ## Summary table - Non-null local/field arithmetic: primitive, no box. - `Int?`, `List<Int>`, `Any`, `Number`, `Array<Int>`: boxed. - `IntArray`/`LongArray`/`DoubleArray`: primitive arrays, no box.
- Why can `x === y` be false for two `Int?` holding 1000 but true for 127?The JVM caches Integer objects for -128..127; values outside that range create distinct boxes, so === (identity) differs while == (value) stays true.
- How do you avoid boxing in a numeric-heavy loop?Use primitive arrays like IntArray/LongArray instead of List<Int>, which keeps values as JVM primitives.
A primitive Int is cash in your pocket; boxing wraps it in an envelope (an object) so it can be put in a box that only accepts envelopes — like a List or a nullable slot.
saying these in an interview costs you the question
- Saying Int is a primitive keyword like Java's int
- Claiming Int is always boxed or never boxed
- Using === to compare numeric values
- Thinking List<Int> stores primitive ints
- Believing Array<Int> and IntArray are the same on the JVM