skip to content

Explicit Conversions & No Implicit Widening

Kotlin refuses to widen numbers for you: an Int is not assignable to a Long without toLong(). Interviewers ask because the rule surprises Java developers, and because it removes a whole class of silent precision and overflow bugs.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

In Kotlin, why does `val l: Long = anInt` fail to compile, and how do you fix it?

level: juniorimportance: must knowfreq 75%

answer

  1. No implicit widening — Int is not a Long
  2. Fix with .toLong()/.toDouble()
  3. Literals adapt to expected type
  4. Operators are overloaded, not widening
  5. Narrowing truncates silently

basics

~10 s

Kotlin never auto-converts a smaller number type to a bigger one. An Int is not a Long. You must convert it yourself by calling a conversion function, for example anInt.toLong().

solid answer

~40 s

Kotlin has no implicit numeric widening: smaller numeric types are not subtypes of wider ones, so assigning an `Int` where a `Long` is expected is a type error, even though every Int value fits in a Long. You fix it with an explicit conversion call: `val l: Long = anInt.toLong()`. The standard library defines `toByte()`, `toShort()`, `toInt()`, `toLong()`, `toFloat()`, `toDouble()`, and `toChar()` on every numeric type. The design avoids the surprising silent conversions Java allows. Note literals are different: `val l: Long = 1` works because the integer literal `1` is typed by its expected context. Also, arithmetic operators like `+` are overloaded across types, so `anInt + 1L` yields a `Long` automatically — that's operator overloading, not implicit widening of a variable.

code

kotlin · 6 lines
kotlin
val anInt: Int = 42
// val l: Long = anInt          // compile error: type mismatch
val l: Long = anInt.toLong()    // explicit conversion
val d: Double = anInt.toDouble()
val back: Int = l.toInt()       // narrowing truncates if out of range
val litLong: Long = 1           // OK: literal adapts to Long

go deeper

for a junior

Knows you must call .toLong() and that a smaller type isn't auto-assignable to a wider one.

for a middle

Explains the no-subtyping rationale and distinguishes literal adaptation from variable widening.

for a senior

Adds operator-overloading behavior for mixed arithmetic and the silent-truncation risk of narrowing.

for a principal

Frames the design tradeoff (safety vs. ergonomics) versus Java and how literal typing keeps the rule ergonomic without breaking it.

## The rule Kotlin has **no implicit widening conversions** between numeric types. In Java, `long l = anInt;` compiles because Java silently widens `int` to `long`. Kotlin rejects the equivalent `val l: Long = anInt` with a type-mismatch error. ## Why In Kotlin the numeric types `Byte`, `Short`, `Int`, `Long`, `Float`, `Double` are **not in a subtype relationship** — `Int` is not a subtype of `Long`. Because there's no subtyping and no implicit conversion, the assignment is simply a type mismatch. The language designers chose this to avoid the silent, sometimes surprising conversions that Java permits (e.g. accidental `int`→`long`→`float` precision loss chains). ## The fix — explicit conversion functions Every numeric type exposes conversion methods: - `toByte()`, `toShort()`, `toInt()`, `toLong()` — integer targets - `toFloat()`, `toDouble()` — floating targets - `toChar()` and `Char.code` for char/int interplay ```kotlin val anInt: Int = 42 // val l: Long = anInt // ERROR: type mismatch val l: Long = anInt.toLong() // OK ``` ## Important exceptions / nuances - **Literals adapt to context.** `val l: Long = 1` compiles: the integer literal `1` is given the expected type `Long` at compile time. There's no variable being widened — the constant is simply typed as `Long`. - **Arithmetic is overloaded, not widening.** `anInt + 1L` returns a `Long` because `Int.plus(Long): Long` is an overloaded operator. The mixed-type operators effectively convert the narrower operand, so you rarely write `toLong()` inside expressions. - **Conversion can lose data.** `toByte()`/`toInt()` truncate; `Long.toInt()` keeps only the low 32 bits. Narrowing is silent — no exception — so be deliberate. ## Summary No automatic widening; call `toLong()`/`toDouble()`/etc. explicitly. Literals and overloaded operators are the two places where it *looks* automatic but isn't variable widening.

  • Why does `val l: Long = 1` compile but `val l: Long = anInt` does not?
    `1` is an integer literal whose type is inferred from the expected context (Long), so it's compiled as a Long constant. `anInt` is already a declared `Int` value, and Int is not assignable to Long without `.toLong()`.
  • Does `anInt + 1L` need an explicit conversion?
    No. The `+` operator is overloaded; `Int.plus(Long)` returns a `Long`, so the narrower operand is handled by the operator and the result is `Long`.

Kotlin treats each numeric type like a different currency: you must explicitly exchange dollars for euros, even though dollars are 'smaller'.

saying these in an interview costs you the question

  • Claiming Int is a subtype of Long
  • Saying Kotlin widens int to long automatically like Java
  • Forgetting that narrowing conversions truncate silently
  • Thinking `val l: Long = 1` proves implicit widening of variables

context

open as a page

When you write `1 + 2L` or `3 + 0.5`, no `.toLong()`/`.toDouble()` call appears. How does Kotlin produce the wider result type without implicit widening of variables?

level: middleimportance: should knowfreq 50%

basics

~20 s

Math operators like + come in many versions, one per type combination. Int.plus(Long) returns a Long. So the operator itself handles the mix and gives the wider result — it's not the variable being auto-converted.

open as a page

What does `300.toByte()` return, and what general rule governs narrowing conversions like `toByte()`, `toShort()`, and `Long.toInt()`?

level: middleimportance: should knowfreq 55%

basics

~10 s

Narrowing keeps only the low bits and throws nothing. 300.toByte() is 44 because 300 doesn't fit in a byte, so the extra bits are dropped. Always check ranges before narrowing.

open as a page

How do explicit conversions interact with nullable numeric types and with Char, e.g. converting `Int?` to `Long?` and getting an Int from a Char?

level: seniorimportance: should knowfreq 35%

basics

~20 s

For a nullable number you call the conversion with a safe call: n?.toLong(). For a Char, you don't use toInt() anymore — you use the code property to get its numeric code, and Char(code) to go back.

open as a page

Kotlin removed implicit numeric widening that Java has. Walk through the design rationale and the tradeoffs this creates for everyday code.

level: principalimportance: nice to knowfreq 20%

basics

~20 s

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

open as a page