← Back to DevBytes

Solving Reverse Integer in JavaScript: Step-by-Step Guide

Introduction to Reverse Integer

The Reverse Integer problem is one of the most popular algorithmic challenges you'll encounter in coding interviews and on platforms like LeetCode. At its core, the problem asks you to take an integer and return its digits in reverse order, while preserving the sign and handling potential overflow conditions. Despite its apparent simplicity, the problem tests your understanding of number manipulation, edge cases, and language-specific quirks.

In JavaScript, solving this problem efficiently requires a solid grasp of how the language handles numbers, particularly around the boundaries of 32-bit signed integers. This tutorial walks you through the problem from concept to optimized solution, covering multiple approaches and best practices along the way.

What Is the Reverse Integer Problem?

The problem statement is straightforward: given a signed 32-bit integer x, return x with its digits reversed. If reversing the integer causes the value to go outside the signed 32-bit integer range [-2^31, 2^31 - 1], return 0.

For example:

The signed 32-bit integer range is from -2147483648 to 2147483647. Any reversed value outside this range must be clamped to zero.

Why It Matters

You might wonder why this problem is so prevalent in interviews. The answer lies in what it evaluates. The Reverse Integer problem tests several fundamental skills simultaneously:

Even if you never reverse an integer in production code, the problem-solving patterns you develop here transfer directly to real-world scenarios involving data transformation, validation, and boundary handling.

Approach 1: String Reversal

The most intuitive approach leverages JavaScript's string manipulation capabilities. Convert the number to a string, reverse it, and convert it back. While not the most performant, it's readable and easy to reason about.

Implementation

function reverseInteger(x) {
  const isNegative = x < 0;
  const absoluteValue = Math.abs(x);
  
  const reversedString = absoluteValue
    .toString()
    .split('')
    .reverse()
    .join('');
  
  const reversedNumber = parseInt(reversedString, 10);
  
  const result = isNegative ? -reversedNumber : reversedNumber;
  
  // Check 32-bit signed integer bounds
  const MIN_INT = Math.pow(-2, 31);
  const MAX_INT = Math.pow(2, 31) - 1;
  
  if (result < MIN_INT || result > MAX_INT) {
    return 0;
  }
  
  return result;
}

// Test cases
console.log(reverseInteger(123));      // 321
console.log(reverseInteger(-123));     // -321
console.log(reverseInteger(120));      // 21
console.log(reverseInteger(1534236469)); // 0

How It Works

The function first determines whether the input is negative and works with its absolute value to simplify the reversal logic. After converting to a string, splitting into characters, reversing, and joining, we parse the result back to an integer. The sign is reapplied, and the overflow check ensures we return zero for out-of-range values.

This approach runs in O(n) time, where n is the number of digits, and uses O(n) space for the string representation. It's clean but involves multiple intermediate allocations.

Approach 2: Mathematical Reversal

A more algorithmic approach avoids string conversion entirely by using arithmetic to extract and rebuild digits. This method demonstrates stronger number manipulation skills and is often preferred in interviews.

Implementation

function reverseInteger(x) {
  const MIN_INT = Math.pow(-2, 31);
  const MAX_INT = Math.pow(2, 31) - 1;
  
  let result = 0;
  let num = Math.abs(x);
  
  while (num !== 0) {
    // Extract the last digit
    const digit = num % 10;
    
    // Remove the last digit from num
    num = Math.floor(num / 10);
    
    // Check for overflow before building the result
    if (result > Math.floor(MAX_INT / 10) || 
        (result === Math.floor(MAX_INT / 10) && digit > 7)) {
      return 0;
    }
    if (result < Math.ceil(MIN_INT / 10) || 
        (result === Math.ceil(MIN_INT / 10) && digit > 8)) {
      return 0;
    }
    
    // Build the reversed number
    result = result * 10 + digit;
  }
  
  return x < 0 ? -result : result;
}

// Test cases
console.log(reverseInteger(123));        // 321
console.log(reverseInteger(-123));       // -321
console.log(reverseInteger(120));        // 21
console.log(reverseInteger(2147483647)); // 0 (overflow)

How It Works

The algorithm repeatedly extracts the last digit using the modulo operator (% 10), then removes that digit from the working number using integer division. Each extracted digit is appended to the result by multiplying the current result by 10 and adding the new digit.

The overflow check happens before the multiplication to prevent incorrect results. The constants 7 and 8 come from the last digits of 2147483647 and -2147483648 respectively. If the result is already at the boundary value, adding a digit larger than these limits would cause overflow.

This approach runs in O(log n) time (since the number of digits is proportional to log base 10 of the number) and uses O(1) space, making it more efficient than the string method.

Approach 3: Using Bitwise Operations

JavaScript's bitwise operators work on 32-bit signed integers, which we can leverage for a concise overflow check. The double bitwise NOT (~~) or bitwise OR with zero (| 0) truncates a number to a 32-bit integer.

Implementation

function reverseInteger(x) {
  const reversed = parseInt(
    Math.abs(x).toString().split('').reverse().join(''),
    10
  );
  
  const signedResult = x < 0 ? -reversed : reversed;
  
  // Use bitwise OR to check 32-bit bounds
  // If the number doesn't fit in 32 bits, the result changes
  if (signedResult !== (signedResult | 0)) {
    return 0;
  }
  
  return signedResult;
}

// Test cases
console.log(reverseInteger(123));      // 321
console.log(reverseInteger(-456));     // -654
console.log(reverseInteger(900000));   // 9
console.log(reverseInteger(1534236469)); // 0

Caveats with Bitwise Operations

While elegant, this approach has a subtle gotcha. JavaScript bitwise operations convert numbers to 32-bit signed integers, which means values outside the range get truncated rather than flagged. The comparison signedResult !== (signedResult | 0) detects this truncation, but you must be careful with very large numbers where precision loss occurs before the bitwise check.

For production code, prefer the explicit boundary checks from Approach 2, as they're more predictable and easier to debug.

Handling Edge Cases

A robust solution must account for several edge cases that trip up inexperienced developers:

Trailing Zeros

Numbers ending in zero, like 120, should reverse to 21, not 021. Both the string and mathematical approaches handle this naturally—parseInt drops leading zeros, and arithmetic never produces them.

Single Digit Numbers

Single digits like 5 or -3 should return themselves. Verify your implementation handles this:

console.log(reverseInteger(5));   // 5
console.log(reverseInteger(-7));  // -7
console.log(reverseInteger(0));   // 0

Zero Input

Zero is a special case. Reversing it should return zero, and the sign handling should not produce -0. Use Object.is(result, -0) in tests to catch this subtle bug:

function reverseInteger(x) {
  if (x === 0) return 0;
  // ... rest of implementation
}

// Verify no negative zero
const result = reverseInteger(0);
console.log(Object.is(result, -0)); // false

Large Numbers Near Boundaries

Test with numbers close to the 32-bit limits to ensure your overflow check triggers correctly:

console.log(reverseInteger(2147483647));  // 0 (reversed would overflow)
console.log(reverseInteger(-2147483648)); // 0 (reversed would overflow)
console.log(reverseInteger(1463847412));  // 2147483641 (valid reversal)

Best Practices

Choose the Right Approach for the Context

For interviews, the mathematical approach (Approach 2) demonstrates stronger algorithmic thinking. For production code where readability matters more than micro-optimizations, the string approach (Approach 1) is perfectly acceptable and often easier to maintain.

Extract Constants for Clarity

Don't scatter magic numbers throughout your code. Define boundary constants at the top of your function or module:

const INT32_MIN = -2147483648;
const INT32_MAX = 2147483647;

function reverseInteger(x) {
  // Use INT32_MIN and INT32_MAX consistently
}

Write Comprehensive Tests

Cover positive numbers, negative numbers, zero, single digits, trailing zeros, and overflow cases. A good test suite might look like:

function testReverseInteger() {
  const tests = [
    { input: 123, expected: 321 },
    { input: -123, expected: -321 },
    { input: 120, expected: 21 },
    { input: 0, expected: 0 },
    { input: 5, expected: 5 },
    { input: -7, expected: -7 },
    { input: 1534236469, expected: 0 },
    { input: 2147483647, expected: 0 },
    { input: -2147483648, expected: 0 },
    { input: 1463847412, expected: 2147483641 },
  ];
  
  tests.forEach(({ input, expected }) => {
    const result = reverseInteger(input);
    const passed = result === expected;
    console.log(
      `${passed ? '✓' : '✗'} reverseInteger(${input}) = ${result} (expected ${expected})`
    );
  });
}

testReverseInteger();

Avoid Premature Optimization

While the mathematical approach is more efficient, the difference is negligible for typical inputs. Don't sacrifice readability for performance unless profiling shows a real bottleneck. The string approach processes a 10-digit number in microseconds.

Document Your Overflow Logic

The overflow check is the trickiest part of the solution. Add comments explaining why you check before the multiplication and what the magic numbers represent:

// Check overflow before multiplying
// MAX_INT = 2147483647, so last digit is 7
// If result > 214748364, multiplying by 10 will overflow
// If result === 214748364, adding digit > 7 will overflow
if (result > Math.floor(MAX_INT / 10) || 
    (result === Math.floor(MAX_INT / 10) && digit > 7)) {
  return 0;
}

Common Pitfalls to Avoid

Performance Comparison

Here's a quick benchmark comparing the three approaches on a large input:

function benchmark(fn, input, iterations = 1000000) {
  const start = performance.now();
  for (let i = 0; i < iterations; i++) {
    fn(input);
  }
  return performance.now() - start;
}

const testInput = 123456789;
console.log(`String approach: ${benchmark(reverseIntegerString, testInput).toFixed(2)}ms`);
console.log(`Math approach: ${benchmark(reverseIntegerMath, testInput).toFixed(2)}ms`);
console.log(`Bitwise approach: ${benchmark(reverseIntegerBitwise, testInput).toFixed(2)}ms`);

Typically, the mathematical approach is fastest because it avoids string allocations, while the string approach incurs overhead from multiple method calls. The bitwise approach falls somewhere in between. However, for most real-world use cases, the difference is imperceptible.

Conclusion

The Reverse Integer problem is a compact exercise that reveals a developer's attention to detail, understanding of number systems, and ability to handle edge cases gracefully. Whether you choose the readable string-based approach or the efficient mathematical method, the key is to handle signs correctly, check for overflow before it happens, and test thoroughly against boundary conditions. By mastering this problem, you build a foundation for tackling more complex algorithmic challenges involving data transformation and validation—skills that translate directly to writing robust, production-quality JavaScript.

🛠 Tools from DevBytes

Inventory Tracker Pro — Excel inventory system, low-stock alerts · $19
AI Dev Kit for Mac — local AI dev environment templates · $9.99
KeyMapper for Mac — custom keyboard shortcut toolkit · $7.99

← Back to all articles