As a system grows, how do you decide between int, long, BigInteger/BigDecimal, and checked arithmetic to manage overflow risk?
answer
- Trade-off: range × exactness × performance × blast radius
- int small/bounded; long for growing IDs/counts/time
- BigInteger unbounded; BigDecimal money/exact
- Math.*Exact at trust boundaries
- Process: large-input/property tests, static analysis, vetted libs
basics
~20 sMatch the type to the value range and the cost of being wrong: int for small bounded counts, long for big counts/IDs/time, BigInteger/BigDecimal when values are unbounded or must be exact (money), and Math.*Exact at trust boundaries to fail loudly instead of wrapping.
solid answer
~50 sThere's no single answer; you trade range, exactness, performance, and blast radius. Use **int** for clearly bounded small quantities in hot paths. Move to **long** for things that grow — IDs, sequence numbers, byte counts, epoch time, accumulators — where int's ~2.1 billion ceiling is plausibly exceeded; it's nearly free on 64-bit. Use **BigInteger** when values are genuinely unbounded (cryptography, combinatorics) and **BigDecimal** for money or any value needing exact decimal arithmetic, accepting object allocation and slower math. Layer **Math.addExact/multiplyExact/toIntExact** at trust boundaries and sizing/financial math so an unexpected overflow throws instead of silently corrupting data. Beyond type choice, the real leverage is process: review additive size/index math for overflow, add large-input and property-based tests, run static analysis, and prefer well-tested library routines. Treat overflow as a class of silent correctness/security defects and design defenses, not as a per-line afterthought.
code
java · 12 lines// int: small, bounded, hot path
int retries = 3;
// long: growing identifiers / time
long eventId = sequence.next();
long nowMillis = System.currentTimeMillis();
// BigDecimal: exact money
BigDecimal price = new BigDecimal("19.99");
// checked arithmetic at a trust boundary
long totalBytes = Math.multiplyExact((long) count, elementSize); // throws on overflowgo deeper
Knows the basic menu: int for small numbers, long for big ones, BigDecimal for money.
Maps each type to typical uses (long for IDs/time, BigInteger for unbounded, BigDecimal for exact decimals) and knows *Exact throws on overflow.
Reasons about the range/exactness/performance trade-offs per value, applies checked arithmetic at boundaries, and avoids the float-for-money and 'long is safe' traps.
Sets organization-wide numeric policy, treats overflow as a defect class, and invests in property/large-input testing, static analysis, boundary validation, and vetted libraries — not just per-line type choices.
## Framing: it's a risk-management decision Overflow management isn't about memorizing one rule; it's choosing, per value, the right point on a trade-off surface with axes of **range** (how big can it get?), **exactness** (must it be precise — e.g. money?), **performance** (hot path or not?), and **blast radius** (how bad is a silently wrong value — cosmetic, data corruption, or a security hole?). Recall the two failure modes: integer types **wrap silently** (modulo 2^N two's-complement), and floating-point **overflows to ±Infinity / NaN** silently. Neither throws on its own, so the defaults are dangerous exactly where it matters. ## The toolbox **`int` (32-bit, ±~2.1 billion).** The default for small, clearly bounded counts and indices, and for hot loops. Cheapest and most cache-friendly. Risk: anything that can plausibly grow past ~2.1e9. **`long` (64-bit, ±~9.2 quintillion).** The pragmatic upgrade for values that grow over a system's life: database IDs, sequence/version numbers, byte/row counts, monetary *minor units* (cents) if you stay integer, accumulators, and **time** (epoch millis/nanos, durations). On 64-bit JVMs it's essentially as fast as int. Still wraps, but the ceiling is rarely reached in practice — and you can guard the rare case with `*Exact`. **Rule of thumb: if a counter or identifier outlives a single request and accumulates, default to long.** **`BigInteger`.** Arbitrary-precision integers — *cannot* overflow. Use when the magnitude is genuinely unbounded or untrusted-large: cryptography, big combinatorics, factorials, accumulating across an open-ended dataset. Cost: each value is a heap object; arithmetic is many times slower and allocates. Don't use it as a lazy substitute for thinking about range in hot paths. **`BigDecimal`.** Arbitrary-precision *decimal* with controllable rounding. The correct choice for **money** and any value where binary floating-point's inexactness (0.1 + 0.2 != 0.3) is unacceptable. It sidesteps float overflow/NaN entirely but is slower and verbose; you must set scale/RoundingMode explicitly. **Checked arithmetic (`Math.*Exact`).** Orthogonal to the type choice: stay in int/long but **throw ArithmeticException on overflow** instead of wrapping. The right tool at **trust boundaries** (untrusted input), **sizing/allocation math**, and **financial counters** — anywhere a silent wrong value is unacceptable but you don't want BigInteger's cost. **Floating-point with guards.** `double` is fine for scientific/statistical work where small relative error is acceptable; guard outputs with `Double.isNaN`/`isInfinite` at boundaries, and never use it for money. ## A decision heuristic 1. **Bounded and small, hot path?** → `int`. 2. **Grows/accumulates/identifier/time?** → `long` (guard the rare overflow with `*Exact`). 3. **Genuinely unbounded magnitude?** → `BigInteger`. 4. **Must be exact decimal (money)?** → `BigDecimal` (or integer minor units in `long`). 5. **Untrusted input or sizing/financial math?** → wrap the arithmetic in `Math.*Exact` regardless of the type. 6. **Scientific/approximate?** → `double` with NaN/Infinity guards. ## Beyond the type: the engineering process At scale the durable wins are organizational, not per-line: - **Review additive size/index/duration math** for overflow (the `(a+b)/2` and `count*size` shapes). - **Test the failure mode**: large-input tests, boundary tests at MAX_VALUE/MIN_VALUE, and **property-based/fuzz** testing that drives values toward the limits. - **Static analysis** (SpotBugs/Error Prone) to flag risky narrowing and overflow-prone patterns. - **Prefer vetted library routines** (Arrays.binarySearch, Guava IntMath/LongMath's checked ops) over hand-rolled arithmetic. - **Defense in depth at boundaries**: validate and bound untrusted numeric input before it reaches arithmetic, and convert with `Math.toIntExact` when narrowing. ## Anti-patterns to avoid - Reflexively using `BigInteger`/`BigDecimal` everywhere "to be safe" — needless allocation and slowness; choose by need. - Sprinkling `*Exact` on every operation including proven-safe hot loops. - Using `double` for currency. - Assuming `long` is overflow-proof — it's just higher-ceiling; size/time products can still overflow. ## Key takeaway Decide per value by range × exactness × performance × blast radius: `int` small/bounded, `long` for growing IDs/counts/time, `BigInteger`/`BigDecimal` for unbounded/exact, and `Math.*Exact` to fail loudly at trust boundaries — backed by tests, static analysis, and reviews that treat overflow as a defect class.
- Why not just use BigInteger everywhere to eliminate overflow risk?BigInteger values are heap objects with much slower, allocating arithmetic; using it for bounded hot-path counts wastes memory and CPU. Choose by actual range/exactness need, not blanket caution.
- Where does checked arithmetic (Math.*Exact) fit relative to choosing a wider type?It's orthogonal: you still pick int/long for range/performance, then wrap risky operations (untrusted input, sizing, money) in *Exact so an unexpected overflow throws instead of silently corrupting data.
saying these in an interview costs you the question
- Treating long as overflow-proof rather than just higher-ceiling
- Using double/float for money instead of BigDecimal
- Reaching for BigInteger/BigDecimal everywhere by default, ignoring allocation/perf cost
- Believing type choice alone solves it without tests, reviews, or boundary validation
- Putting *Exact on every operation regardless of proven safety