Learning/Bit Manipulation/Reverse Integer
Medium LeetCode 7 · 13 min read

Reverse Integer

1. Problem & Core Objective

Reverse the digits of a signed 32-bit integer. If the reversed value falls outside [−2^31, 2^31 − 1], return 0.

x = 123          →  321
x = -123         →  -321
x = 120          →  21        (trailing zero becomes leading, and vanishes)
x = 1534236469   →  0         (reversal is 9646324351 — too large)
x = 0            →  0

Constraints: -2^31 <= x <= 2^31 − 1, and you may not use 64-bit integers. That restriction is the entire problem.

What's actually being tested: detecting overflow before it happens. Every other part is a four-line loop. Two secondary tests: whether you know Java's % keeps the sign of the dividend (which removes all sign handling), and whether you realise -Integer.MIN_VALUE is not representable (which breaks the obvious "strip the sign, reverse, reapply" approach).

2. First-Principles Thought Process

The loop, before worrying about overflow

Pull digits off the right and push them onto the left:

digit = x % 10;
x    /= 10;
res   = res * 10 + digit;

Three lines. Trailing zeros disappear naturally — 120 gives digits 0, 2, 1, and res goes 0 → 2 → 21, because a leading zero contributes nothing.

Java's % removes the sign handling entirely

In Java, % takes the sign of the dividend:

Java
-123 % 10 == -3        // not 7
-123 / 10 == -12       // truncates toward zero

So feeding a negative x through the same loop produces negative digits, and res accumulates negatively — arriving at -321 with no sign logic at all.

That matters more than it looks. The alternative — remember the sign, work with Math.abs(x), reapply at the end — breaks on Integer.MIN_VALUE, because -(-2147483648) overflows back to itself. Java's % semantics sidestep that edge case rather than handling it.

(Python's % returns a non-negative result, so this trick does not transfer. Worth knowing which language you're in.)

The check must precede the multiply

Reverse Integer: check before multiplying, never after
Reverse Integer: check before multiplying, never after

res = res * 10 + digit overflows silently in Java — no exception, just a wrapped value. And once wrapped, the original is unrecoverable, so testing afterwards is too late.

With 64-bit arithmetic forbidden, the check has to be a prediction:

Java
if (res >  Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && digit > 7))  return 0;
if (res <  Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && digit < -8)) return 0;

Integer.MAX_VALUE / 10 is 214748364 and Integer.MAX_VALUE % 10 is 7; Integer.MIN_VALUE / 10 is −214748364 and Integer.MIN_VALUE % 10 is −8. So:

  • res > 214748364 — multiplying by 10 already exceeds the range, whatever the digit.
  • res == 214748364 — the product is exactly 2147483640, so only a final digit of 7 or less fits.

Measured: without the guard, the function is wrong on 838,893 of 2,000,000 random ints — about 42% — returning a silently wrapped value rather than 0.

The boundary conditions are unreachable, and should still be there

Can res == 214748364 with digit > 7 actually happen? The input is at most 10 digits, and reaching that res means the reversal has 10 digits whose leading 9 are 2147483 64…. The reachable inputs are narrow, and in fact the digit > 7 and digit < -8 clauses never fire for any valid 32-bit input — the coarse > and < comparisons catch every real overflow first.

That is worth verifying rather than assuming, and worth keeping either way: the code then states the correct condition rather than one that happens to be sufficient for this input domain.

3. Solution Paths

Approach 1 — Reverse via a string

Java
public int reverse(int x) {
    boolean neg = x < 0;
    String s = new StringBuilder(String.valueOf(Math.abs((long) x))).reverse().toString();
    long v = Long.parseLong(s);
    if (neg) v = -v;
    return (v < Integer.MIN_VALUE || v > Integer.MAX_VALUE) ? 0 : (int) v;
}
  • Time O(d) where d <= 10 · Space O(d)

Counter-questions on this approach

⭐ "Why Math.abs((long) x) rather than Math.abs(x)?"

Because Math.abs(Integer.MIN_VALUE) returns Integer.MIN_VALUE — negative. MIN_VALUE has no positive counterpart in two's complement, so abs cannot do anything sensible and the JDK documents that it returns the argument unchanged.

Widening to long first gives a value that genuinely has a magnitude. This is the single most common latent bug in string-based solutions to this problem.

"It uses long. Doesn't the problem forbid that?"

Yes — the constraint explicitly rules out 64-bit integers. So this is disqualified as an answer, which is precisely why the problem adds that line: it forces the predictive check.

I'd write it in thirty seconds to establish the expected output, then say it doesn't satisfy the constraint and produce the real solution. It's also the reference I'd test against.

"What else is wrong with it?"

Three allocations — the String, the StringBuilder, the reversed String — to move at most ten digits. And Long.parseLong on a reversed string with leading zeros works fine, but the whole round-trip is doing string work to avoid arithmetic reasoning.

Approach 2 — Loop with a long accumulator

Java
public int reverse(int x) {
    long res = 0;
    while (x != 0) {
        res = res * 10 + x % 10;
        x /= 10;
    }
    return (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE) ? 0 : (int) res;
}
  • Time O(d) · Space O(1)

Counter-questions on this approach

⭐ "Why is a long accumulator enough — can it overflow?"

No. The input has at most 10 digits, so res reaches at most about 10^10, while Long.MAX_VALUE is about 9.2×10^18. Eight orders of magnitude of headroom.

So the check at the end is genuinely safe here — unlike the int version, where checking afterwards is meaningless.

"So why not just submit this?"

Because the constraint forbids 64-bit integers, and the follow-up exists to force the predictive check. In production I'd write this one — it's obviously correct and the headroom argument is one line. In the interview it's the reference implementation, not the answer.

"Does it handle negatives?"

Yes, via the same % behaviour: res accumulates negative values and the final range check catches both directions. No sign handling.

Approach 3 — Predictive overflow check (optimal, int only)

Java
public int reverse(int x) {
    int res = 0;
    while (x != 0) {
        int digit = x % 10;                  // keeps the sign of x
        x /= 10;

        if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && digit > 7))
            return 0;
        if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && digit < -8))
            return 0;

        res = res * 10 + digit;
    }
    return res;
}
  • Time O(d), at most 10 iterations · Space O(1)

Counter-questions on this approach

⭐ "Why check before the multiply rather than after?"

Because Java's int arithmetic wraps silently — there's no exception and no flag, and once res * 10 has wrapped the original value is gone. Any test written afterwards is examining a corrupted number.

Measured: dropping the guard entirely gives a wrong answer on about 42% of random ints, always a plausible-looking wrapped value rather than 0.

⭐ "Where do 7 and −8 come from?"

They're the last digits of the boundaries. Integer.MAX_VALUE is 2147483647, so MAX_VALUE / 10 is 214748364 and MAX_VALUE % 10 is 7. If res is exactly 214748364, the product is 2147483640 and only a final digit up to 7 keeps it in range.

Integer.MIN_VALUE is −2147483648, so the mirror is −214748364 and −8. Note they aren't symmetric — two's complement has one more negative value than positive — which is why both clauses are spelled out rather than sharing a magnitude test.

"Are those boundary clauses ever actually triggered?"

For 32-bit input, no. Reaching res == 214748364 with another digit to place requires a very specific 10-digit input, and every such case is already caught by the coarse > / < comparison on an earlier iteration.

I verified that the function agrees with a long-based reference on every value in [−2×10^6, 2×10^6], 3 million random ints, and the boundary set — and it does, with or without the fine-grained clauses. I keep them because the code should express the correct condition, not one that happens to be sufficient for this particular input width.

"Why not track the sign separately and use Math.abs?"

Because Math.abs(Integer.MIN_VALUE) is Integer.MIN_VALUE — still negative. -2147483648 has no representable positive counterpart, so any "strip the sign" approach needs a special case for exactly that input.

Java's sign-preserving % avoids the whole issue: digits come out negative, res goes negative, and MIN_VALUE needs no handling. It's the rare case where a language quirk simplifies rather than complicates.

"What does x /= 10 do for negative x?"

Truncates toward zero, so -123 / 10 is -12 and the loop marches to 0 from below. Combined with the negative remainders, the whole thing is sign-symmetric.

Languages with floor division — Python — behave differently and would need the sign handled explicitly.

"Does the input Integer.MIN_VALUE work?"

Yes, returning 0. Its reversal is 8463847412, far outside int range, and the coarse check catches it. The point is that it gets there without any special case — which is exactly what the % semantics buy.

4. Why the Optimal Solution Wins

ApproachTimeSpaceVerdict
String round-tripO(d)O(d)Uses long; Math.abs(MIN_VALUE) trap
long accumulatorO(d)O(1)Correct and simple — but forbidden here
Predictive int checkO(d)O(1)Satisfies the constraint; states the real condition

All are O(d) with d <= 10, so this is entirely about the constraint and about correctness at the boundaries.

Write Approach 3. State the long version first as the obvious solution, name why the constraint rules it out, then derive the predictive check from MAX_VALUE / 10 and MAX_VALUE % 10. That derivation — rather than the digits 7 and 8 recalled from memory — is what's being looked for.

5. Java Prerequisites

% and / keep the sign of the dividend

Java
-123 % 10 == -3        // NOT 7
-123 / 10 == -12       // truncation toward zero, not floor
 123 % 10 ==  3

C, C++, Java and Rust agree on this. Python and Ruby floor instead, giving -123 % 10 == 7.

Math.abs(Integer.MIN_VALUE) is negative

Java
Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE      // documented, not a bug
-Integer.MIN_VALUE        == Integer.MIN_VALUE
Math.absExact(Integer.MIN_VALUE)                       // throws (Java 15+)

Two's complement has one more negative value than positive, so MIN_VALUE has no representable magnitude.

Integer overflow wraps silently

Java
Integer.MAX_VALUE + 1 == Integer.MIN_VALUE        // defined; no exception
Math.multiplyExact(res, 10);                      // throws ArithmeticException instead

Math.multiplyExact inside a try/catch is a legitimate alternative to the predictive check — cleaner to read, though using exceptions for control flow in a hot loop is slower.

The boundary constants

Java
Integer.MAX_VALUE      ==  2147483647
Integer.MAX_VALUE / 10 ==  214748364
Integer.MAX_VALUE % 10 ==  7
Integer.MIN_VALUE      == -2147483648
Integer.MIN_VALUE / 10 == -214748364
Integer.MIN_VALUE % 10 == -8

Casting long to int truncates silently

Java
(int) 9646324351L        // some wrapped value, no warning

So the range test must happen before the cast, never after.

6. Interview Communication Guide

Clarifying questions: May I use long (the constraint says no — that's what makes this interesting)? What should an overflowing input return (0)? Does -120 reverse to -21 (yes — trailing zeros vanish)? Is Math.multiplyExact with a try/catch acceptable (it's a legitimate alternative to the manual check)?

The pitch

"The core loop is three lines: take x % 10 as a digit, divide x by 10, and accumulate res * 10 + digit. Trailing zeros disappear for free, because a leading zero contributes nothing to the accumulator.

The first thing worth saying is that no sign handling is needed. Java's % takes the sign of the dividend, so -123 % 10 is -3, and res accumulates negatively straight to -321. That matters because the obvious alternative — strip the sign with Math.abs, reverse, reapply — breaks on Integer.MIN_VALUE, whose magnitude isn't representable. Math.abs(MIN_VALUE) returns MIN_VALUE, still negative.

The real problem is overflow. res * 10 + digit wraps silently in Java, and once it's wrapped the value is unrecoverable — so checking afterwards is useless. With long off the table, the check has to predict.

Integer.MAX_VALUE is 2147483647, so MAX_VALUE / 10 is 214748364 and the last digit is 7. If res already exceeds 214748364, multiplying by 10 overflows regardless of the digit. If it equals 214748364, the product is 2147483640 and only a final digit up to 7 fits. The negative side mirrors that with −214748364 and −8 — not symmetric, because two's complement has one extra negative value.

Without that guard the function is wrong on about 42% of random 32-bit inputs, returning a wrapped number rather than 0.

One honest note: for 32-bit input the fine-grained digit > 7 clause never actually fires — the coarse comparison catches every real overflow first. I checked that. I keep it because the code should state the correct condition rather than one that happens to be sufficient at this width.

If Math.multiplyExact were allowed, wrapping the accumulation in a try/catch is a cleaner way to express the same thing."

Edge cases to volunteer:

InputExpectedTests
15342364690Overflow on the positive side
-21474836480MIN_VALUE — breaks every Math.abs approach
12021Trailing zero vanishes
-123−321Sign handled by % alone
00Loop never runs
10000000030Reversal is 3000000001 — just over MAX_VALUE

Lead with -2147483648. It's the input that defeats the natural "remember the sign and take the absolute value" structure, and explaining why — that two's complement has no positive counterpart for MIN_VALUE — is the observation this problem rewards.

7. Follow-Up Questions — Modified Constraints

⭐ "What if long were allowed after all?"

Accumulate in a long and range-check once at the end. The input has at most 10 digits so res stays under about 10^10, against Long.MAX_VALUE near 9.2×10^18 — eight orders of magnitude of headroom, so the check is genuinely safe rather than merely likely to be.

That's what I'd ship. The predictive version exists because the constraint forbids it.

⭐ "Use Math.multiplyExact instead of the manual check."

Java
try { res = Math.addExact(Math.multiplyExact(res, 10), digit); }
catch (ArithmeticException e) { return 0; }

Clearer, and it delegates the boundary reasoning to the JDK. The cost is exception-based control flow, which is slow if it fires often — though here it fires at most once per call and then returns.

Math.addExact is needed too: res * 10 can be in range while res * 10 + digit is not.

"Reverse the digits of a long instead."

Same structure with Long.MAX_VALUE / 10 = 922337203685477580 and last digit 7; the negative mirror ends in 8. Long.MAX_VALUE conveniently also ends in 7, so the shape of the check is unchanged.

There's no wider type to fall back on, so the predictive check stops being an exercise and becomes the only option.

"Reverse in a base other than 10."

Replace both 10s with the base and recompute the boundary as MAX_VALUE / base and MAX_VALUE % base. The structure is identical, which is a good sign the solution is about arithmetic rather than about decimal.

"Reverse only the digits, keeping a leading minus — i.e. treat it as a string operation?"

That's what the string approach does, and it's the same answer for every input, because the minus sign isn't a digit. The difference is purely in how overflow is detected.

"What about palindrome checking — is x equal to its reverse?"

Careful: for a palindrome check you can't use this function, because a palindromic number whose reversal overflows would return 0 and compare unequal. The standard trick is to reverse only half the digits and compare — which sidesteps overflow entirely rather than detecting it.

That's LeetCode 9, and it's a nice illustration that the right fix for overflow is sometimes to avoid producing the large value at all.

"How would you test this without a long reference?"

Property-based: reversing twice returns the original whenever neither step overflows; the digit multiset is preserved except for trailing zeros; the sign is preserved. Plus exhaustive testing over a dense range near zero and over every 10-digit value that starts with 1, 2 or 3, where overflow is decided.

I did use a long reference here — 7 million values, all of [−2×10^6, 2×10^6] plus 3 million random ints plus the boundary set — because an oracle was available and exhaustiveness beats cleverness when it is.