Explain how Kotlin decides whether a numeric literal is Int or Long, and how that affects an expression like `val ms = 60 * 60 * 1000 * 24 * 365`.
answer
- literal fits 32-bit -> Int, else Long
- Int*Int = Int, can wrap silently
- promote early: 60L at the front
- left-associative evaluation
- Math.multiplyExact to catch overflow
basics
~20 sAn unsuffixed integer literal is Int if it fits in 32 bits, otherwise Long. If every operand in a multiplication is Int, the math is done in Int and can overflow silently. Add an L to one operand to force Long math.
solid answer
~50 sKotlin infers an unsuffixed integer literal as `Int` when its value fits in the 32-bit signed range, and as `Long` only when it exceeds `Int.MAX_VALUE` (2,147,483,647). Crucially, inference is per-literal: each of `60`, `60`, `1000`, `24`, `365` is an `Int`, so `60 * 60 * 1000 * 24 * 365` is computed entirely in `Int` arithmetic. The true product (~31.5 billion) overflows the 32-bit range and wraps silently — no exception, just a wrong value. The fix is to make one operand a `Long` so the whole expression promotes to `Long`: `60L * 60 * 1000 * 24 * 365`. The position matters less than presence: once one `Long` appears, subsequent multiplications use `Long`. Putting `L` on the *last* operand would be too late — the earlier `Int` multiplications already overflowed. This is a classic interview trap about overflow and evaluation order.
code
kotlin · 4 linesval wrong = 60 * 60 * 1000 * 24 * 365 // Int overflow -> 1471228928
val right = 60L * 60 * 1000 * 24 * 365 // Long -> 31536000000
println(wrong)
println(right)go deeper
Recognizes the result is wrong and that adding L helps, even if fuzzy on why.
Explains per-literal Int inference, silent wraparound, and putting L early to keep Long arithmetic.
Reasons precisely about left-associative evaluation and why a trailing L is too late, plus Math.*Exact.
Discusses defensive coding (typed constants, units, exact ops) and how such bugs slip through tests and reviews at scale.
## Per-literal inference rule Kotlin types each integer literal independently: - Fits in signed 32-bit (`-2_147_483_648..2_147_483_647`) -> **`Int`**. - Exceeds that range -> **`Long`** automatically. - An explicit `L` suffix always forces **`Long`**. ```kotlin val a = 2_000_000_000 // Int (fits) val b = 3_000_000_000 // Long (too big for Int) val c = 5L // Long (forced) ``` ## Why the expression overflows ```kotlin val ms = 60 * 60 * 1000 * 24 * 365 ``` Every factor is a small number that fits in `Int`, so **every literal is `Int`**, and `Int * Int` yields `Int`. The mathematically correct product is `31_536_000_000`, far beyond `Int.MAX_VALUE`. Kotlin (like the JVM) performs **two's-complement wraparound** — it silently truncates to 32 bits, giving a wrong result with **no exception**. ## Evaluation order matters for the fix Multiplication is left-associative, so it evaluates as `(((60 * 60) * 1000) * 24) * 365`. To stay in `Long`, the `Long`-ness must enter **early**: ```kotlin val good = 60L * 60 * 1000 * 24 * 365 // Long throughout -> 31536000000 val bad = 60 * 60 * 1000 * 24 * 365L // first 4 multiplies already overflowed in Int ``` In `bad`, by the time the `365L` is reached the `Int` sub-product is already wrong; promoting an already-overflowed value to `Long` cannot recover the lost bits. ## Mixed-type arithmetic promotion When operands differ, Kotlin promotes to the wider type for the operation: `Int * Long -> Long`, `Int + Double -> Double`. There is **no implicit narrowing**. So a single `Long` operand pulls the running result into `Long`. ## Detecting overflow Kotlin's normal `*`/`+` wrap silently. Use `Math.multiplyExact` (JVM) or `Math.addExact` if you want an exception on overflow, or simply pick `Long` up front. ```kotlin val safe = Math.multiplyExact(60, 60) // throws on overflow instead of wrapping ```
- Does putting the L on the last operand fix the overflow?No. The earlier Int*Int multiplications already overflowed before the Long operand is reached; the L must appear early (e.g., first factor).
- How can you make overflow throw instead of wrapping?Use the JVM's Math.multiplyExact / Math.addExact, which throw ArithmeticException on overflow.
It's like pouring into a measuring cup chosen before you start: if you grab the 32-bit cup, it overflows midway — switching to a bigger cup at the end doesn't un-spill what already overflowed.
saying these in an interview costs you the question
- Saying Int overflow throws an exception
- Believing the literal's type depends on the variable's declared type rather than its value
- Thinking any single L anywhere fixes the math regardless of position
- Claiming Kotlin auto-widens Int to Long mid-expression to avoid overflow
- Ignoring left-associativity when reasoning about the fix