skip to content

What do the L, f, and d suffixes do on numeric literals, and why can the L suffix actually change program behavior?

level: middleimportance: must knowfreq 60%

answer

  1. int and double are the defaults
  2. L=long, f=float, d=double
  3. Use uppercase L (l looks like 1)
  4. Expression type comes from operands, not the target
  5. 1000L * ... avoids int overflow

basics

~20 s

L makes an integer literal a long, f makes a number a float, and d makes it a double. Without L, big literals stay int and can overflow during arithmetic; adding L forces 64-bit math.

solid answer

~50 s

By default an integer literal is an int (32-bit) and a floating-point literal is a double (64-bit). Suffixes override or pin the type: L (or l) makes an integer literal a long; f or F makes a literal a float; d or D makes it an explicit double. The L suffix matters because arithmetic on int literals happens in 32-bit and can silently overflow before any long variable is involved. For example long ms = 1000 * 60 * 60 * 24 * 365; overflows because the whole product is computed as int; writing 1000L * 60 * ... promotes the expression to long arithmetic and yields the correct value. Use uppercase L (lowercase l looks like the digit 1). The f suffix is required when assigning a fractional literal to a float, since 3.14 alone is a double and won't narrow implicitly.

code

java · 10 lines
java
// Overflow because all operands are int — computed in 32-bit BEFORE storing in long:
long wrongMs = 1000 * 60 * 60 * 24 * 365;   // -> 1471228928 (overflowed!)

// Fix: one L promotes the whole expression to long (64-bit) arithmetic:
long rightMs = 1000L * 60 * 60 * 24 * 365;  // -> 31536000000 (correct)

float pi = 3.14f;     // f required: 3.14 alone is a double, can't implicitly narrow
// float bad = 3.14;  // COMPILE ERROR: possible lossy conversion double -> float

long id = 5_000_000_000L;  // needed: too big for int, L makes it a long literal

go deeper

for a junior

Know that L means long, f means float, d means double, and that float assignments usually need an f.

for a middle

Explain the default int/double types and demonstrate the int-overflow-before-assignment bug and the 1000L fix.

for a senior

Articulate that expression type derives from operand types with numeric promotion, and connect this to real defects (time/byte arithmetic) and review heuristics.

for a principal

Discuss the JLS binary numeric promotion rules, the readability/safety conventions (uppercase L, underscores, suffix discipline), and how to systematically prevent overflow classes via types or Math.multiplyExact.

## Default types of numeric literals Every literal has a **type** — the kind and size of value it represents. Java picks defaults: - An **integer literal** (a whole number with no decimal point) defaults to **`int`**, a 32-bit signed integer that can hold roughly -2.1 billion to +2.1 billion. - A **floating-point literal** (has a decimal point or an exponent) defaults to **`double`**, a 64-bit value. ## What each suffix does - **`L` / `l`** -> makes an integer literal a **`long`** (64-bit signed integer, range about +/-9.2 quintillion). Example: `9000000000L`. Always prefer the **uppercase `L`**, because the lowercase `l` is easily confused with the digit `1` (`1l` vs `11`). - **`f` / `F`** -> makes the literal a **`float`** (32-bit floating point). Required to assign a fractional literal to a `float` variable: `float pi = 3.14f;` (`3.14` alone is a `double`, and Java will not implicitly narrow a `double` to a `float`). - **`d` / `D`** -> makes the literal an explicit **`double`**. Rarely needed because `double` is already the default, but it documents intent: `double x = 5d;` is a double, whereas `5` is an int. There are no suffixes for `byte`, `short`, or `char`; those types are reached by assignment (with an implicit narrowing of a constant that fits) or by a cast. ## Why the `L` suffix can change behavior — integer overflow This is the load-bearing point. In Java, the **type of an arithmetic expression is decided by its operands, not by where the result is stored.** If every operand is an `int`, the whole calculation runs in 32-bit `int` arithmetic, and if the intermediate value exceeds the `int` range it **overflows** (wraps around) — *before* it is ever assigned to a `long`. ```java long ms = 1000 * 60 * 60 * 24 * 365; // BUG ``` Here `1000`, `60`, etc. are all `int` literals, so the product is computed in `int`. The true value (31,536,000,000) exceeds `Integer.MAX_VALUE` (2,147,483,647), so it overflows to a wrong, smaller number, and *then* that wrong int is widened to `long`. The fix is to make at least the first operand a `long` so the whole expression is evaluated in 64-bit: ```java long ms = 1000L * 60 * 60 * 24 * 365; // correct (one L promotes the rest) ``` Once one operand is `long`, the `int` operands are **promoted** to `long` and the arithmetic is 64-bit, so no overflow. ## Why `f` is often mandatory, not optional Java allows *widening* a `double`? No — `double` is wider than `float`, so going from a `double` literal to a `float` is a **narrowing** conversion, which Java refuses to do implicitly because it can lose precision. Hence `float r = 1.5;` is a compile error and you must write `float r = 1.5f;` (or cast). The `d` suffix is the mirror image and is mostly for explicitness. ## Practical rules 1. Add `L` to any integer literal that participates in arithmetic whose result could exceed ~2.1 billion (time in milliseconds, byte counts, IDs). 2. Use uppercase `L`, never lowercase `l`. 3. Use `f` whenever you assign a fractional constant to a `float`. 4. Reach for `d` only to make an integer-looking literal a double for clarity.

  • Why does long total = 1_000_000 * 1_000_000; produce the wrong answer?
    Both literals are int, so the multiplication is done in 32-bit int arithmetic; the true result (10^12) overflows the int range and wraps to a wrong value, which is only then widened to long. Writing 1_000_000L * 1_000_000 evaluates the product in long and gives the right answer.
  • Do you ever need the d suffix?
    Not for type-correctness in most cases since double is already the default for fractional literals. It is useful to force an integer-looking literal to be a double (e.g. 5d, or 1/2d to get 0.5 instead of integer-division 0) and to document intent.

saying these in an interview costs you the question

  • Thinking the target variable's type (long) controls the arithmetic — it does not
  • Assigning 3.14 to a float without f and expecting it to compile
  • Using lowercase l for long
  • Believing d is never useful (it pins int-looking literals to double)

context