Overflow in intermediate arithmetic
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
A complete visual walkthrough of digit-by-digit reversal with overflow detection — from brute force to optimal.
Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-231, 231 - 1], then return 0.
Assume the environment does not allow you to store 64-bit integers (signed or unsigned).
Example 1:
Input: x = 123 Output: 321
Example 2:
Input: x = -123 Output: -321
Example 3:
Input: x = 120 Output: 21
Constraints:
-231 <= x <= 231 - 1The first instinct: convert the integer to a string, reverse the characters, then parse it back. Let's trace it with x = -123:
This works, but it's clunky. You need to handle the sign separately, strip leading zeros, and you still need to check for overflow before parsing back. The string conversion also uses extra memory for the character array.
// Brute force (still needs overflow check!) String s = String.valueOf(Math.abs((long)x)); String rev = new StringBuilder(s).reverse().toString(); long val = Long.parseLong(rev) * (x < 0 ? -1 : 1); return (val > Integer.MAX_VALUE || val < Integer.MIN_VALUE) ? 0 : (int)val;
// Brute force (still needs overflow check!) sign := 1 if x < 0 { sign = -1; x = -x } s := strconv.Itoa(x) runes := []rune(s) for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { runes[i], runes[j] = runes[j], runes[i] } val, _ := strconv.ParseInt(string(runes), 10, 64) val *= int64(sign) if val > math.MaxInt32 || val < math.MinInt32 { return 0 } return int(val)
# Brute force (still needs overflow check!) s = str(abs(x)) rev = s[::-1] val = int(rev) * (-1 if x < 0 else 1) return 0 if val > 2147483647 or val < -2147483648 else val
// Brute force (still needs overflow check!) let sign: i64 = if x < 0 { -1 } else { 1 }; let rev: String = (x as i64).abs().to_string().chars().rev().collect(); let val: i64 = rev.parse().unwrap_or(0) * sign; if val > i32::MAX as i64 || val < i32::MIN as i64 { return 0; } val as i32
// Brute force (still needs overflow check!) const s = Math.abs(x).toString(); const rev = s.split("").reverse().join(""); const val = Number.parseInt(rev) * (x < 0 ? -1 : 1); return val > 2147483647 || val < -2147483648 ? 0 : val;
The real problem isn't reversing digits — it's detecting overflow without using a larger data type. Can we do this with pure math, no strings, no long?
We can reverse an integer purely with arithmetic. Two operations are all we need:
The beauty: popping digits from x gives them in reverse order. The last digit comes out first, which is exactly where it needs to go in the result. No string manipulation needed.
Digits came out as 3, 2, 1 — the reverse of 1, 2, 3!
Negative numbers work automatically! In Java, -123 % 10 = -3 and -123 / 10 = -12. The sign is preserved through modulo and division, so no special sign handling is needed.
Let's trace the full algorithm with x = 1534, watching digits move from the input to the result one by one.
x = 1534 / 10 = 153, result = 4
x = 153 / 10 = 15, result = 43
x = 15 / 10 = 1, result = 435
x = 1 / 10 = 0 (done!), result = 4351
The operation result * 10 + digit can overflow a 32-bit integer. The trick is to check BEFORE the multiplication happens, not after. Once overflow occurs, the value is already corrupted.
The 32-bit signed integer range is [-2,147,483,648 to 2,147,483,647]. We need to check if result * 10 + digit would exceed these bounds.
result > MAX_VALUE / 10 (i.e., result > 214748364), then result * 10 alone would overflow.result == MAX_VALUE / 10 and digit > 7, then result * 10 + digit > 2147483647.result < MIN_VALUE / 10 (i.e., result < -214748364), then result * 10 alone would underflow.result == MIN_VALUE / 10 and digit < -8, then result * 10 + digit < -2147483648.Why 7 and -8? Because MAX_VALUE = 2,147,483,647 and MIN_VALUE = -2,147,483,648. When result == 214748364, we can only safely add a digit up to 7 (positive) or down to -8 (negative).
Reversed this would be 9646324351, which exceeds 2,147,483,647. The algorithm catches it:
The most important edge case. If reversing the digits would produce a number outside [-2,147,483,648, 2,147,483,647], return 0. For example, x = 1534236469 reverses to 9646324351, which exceeds INT_MAX, so the answer is 0. The algorithm detects this before the multiplication corrupts the value.
The sign is preserved automatically by the modulo and division operations. In Java (and most languages with C-style truncation), -123 % 10 = -3 and -123 / 10 = -12, so the pop-and-push loop produces -321 without any special sign handling. The overflow check for negatives uses MIN_VALUE: if result < MIN_VALUE / 10 or (result == MIN_VALUE / 10 and digit < -8), return 0.
Trailing zeros on the input become leading zeros on the reversed number, which are dropped naturally. x = 1200 reverses to 0021, but leading zeros are not stored — the result is simply 21. The algorithm handles this correctly because pushing a 0 digit just multiplies result by 10 and adds 0, and the initial zeros land at the front of the reversed number where they have no effect.
Any single-digit integer (-9 through 9) is already its own reverse. The loop runs exactly once: it pops the only digit, the overflow check trivially passes (since |digit| ≤ 9 < 214748364), and pushes it onto result. The output equals the input. For example, x = 7 returns 7.
x = 0 is the degenerate case. The while (x != 0) condition is false from the start, so the loop body never executes and result stays 0. The function returns 0 immediately, which is correct.
class Solution { public int reverse(int x) { int result = 0; while (x != 0) { // Pop the last digit from x int digit = x % 10; x /= 10; // Check for positive overflow BEFORE it happens // If result > MAX/10, then result*10 would already overflow // If result == MAX/10 and digit > 7, then result*10+digit > MAX if (result > Integer.MAX_VALUE / 10 || (result == Integer.MAX_VALUE / 10 && digit > 7)) return 0; // Check for negative overflow (underflow) // If result < MIN/10, then result*10 would already underflow // If result == MIN/10 and digit < -8, then result*10+digit < MIN if (result < Integer.MIN_VALUE / 10 || (result == Integer.MIN_VALUE / 10 && digit < -8)) return 0; // Push the digit onto result result = result * 10 + digit; } return result; } }
func reverse(x int) int { result := 0 for x != 0 { // Pop the last digit from x digit := x % 10 x /= 10 // Check for positive overflow BEFORE it happens // If result > MAX/10, then result*10 would already overflow // If result == MAX/10 and digit > 7, then result*10+digit > MAX if result > math.MaxInt32/10 || (result == math.MaxInt32/10 && digit > 7) { return 0 } // Check for negative overflow (underflow) if result < math.MinInt32/10 || (result == math.MinInt32/10 && digit < -8) { return 0 } // Push the digit onto result result = result*10 + digit } return result }
def reverse(x: int) -> int: MAX = 2147483647 # 2^31 - 1 MIN = -2147483648 # -(2^31) result = 0 while x != 0: # Pop the last digit from x # Python's % always returns non-negative for positive divisor, # so we use int(math.fmod(x, 10)) to match Java behavior digit = int(math.fmod(x, 10)) x = int(x / 10) # truncate toward zero # Check for positive overflow BEFORE it happens if result > MAX // 10 or \ (result == MAX // 10 and digit > 7): return 0 # Check for negative overflow (underflow) if result < -((-MIN) // 10) or \ (result == -((-MIN) // 10) and digit < -8): return 0 # Push the digit onto result result = result * 10 + digit return result
impl Solution { pub fn reverse(x: i32) -> i32 { let mut x = x; let mut result: i32 = 0; while x != 0 { // Pop the last digit from x let digit = x % 10; x /= 10; // Check for positive overflow BEFORE it happens // If result > MAX/10, then result*10 would already overflow // If result == MAX/10 and digit > 7, then result*10+digit > MAX if result > i32::MAX / 10 || (result == i32::MAX / 10 && digit > 7) { return 0; } // Check for negative overflow (underflow) if result < i32::MIN / 10 || (result == i32::MIN / 10 && digit < -8) { return 0; } // Push the digit onto result result = result * 10 + digit; } result } }
function reverse(x: number): number { const MAX = 2147483647; // 2^31 - 1 const MIN = -2147483648; // -(2^31) let result = 0; while (x !== 0) { // Pop the last digit from x const digit = x % 10; x = Math.trunc(x / 10); // Check for positive overflow BEFORE it happens if (result > Math.floor(MAX / 10) || (result === Math.floor(MAX / 10) && digit > 7)) return 0; // Check for negative overflow (underflow) if (result < Math.ceil(MIN / 10) || (result === Math.ceil(MIN / 10) && digit < -8)) return 0; // Push the digit onto result result = result * 10 + digit; } return result; }
No long, no strings, no abs(). This solution handles positive and negative numbers uniformly, detects overflow using only 32-bit arithmetic, and runs in O(log10(x)) time with O(1) space. This is the optimal solution LeetCode expects.
Enter any 32-bit integer and watch the algorithm pop and push digits one at a time, with live overflow detection.
The loop runs once per digit, and the number of digits in x is floor(log10(|x|)) + 1. Each iteration does O(1) work: one modulo (x % 10), one integer division (x / 10), two overflow comparisons, one multiply, and one add. No extra data structures are allocated — just a single result integer variable, giving O(1) space.
Converts the integer to a character array of d = log10(n) digits, reverses the array in O(d), then parses it back. The character array and the reversed string each require O(d) memory. Still needs an overflow check before parsing.
A while loop pops digits via x % 10 and pushes them onto the result via result * 10 + digit -- one digit per iteration for d total iterations. Overflow is detected before each multiply by comparing result against MAX_VALUE / 10. Only a single result integer is allocated.
The number of digits is log10(n), so reversing is logarithmic. For a 32-bit integer the loop runs at most 10 times, making the algorithm effectively O(1) in practice. The tricky part of this problem isn't the complexity — it's detecting overflow before it happens, using only 32-bit arithmetic and no larger data types.
Review these before coding to avoid predictable interview regressions.
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.