Kotlin removed implicit numeric widening that Java has. Walk through the design rationale and the tradeoffs this creates for everyday code.
answer
- No subtyping, no implicit conversion
- Tradeoff: explicitness vs verbosity
- Literals + overloaded operators absorb common cases
- You own overflow/narrowing
- Java widening enabled silent precision loss
basics
~20 sKotlin makes conversions explicit so number-type changes are visible and you don't get surprising silent ones. The cost is more .toLong() calls, but Kotlin softens that with smart literals and overloaded operators so normal code rarely suffers.
solid answer
~50 sJava's implicit widening (`int`→`long`→`float`→`double`) is convenient but enables silent precision-loss chains and ambiguous overload resolution. Kotlin's principle: **numeric types are not subtypes of each other, and there is no implicit conversion** — every change of representation is an explicit `toX()` call, so it's visible at the call site. The tradeoff is verbosity, which Kotlin mitigates two ways: (1) **integer literals adapt to expected type** (`val l: Long = 1`), so constants never need conversion; (2) **operators are overloaded per type pair**, so mixed arithmetic (`1 + 2L`) just works and returns the wider type without a cast. What you give up: drop-in Java-style numeric code, and you must think about overflow/narrowing explicitly. The net effect is fewer surprising bugs (accidental int division, silent widening into a wrong overload) at the cost of occasional `toLong()` noise — generally judged a good trade for a safety-first language.
code
kotlin · 11 lines// Strict rule
val count: Int = 10
// val total: Long = count // ERROR
val total: Long = count.toLong() * 1_000_000L
// Mitigations
val pageSize: Long = 50 // literal adapts to Long
val mixed = 1 + 2L // Long via overloaded operator
// Author owns overflow
val safe = Int.MAX_VALUE.toLong() + 1 // convert operand firstgo deeper
States that Kotlin makes conversions explicit unlike Java and that you call .toLong().
Names the no-subtyping rule and that literals/operators reduce the verbosity cost.
Articulates the readability and overload-resolution benefits and the overflow/narrowing responsibility shift.
Weighs the full safety-vs-ergonomics tradeoff against Java, explains how literal typing and operator overloading preserve usability, and identifies remaining costs (migration, overflow ownership).
## The Java baseline Java performs **implicit widening primitive conversions**: `byte`→`short`→`int`→`long`→`float`→`double`. This is ergonomic but has well-known hazards: - **Silent precision loss**: `long` widened to `float` loses precision without warning. - **Overload-resolution surprises**: implicit widening can pick an unexpected overload. - **Hidden cost** in mixed expressions. ## Kotlin's rule and rationale Kotlin states: smaller numeric types are **not subtypes** of larger ones, and there are **no implicit numeric conversions**. Rationale: 1. **Explicitness/readability** — a representation change is a visible `toLong()`/`toDouble()` at the call site, aiding code review. 2. **Predictable overload resolution** — without implicit numeric coercion, the compiler doesn't silently choose a widened overload. 3. **Forces awareness of cost/loss** — narrowing is lossy and widening can lose float precision; making it explicit nudges authors to think. ## The ergonomic mitigations (so it isn't painful) Kotlin removes the rough edges so the strict rule rarely hurts: - **Literal typing by context**: an integer literal is typed to its expected type, so `val l: Long = 1`, `fun f(x: Long); f(1)` compile with no `.toLong()`. - **Overloaded operators**: `Int.plus(Long): Long`, etc., make `1 + 2L` produce a `Long` and `3 + 0.5` produce a `Double` without casts — the wider operand wins. - **Conversion family** is uniform and discoverable: `toByte/toShort/toInt/toLong/toFloat/toDouble`, plus `Char.code`/`Char(...)`/`digitToInt()`. ## Tradeoffs you accept - **Verbosity** when bridging real variables of different types (`store.id.toLong()`), especially against Java APIs. - **You own overflow/narrowing**: `Int.MAX_VALUE + 1` still overflows silently (operators don't auto-widen the *result* type), so correctness is the author's responsibility — convert operands before arithmetic. - **Migration friction** porting Java numeric code. ## Judgment The design favors **safety and explicitness** over Java's convenience. Because literals and operators absorb the common cases, the verbosity tax falls mostly on genuine cross-type variable conversions — a deliberate, generally well-regarded tradeoff consistent with Kotlin's null-safety and immutability defaults. ```kotlin // Java would silently widen; Kotlin makes you choose: val count: Int = 10 val total: Long = count.toLong() * 1_000_000L // explicit, no silent loss val pageSize: Long = 50 // literal adapts, no .toLong() ```
- If conversions are explicit, why does `val l: Long = 1` not need `.toLong()`?Because `1` is an integer literal, not a typed variable. Literals are typed by the expected context, so the compiler emits it as a Long constant — no runtime conversion of a value of another type occurs.
- Does the no-implicit-widening rule protect you from integer overflow?No. Overflow is orthogonal: `Int + Int` stays Int and wraps silently. You must convert an operand to a wider type before the arithmetic if the result could exceed Int range.
Java auto-translates between number 'languages' behind your back; Kotlin makes you write the translation, but gives you a phrasebook (literals + operators) so you rarely notice.
saying these in an interview costs you the question
- Claiming the rule also prevents overflow
- Saying the verbosity has no mitigation
- Not knowing literals are typed by context
- Asserting Kotlin keeps Java's implicit widening