skip to content

Explain widening and narrowing conversions between primitive types and when casts are required.

level: middleimportance: should knowfreq 50%

answer

  1. Ladder: byte->short->int->long->float->double
  2. Widening (up) = automatic; Narrowing (down) = explicit cast
  3. Narrowing int->byte truncates low bits; double->int drops fraction
  4. int/long -> float can lose PRECISION though widening
  5. Constant that fits a narrower type needs no cast (byte b = 100)

basics

~20 s

Widening (small to big, like int to long) happens automatically because no data is lost. Narrowing (big to small, like long to int) can lose data, so Java forces you to write an explicit cast.

solid answer

~50 s

Java orders the numeric primitives by capacity: byte -> short -> int -> long -> float -> double, with char sitting at the 16-bit unsigned level promoting to int. A **widening** conversion moves up this chain (e.g. int to long, int to double) and is **implicit/automatic** because the larger type can hold any value of the smaller. A **narrowing** conversion moves down (e.g. long to int, double to int, int to byte) and **requires an explicit cast** because it can lose information — high bits are truncated for integers, and double-to-int drops the fraction and clamps out-of-range values. Two subtleties: int-to-float and long-to-float/double are widening yet may lose *precision* (not range) because floats have fewer significant bits; and compile-time constant expressions that fit a narrower type (e.g. `byte b = 100;`) are allowed without a cast via implicit narrowing of constants.

code

java · 12 lines
java
int i = 70000;
long l = i;            // widening, implicit
double d = i;          // widening, implicit

byte b = (byte) i;     // narrowing, explicit; 70000 truncated low bits
System.out.println(b); // 112

int t = (int) 9.99;    // narrowing float->int; fraction dropped
System.out.println(t); // 9

byte ok = 100;         // constant fits -> implicit narrowing allowed
// byte bad = 200;     // compile error: 200 out of byte range

go deeper

for a junior

Knows widening (int to long/double) is automatic and narrowing (double to int) needs a cast that can lose data.

for a middle

Explains the capacity ladder, truncation behavior of narrowing, and the constant-fits exception.

for a senior

Distinguishes range loss vs precision loss (int/long to float), describes double-to-int truncation/clamping/NaN rules, and the design rationale of explicit casts.

for a principal

Sets numeric-conversion conventions to prevent silent truncation bugs and reasons about precision loss in data pipelines and serialization boundaries.

## The capacity ladder The numeric primitives form a rough ordering by how much they can represent: ``` byte (8) -> short (16) -> int (32) -> long (64) -> float (32) -> double (64) ``` `char` (16-bit unsigned) sits beside short but, being unsigned, widens directly to `int`. Note float/double appear *after* long even though float is only 32 bits — that is because floating-point types can represent a far larger *range* (via the exponent), just with limited precision. ## Widening: automatic, safe for range A **widening primitive conversion** goes *up* the ladder — a smaller-capacity type to a larger one. Java does it **automatically (implicitly)** because the target can represent every value of the source's range: ```java int i = 100; long l = i; // int -> long, implicit double d = i; // int -> double, implicit ``` **Caveat — precision vs range.** Widening preserves *range* but not always *precision*. A `float` has only ~24 bits of significand, so converting a large `int` or `long` to `float` (or a large `long` to `double`) can lose low-order digits even though no exception occurs. So `int -> float` and `long -> float`/`double` are widening but *lossy* in precision. ## Narrowing: explicit cast required, may lose data A **narrowing primitive conversion** goes *down* the ladder — a larger type to a smaller one. Because the target may not hold the value, Java **requires an explicit cast** `(type)` so the loss is intentional: ```java long big = 300; int i = (int) big; // ok, fits byte b = (byte) 300; // 300 doesn't fit in byte -> truncated to 44 double pi = 3.99; int t = (int) pi; // 3 (fraction discarded, toward zero) ``` What narrowing does mechanically: - **Integer to smaller integer:** keeps only the low-order bits (truncation), reinterpreted in the smaller type's two's-complement — hence `(byte)300 == 44`. - **Floating-point to integer:** discards the fractional part (rounds toward zero); values beyond the integer's range clamp to MIN/MAX, and NaN becomes 0. Forgetting the cast is a **compile error**, which is the language nudging you to confirm you accept possible loss. ## The constant-expression exception There is one convenience: if you assign a **compile-time constant** that *fits* the target, implicit narrowing is allowed for `byte`, `short`, `char`, and `int` assignments — no cast needed: ```java byte b = 100; // ok: 100 fits in byte, compiler narrows the constant byte c = 200; // ERROR: 200 does not fit in byte int x = 5; byte d = x; // ERROR: x is a variable, not a constant -> needs (byte) cast ``` ## Why the rules are shaped this way The design principle is: **silent only when provably safe**. Widening (up the ladder) cannot lose range, so it is implicit. Narrowing can lose data, so the language forces an explicit, visible cast — making the risk obvious in the source and preventing accidental truncation bugs.

  • Does double-to-int rounding round to nearest?
    No — it truncates toward zero, discarding the fraction, so (int)3.99 is 3 and (int)-3.99 is -3. Out-of-range values clamp to Integer.MIN/MAX, and NaN becomes 0.
  • Why can int-to-float lose data if float is 'wider'?
    float has more range (via its exponent) but only ~24 significand bits, fewer than int's 31 magnitude bits, so large int values can't be represented exactly and are rounded.

saying these in an interview costs you the question

  • Thinking widening never loses anything (large int/long -> float loses precision)
  • Believing narrowing rounds (it truncates toward zero / keeps low bits)
  • Expecting a runtime exception on overflowing narrowing cast (it silently truncates/clamps)
  • Forgetting that a variable, unlike a constant, always needs an explicit narrowing cast

context