Digit Reversal with Overflow Guard

AS
Ackshaey Singh
Difficulty Hard
Time Complexity
O(log(n))
Space Complexity
O(1)
Math
Google TCS Microsoft Yahoo Amazon

You're in the first round of a Google interview. The interviewer types reverse(1234) on a shared screen and asks, "Write a function that returns 4321. Simple enough, right?" You nod, start coding, and then they add: "Oh, and what happens when the reversed number is too large for a 32-bit integer?" That follow-up is the whole problem. Reversing digits is straightforward arithmetic. Detecting overflow without using a larger data type is where this problem separates candidates who understand integer boundaries from those who don't. On Firecode, this is "Digit Reversal with Overflow Guard," but you'll see it across the industry as Reverse Integer.

TL;DR

Extract digits from right to left using x % 10, build the reversed number with reversed = reversed * 10 + pop, and divide x by 10 each iteration. Before every multiplication, compare reversed against INT_MAX / 10 (or INT_MIN / 10 for negatives) to catch overflow. If it would overflow, return 0. This runs in O(log n) time and O(1) space.

Why This Problem Matters

Reverse Integer is one of the most frequently asked interview problems, appearing at Google, Amazon, Microsoft, Yahoo, and TCS. It tests two distinct skills: basic modular arithmetic (extracting and rearranging digits) and defensive programming against integer overflow. The overflow guard is what makes this a medium-difficulty problem instead of an easy one, and it's the part that trips up most candidates.

Understanding the Problem

Given a signed 32-bit integer x, reverse its digits and return the result. If the reversed integer falls outside the range [-2^31, 2^31 - 1], return 0.

Examples:

  • reverse(1234) returns 4321
  • reverse(-1234) returns -4321
  • reverse(140) returns 41
  • reverse(2147483647) returns 0 (reversed value 7463847412 overflows)

Constraints:

  • The integer x satisfies -2^31 <= x <= 2^31 - 1
  • You cannot use 64-bit integers

Edge Cases to Watch

  1. Negative numbers: The sign should be preserved
  2. Trailing zeros: 140 becomes 41, not 041
  3. INT_MAX / INT_MIN: These overflow when reversed
  4. Zero: Returns 0
  5. Single digit: Returns itself

The Core Idea: Modular Arithmetic

Reversing an integer digit by digit boils down to two operations you probably learned in grade school:

  • x % 10 gives you the last digit (the "ones" place)
  • x / 10 (integer division) removes the last digit

By repeatedly extracting the last digit and appending it to a running total, you build the reversed number. The formula at each step is:

pop = x % 10
x = x / 10
reversed = reversed * 10 + pop

For x = 1234:

Loading visualization...

Each step peels off the rightmost digit and shifts it into position in the reversed number. When x reaches 0, you're done.

Negative Numbers Work Automatically

In Java, C++, Go, and most compiled languages, the % operator preserves the sign of the dividend. This means -1234 % 10 evaluates to -4, not 6. The extracted digit carries its sign naturally, so the same loop handles both positive and negative inputs with no branching.

Loading visualization...

Trailing Zeros Disappear Naturally

When x = 140, the first extracted digit is 0. Multiplying reversed (which is 0) by 10 and adding 0 gives 0, so the leading zero in the reversed number simply vanishes:

Loading visualization...

The Overflow Guard

The digit extraction loop is the easy part. The real challenge is preventing reversed * 10 + pop from exceeding the 32-bit signed integer range. You cannot detect overflow after it happens, because in C++ the behavior is undefined, and in Java the value silently wraps around. You need to check before performing the multiplication.

32-Bit Integer Boundaries

Loading visualization...

INT_MAX is 2,147,483,647 and INT_MIN is -2,147,483,648. The key values for our guard are INT_MAX / 10 = 214,748,364 and INT_MIN / 10 = -214,748,364.

Two Conditions Per Side

The overflow check has two cases for positive numbers and two for negative:

Positive overflow (before computing reversed * 10 + pop):

  1. If reversed > INT_MAX / 10, then reversed * 10 already exceeds INT_MAX. Return 0.
  2. If reversed == INT_MAX / 10 and pop > 7, then reversed * 10 + pop > 2,147,483,647. Return 0.

Negative underflow:

  1. If reversed < INT_MIN / 10, then reversed * 10 already goes below INT_MIN. Return 0.
  2. If reversed == INT_MIN / 10 and pop < -8, then reversed * 10 + pop < -2,147,483,648. Return 0.

Loading visualization...

The numbers 7 and -8 come directly from the last digits of INT_MAX and INT_MIN.

Overflow in Action

When x = 2147483647, the reversed value would be 7463847412, which blows past INT_MAX. The guard catches this partway through the loop:

Loading visualization...

Implementation

Prefer a different language? Jump to solutions in other languages.

Here is the complete Java solution:

public class Solution {

  public int reverse(int x) {
    int reversed = 0;

    while (x != 0) {
      int pop = x % 10;
      x /= 10;

      // Overflow guard: positive side
      if (reversed > Integer.MAX_VALUE / 10
          || (reversed == Integer.MAX_VALUE / 10 && pop > 7)) {
        return 0;
      }
      // Overflow guard: negative side
      if (reversed < Integer.MIN_VALUE / 10
          || (reversed == Integer.MIN_VALUE / 10 && pop < -8)) {
        return 0;
      }

      reversed = reversed * 10 + pop;
    }

    return reversed;
  }
}

Step-by-Step Walkthrough

For reverse(1234):

Iterationxpop (x % 10)x /= 10Overflow checkreversed * 10 + pop
1123441230 < 214748364, safe4
21233124 < 214748364, safe43
3122143 < 214748364, safe432
4110432 < 214748364, safe4321

Loop ends when x == 0. Return 4321.

For reverse(-2147483648) (INT_MIN):

IterationxpopOverflow checkResult
1-2147483648-8reversed = 0, safe-8
2-214748364-4-8 > -214748364, safe-84
...............
9-2-2-846384741 < -214748364, overflow detected!return 0

Complexity Analysis

Time Complexity: O(log n)

The number of digits in an integer n is \lfloor \log_{10}(|n|) \rfloor + 1. Each iteration processes one digit, so the loop runs at most 10 times for any 32-bit integer. This is technically O(log n), though in practice it is bounded by a small constant.

Space Complexity: O(1)

The algorithm uses three variables (reversed, pop, and the mutating x) regardless of input size. No strings, arrays, or additional data structures are allocated.

Common Pitfalls

  1. Forgetting the overflow check entirely: Without the guard, reversed * 10 + pop can silently wrap around in Java or trigger undefined behavior in C++.

  2. Checking overflow after the multiplication: At that point the damage is done. You must check before reversed * 10.

  3. Using a 64-bit long: This works but violates the problem constraint. Interviewers will ask you to solve it within 32-bit arithmetic.

  4. Mishandling negative modulus: In Python and Ruby, % and // round toward negative infinity rather than toward zero. If you're working in those languages, you need to handle the sign separately. See the Python and Ruby solutions below.

  5. Off-by-one on boundary digits: The threshold digits are 7 (from INT_MAX's last digit) and -8 (from INT_MIN's last digit). Using the wrong constants causes subtle bugs on boundary inputs.

Interview Tips

When solving this in an interview:

  1. Start with the simple case. Write the extraction loop first (pop = x % 10, x /= 10, reversed = reversed * 10 + pop). Show that it works for 1234 before adding overflow logic.

  2. Bring up overflow proactively. Don't wait for the interviewer to ask "what about large numbers?" Mention the 32-bit constraint yourself and explain why you need a pre-check.

  3. Explain the guard in terms of INT_MAX / 10. The core insight is that if reversed is already larger than INT_MAX / 10, then reversed * 10 will definitely overflow. The boundary case (reversed == INT_MAX / 10) requires checking the digit being appended.

  4. Test with specific inputs: 0, -1, 10, INT_MAX, INT_MIN. Walk through at least one overflow case to demonstrate the guard in action.

  5. Mention the language caveat. If the interviewer asks about Python or Ruby, point out that their modulus operator behaves differently for negative numbers, and show how your approach adapts.

Key Takeaways

  • Reverse digits with repeated x % 10 (extract) and x / 10 (shrink), building the result as reversed = reversed * 10 + pop.
  • The overflow guard compares reversed against INT_MAX / 10 before multiplying. If reversed exceeds this threshold, or equals it with a digit larger than 7, return 0. Mirror the check for INT_MIN / 10 and -8 on the negative side.
  • In Java, C++, Go, and Rust, the % operator preserves sign, so the same loop handles positive and negative inputs. Python and Ruby require sign-separated handling because their modulus rounds toward negative infinity.
  • Time is O(log n) with at most 10 iterations for 32-bit integers. Space is O(1) with no auxiliary data structures.
  • Always validate with INT_MAX and INT_MIN as inputs. These are the cases interviewers use to verify your overflow guard is correct.

Solutions in Other Languages

Python

Python integers have arbitrary precision, so they never overflow natively. However, the problem asks us to simulate 32-bit overflow behavior. Since Python's % and // operators round toward negative infinity (not toward zero), we handle the sign separately to keep the logic clean.

class Solution:
    def reverse(self, x: int) -> int:
        INT_MAX = 2**31 - 1
        INT_MIN = -2**31

        sign = -1 if x < 0 else 1
        x *= sign
        reversed_x = 0

        while x != 0:
            pop = x % 10
            x //= 10

            if reversed_x > (INT_MAX - pop) // 10:
                return 0

            reversed_x = reversed_x * 10 + pop

        return sign * reversed_x

JavaScript

JavaScript does not have a native 32-bit integer type, so Math.pow(2, 31) - 1 and Math.floor / Math.ceil are used for the bounds. The bitwise OR trick (x / 10) | 0 truncates toward zero, matching C-style integer division.

class Solution {
  reverse(x) {
    let reversed = 0;
    const INT_MAX = Math.pow(2, 31) - 1;
    const INT_MIN = -Math.pow(2, 31);

    while (x !== 0) {
      const pop = x % 10;
      x = (x / 10) | 0;

      if (reversed > Math.floor(INT_MAX / 10)
          || (reversed === Math.floor(INT_MAX / 10) && pop > 7)) {
        return 0;
      }
      if (reversed < Math.ceil(INT_MIN / 10)
          || (reversed === Math.ceil(INT_MIN / 10) && pop < -8)) {
        return 0;
      }

      reversed = reversed * 10 + pop;
    }

    return reversed;
  }
}

TypeScript

Identical to the JavaScript solution with type annotations added.

class Solution {
  reverse(x: number): number {
    let reversed = 0;
    const INT_MAX = Math.pow(2, 31) - 1;
    const INT_MIN = -Math.pow(2, 31);

    while (x !== 0) {
      const pop = x % 10;
      x = Math.trunc(x / 10);

      if (reversed > Math.floor(INT_MAX / 10)
          || (reversed === Math.floor(INT_MAX / 10) && pop > 7)) {
        return 0;
      }
      if (reversed < Math.ceil(INT_MIN / 10)
          || (reversed === Math.ceil(INT_MIN / 10) && pop < -8)) {
        return 0;
      }

      reversed = reversed * 10 + pop;
    }

    return reversed;
  }
}

C++

C++ integer overflow on signed types is undefined behavior, which makes the pre-check essential. The <limits> header provides std::numeric_limits<int>::max() and min().

#include <limits>

class Solution {
public:
  int reverse(int x) {
    int reversed = 0;
    while (x != 0) {
      int pop = x % 10;
      x /= 10;

      if (reversed > std::numeric_limits<int>::max() / 10
          || (reversed == std::numeric_limits<int>::max() / 10 && pop > 7)) {
        return 0;
      }
      if (reversed < std::numeric_limits<int>::min() / 10
          || (reversed == std::numeric_limits<int>::min() / 10 && pop < -8)) {
        return 0;
      }

      reversed = reversed * 10 + pop;
    }
    return reversed;
  }
};

Go

Go does not have exceptions for integer overflow on fixed-width types; it wraps silently. The constants are defined as shifts: 1<<31 - 1 for INT_MAX and -1 << 31 for INT_MIN.

package solution

func (s *Solution) Reverse(x int) int {
    const (
        intMax = 1<<31 - 1
        intMin = -1 << 31
    )

    result := 0
    for x != 0 {
        pop := x % 10
        x /= 10

        if result > intMax/10 || (result == intMax/10 && pop > 7) {
            return 0
        }
        if result < intMin/10 || (result == intMin/10 && pop < -8) {
            return 0
        }

        result = result*10 + pop
    }
    return result
}

Kotlin

Kotlin's Int is a 32-bit signed type. Int.MAX_VALUE and Int.MIN_VALUE provide the bounds directly.

class Solution {
  fun reverse(x: Int): Int {
    var reversed = 0
    var num = x
    while (num != 0) {
      val pop = num % 10
      num /= 10

      if (reversed > Int.MAX_VALUE / 10
          || (reversed == Int.MAX_VALUE / 10 && pop > 7)) return 0
      if (reversed < Int.MIN_VALUE / 10
          || (reversed == Int.MIN_VALUE / 10 && pop < -8)) return 0

      reversed = reversed * 10 + pop
    }
    return reversed
  }
}

Scala

Scala's Int behaves identically to Java's. Int.MaxValue and Int.MinValue provide the bounds.

class Solution {
  def reverse(x: Int): Int = {
    var num = x
    var reversed = 0

    while (num != 0) {
      val pop = num % 10
      num /= 10

      if (reversed > Int.MaxValue / 10
          || (reversed == Int.MaxValue / 10 && pop > 7)) return 0
      if (reversed < Int.MinValue / 10
          || (reversed == Int.MinValue / 10 && pop < -8)) return 0

      reversed = reversed * 10 + pop
    }

    reversed
  }
}

Rust

Rust's i32 panics on overflow in debug mode and wraps in release mode. The pre-check prevents either behavior from being reached.

impl Solution {
    pub fn reverse(&self, mut x: i32) -> i32 {
        let mut reversed = 0_i32;
        while x != 0 {
            let pop = x % 10;
            x /= 10;

            if reversed > i32::MAX / 10
                || (reversed == i32::MAX / 10 && pop > 7) {
                return 0;
            }
            if reversed < i32::MIN / 10
                || (reversed == i32::MIN / 10 && pop < -8) {
                return 0;
            }

            reversed = reversed * 10 + pop;
        }
        reversed
    }
}

Swift

Swift's Int is 64-bit on modern platforms, but the problem constrains us to 32-bit bounds. We use Int32.max and Int32.min for the guard, converted to Int for arithmetic.

import Foundation

class Solution {
    func reverse(_ x: Int) -> Int {
        let maxDiv10 = Int(Int32.max) / 10
        let minDiv10 = Int(Int32.min) / 10
        var reversed = 0
        var num = x

        while num != 0 {
            let pop = num % 10
            num /= 10

            if reversed > maxDiv10 || (reversed == maxDiv10 && pop > 7) { return 0 }
            if reversed < minDiv10 || (reversed == minDiv10 && pop < -8) { return 0 }

            reversed = reversed * 10 + pop
        }
        return reversed
    }
}

C#

C#'s int is a 32-bit signed type. int.MaxValue and int.MinValue give the bounds.

public class Solution {
    public int Reverse(int x) {
        int reversed = 0;
        while (x != 0) {
            int pop = x % 10;
            x /= 10;

            if (reversed > int.MaxValue / 10
                || (reversed == int.MaxValue / 10 && pop > 7)) return 0;
            if (reversed < int.MinValue / 10
                || (reversed == int.MinValue / 10 && pop < -8)) return 0;

            reversed = reversed * 10 + pop;
        }
        return reversed;
    }
}

Dart

Dart's int is 64-bit, but the problem constrains us to 32-bit bounds. The ~/ operator performs truncating integer division, and .remainder() preserves sign like C-style modulus.

class Solution {
  int reverse(int x) {
    int maxInt32 = 2147483647;
    int minInt32 = -2147483648;
    int reversed = 0;

    while (x != 0) {
      int pop = x.remainder(10);
      x = x ~/ 10;

      if (reversed > maxInt32 ~/ 10
          || (reversed == maxInt32 ~/ 10 && pop > 7)) return 0;
      if (reversed < minInt32 ~/ 10
          || (reversed == minInt32 ~/ 10 && pop < -8)) return 0;

      reversed = reversed * 10 + pop;
    }
    return reversed;
  }
}

PHP

PHP integers are 64-bit on modern platforms, but we enforce 32-bit bounds manually. intdiv() performs truncating integer division.

class Solution {
    public function reverse(int $x): int {
        $INT_MAX = 2147483647;
        $INT_MIN = -2147483648;
        $reversed = 0;

        while ($x !== 0) {
            $pop = $x % 10;
            $x = intdiv($x, 10);

            if ($reversed > intdiv($INT_MAX, 10)
                || ($reversed === intdiv($INT_MAX, 10) && $pop > 7)) {
                return 0;
            }
            if ($reversed < intdiv($INT_MIN, 10)
                || ($reversed === intdiv($INT_MIN, 10) && $pop < -8)) {
                return 0;
            }

            $reversed = $reversed * 10 + $pop;
        }
        return $reversed;
    }
}

Ruby

Like Python, Ruby's % and / round toward negative infinity. The solution handles negative numbers by adjusting the digit when Ruby's modulus returns a positive value for a negative dividend, and uses .truncate for toward-zero division.

class Solution
  def reverse(x)
    int_max = 2**31 - 1
    int_min = -(2**31)
    reversed = 0

    while x != 0
      digit = x % 10
      digit -= 10 if x < 0 && digit > 0
      x = (x / 10.0).truncate

      return 0 if reversed > int_max / 10 || (reversed == int_max / 10 && digit > 7)
      return 0 if reversed < int_min / 10 || (reversed == int_min / 10 && digit < -8)

      reversed = reversed * 10 + digit
    end
    reversed
  end
end

Practice Makes Perfect

Once you're comfortable with the overflow guard pattern, try these related problems to reinforce the underlying skills:

  • Palindrome Number (determine if an integer reads the same forward and backward, using the same digit extraction loop)
  • String to Integer (atoi) (parse a string into an integer with overflow checking)
  • Add Two Numbers (sum digits with carry across linked list nodes)

Consistent practice is the key to succeeding in coding interviews. This problem and thousands of others are available on Firecode, where over 50,000 users have successfully prepared for technical interviews and landed six and seven-figure jobs at top tech companies. Whether you're just starting or sharpening up for a Google onsite, building fluency with integer math and boundary conditions will pay off.

Happy coding, and may your integers never overflow!

Frequently Asked Questions

What is the Reverse Integer problem?
Given a signed 32-bit integer x, reverse its digits and return the result. If reversing causes the value to go outside the signed 32-bit range [-2^31, 2^31 - 1], return 0 instead. For example, reverse(1234) returns 4321, reverse(-1234) returns -4321, and reverse(1534236469) returns 0 because the reversed value exceeds INT_MAX.
Why do we check for overflow before multiplying by 10?
If reversed is already greater than INT_MAX / 10 (which is 214748364), then reversed * 10 will exceed 2,147,483,647 and overflow. Checking before the multiplication lets us detect this without ever producing an out-of-range value. The same logic applies in reverse for negative numbers and INT_MIN.
Why are the last-digit thresholds 7 and -8?
INT_MAX is 2,147,483,647 and its last digit is 7. INT_MIN is -2,147,483,648 and its last digit is -8. When reversed equals exactly INT_MAX / 10 (214748364), adding any digit greater than 7 would push past INT_MAX. Similarly, adding any digit less than -8 when reversed equals INT_MIN / 10 would push past INT_MIN.
How does the modulus operator handle negative numbers in this problem?
In Java, C++, Go, and most compiled languages, the modulus operator preserves the sign of the dividend. So -1234 % 10 equals -4, not 6. This means the pop variable naturally carries the correct sign, and the same loop logic works for both positive and negative inputs without special case handling.
What is the time complexity of reversing an integer?
O(log10(n)) where n is the absolute value of the input. Each iteration of the loop removes one digit, and the number of digits in n is floor(log10(n)) + 1. In practice this is at most 10 iterations for a 32-bit integer, so it runs in effectively constant time.
What is the space complexity of the digit reversal approach?
O(1) because the algorithm uses only a fixed number of variables (reversed, pop, and the input x) regardless of the input size. No arrays, strings, or auxiliary data structures are needed.
Can you solve this problem by converting the integer to a string?
Technically yes, but the problem states that the environment does not allow 64-bit integers, and a string-based approach introduces extra space and complexity. The mathematical approach using modulus and division is cleaner, runs in O(1) space, and is what interviewers expect. Converting to a string also makes overflow detection less straightforward.
Why can't you use a long (64-bit integer) to detect overflow?
The problem explicitly constrains you to 32-bit integers. Using a 64-bit long to accumulate the reversed value and then checking the range at the end is a valid workaround in some languages, but it violates the problem constraint. The pre-check approach with INT_MAX / 10 works entirely within 32-bit arithmetic.
How does this problem differ across programming languages?
The core algorithm is identical, but languages differ in how they handle integer division and modulus for negative numbers. Java, C++, Go, Kotlin, and Rust truncate toward zero, so the loop works uniformly. Python's floor division and modulus round toward negative infinity, so you need to handle the sign separately. Ruby has the same quirk and requires adjustment.
What are common mistakes when solving Reverse Integer in interviews?
The most common mistake is forgetting the overflow check entirely, which causes undefined behavior in C++ or silent wraparound in Java. Other pitfalls include checking for overflow after the multiplication (too late), using 64-bit integers when the problem forbids them, and mishandling negative numbers in languages where modulus behaves differently.