skip to content

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`.

level: middleimportance: must knowfreq 60%

answer

  1. literal fits 32-bit -> Int, else Long
  2. Int*Int = Int, can wrap silently
  3. promote early: 60L at the front
  4. left-associative evaluation
  5. Math.multiplyExact to catch overflow

basics

~20 s

An 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 s

Kotlin 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 lines
kotlin
val wrong = 60 * 60 * 1000 * 24 * 365      // Int overflow -> 1471228928
val right = 60L * 60 * 1000 * 24 * 365     // Long -> 31536000000
println(wrong)
println(right)

go deeper

for a junior

Recognizes the result is wrong and that adding L helps, even if fuzzy on why.

for a middle

Explains per-literal Int inference, silent wraparound, and putting L early to keep Long arithmetic.

for a senior

Reasons precisely about left-associative evaluation and why a trailing L is too late, plus Math.*Exact.

for a principal

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

context