skip to content

Why is Integer.compare(a, b) safer than writing a - b, and what is the Math.abs(Integer.MIN_VALUE) quirk?

level: seniorimportance: should knowfreq 55%

answer

  1. a - b can overflow -> wrong sign
  2. Integer.compare never subtracts
  3. range is asymmetric (one extra negative)
  4. abs(MIN_VALUE) == MIN_VALUE (negative)
  5. use floorMod / absExact / Comparator.comparingInt

basics

~20 s

Integer.compare(a, b) never overflows, but the old trick a - b can overflow when the difference exceeds int range and then return the wrong sign. Similarly Math.abs(Integer.MIN_VALUE) overflows and stays negative, because MIN_VALUE has no positive counterpart.

solid answer

~40 s

A common comparator shortcut is to return a - b, expecting a negative, zero, or positive result. But int subtraction can overflow: if a is large positive and b is large negative, a - b exceeds Integer.MAX_VALUE, wraps around to a negative number, and the comparator reports the wrong order. Integer.compare(a, b) avoids this by comparing the operands directly (a < b ? -1 : (a == b ? 0 : 1)) without ever subtracting, so it is overflow-safe for all inputs. A related two's-complement quirk: Math.abs(Integer.MIN_VALUE) returns Integer.MIN_VALUE (a negative number), because the range is asymmetric (-2147483648..2147483647) and there is no positive 2147483648 to represent. The same applies to Long.MIN_VALUE. Lessons: use Integer.compare in comparators, and never assume abs of a value is non-negative without guarding the MIN_VALUE edge.

code

java · 8 lines
java
int a = 2_000_000_000, b = -2_000_000_000;
System.out.println(a - b);                 // overflow -> negative, WRONG
System.out.println(Integer.compare(a, b)); // 1, correct (a > b)

System.out.println(Math.abs(Integer.MIN_VALUE)); // -2147483648 (still negative)
// safer:
int bucket = Math.floorMod(Integer.MIN_VALUE, 16); // always >= 0
// Math.absExact(Integer.MIN_VALUE); // throws ArithmeticException

go deeper

for a junior

Knows Integer.compare exists and returns negative/zero/positive for ordering.

for a middle

Explains that a - b can overflow and that Integer.compare is the safe alternative.

for a senior

Reasons about the asymmetric two's-complement range, abs(MIN_VALUE) wrapping, and uses floorMod/absExact and Comparator.comparingInt appropriately.

for a principal

Establishes overflow-safety conventions (Exact math, floorMod, no subtractive comparators), and recognizes the broken-comparator contract failures these bugs cause in sorted collections.

## Two's complement and the asymmetric range A Java `int` is a 32-bit signed integer in **two's complement**. Its range is **-2147483648 (`Integer.MIN_VALUE`) to 2147483647 (`Integer.MAX_VALUE`)**. Notice it is **asymmetric**: there is one more negative value than positive, because zero takes one of the non-negative slots. This single fact drives both problems below. ## Problem 1: the `a - b` comparator trick overflows A comparator must return a negative number if `a` should come before `b`, zero if equal, positive otherwise. People write: ```java Comparator<Integer> bad = (a, b) -> a - b; // BUG ``` It usually works, but `a - b` is computed as an `int` and **silently overflows** (wraps) when the true difference doesn't fit in 32 bits. Example: `a = 2_000_000_000`, `b = -2_000_000_000`. The real difference is 4,000,000,000, which exceeds `MAX_VALUE`; the int result wraps to a **negative** value, so the comparator claims `a < b` — exactly backwards. A broken comparator can corrupt a `TreeMap` or make `Collections.sort` throw 'Comparison method violates its general contract'. ## The fix: Integer.compare `Integer.compare(a, b)` is implemented essentially as: ```java return (a < b) ? -1 : ((a == b) ? 0 : 1); ``` It **never subtracts**, so it cannot overflow, for *any* pair of ints. Use it directly or via `Comparator.comparingInt`: ```java Comparator<Integer> good = Integer::compare; Comparator<Foo> byId = Comparator.comparingInt(Foo::id); // uses compare under the hood ``` The same exists for the other numeric types (`Long.compare`, `Double.compare`, etc.). `Double.compare` additionally handles `NaN` and `-0.0` correctly, which `<`/`==` do not. ## Problem 2: Math.abs(Integer.MIN_VALUE) stays negative `Math.abs(x)` should return the magnitude (always non-negative)... except for one value. Because the range is asymmetric, **`-Integer.MIN_VALUE` cannot be represented**: negating -2147483648 would need +2147483648, which is one past `MAX_VALUE`. So the negation **overflows and wraps back to `Integer.MIN_VALUE`** — a negative result: ```java Math.abs(Integer.MIN_VALUE); // -2147483648 (still negative!) ``` This is documented behavior, not a bug. `Long.MIN_VALUE` behaves the same way. It bites code that assumes `Math.abs(x) >= 0`, e.g. `Math.abs(hash) % buckets` can produce a **negative index** when `hash == MIN_VALUE`, causing `ArrayIndexOutOfBoundsException`. ## How to guard it - Use `Math.absExact(x)` (Java 15+), which **throws** `ArithmeticException` instead of silently wrapping. - Or use `Math.floorMod(hash, buckets)`, which always returns a non-negative result for a positive modulus. - Or mask the sign bit (`hash & Integer.MAX_VALUE`) when you just need a non-negative bucket. ## Takeaways 1. In comparators, use `Integer.compare` / `Comparator.comparingInt`, never `a - b`. 2. Remember the asymmetric two's-complement range: `abs(MIN_VALUE)` is negative; arithmetic on extremes can overflow silently. 3. Prefer the `*Exact` math methods (`addExact`, `multiplyExact`, `absExact`) or `floorMod` when overflow would be a correctness bug.

  • Why does Comparator.comparingInt avoid the overflow bug?
    It delegates to Integer.compare, which compares operands directly with </== rather than subtracting, so no difference is computed and nothing can overflow.
  • How do you compute a non-negative bucket from a possibly-MIN_VALUE hash?
    Use Math.floorMod(hash, n) (always non-negative for positive n), or mask with hash & Integer.MAX_VALUE, instead of Math.abs(hash) % n which can be negative or zero-biased.

The int range is like a clock that wraps: pushing one step past the top (MAX_VALUE) lands you at the bottom (MIN_VALUE). abs(MIN_VALUE) tries to step past the top and wraps right back to where it started.

saying these in an interview costs you the question

  • Using a - b in a comparator
  • Assuming Math.abs always returns a non-negative value
  • Thinking the int range is symmetric
  • Believing Integer.compare can overflow
  • Using Math.abs(hash) % n for bucket selection without guarding MIN_VALUE

context