int32 vs int64: The Overflow Bug Every Coding Interview Tests

June 18, 202614 min read
dsaalgorithmsinterview-prepdata-structures
TL;DR
  • int32 caps at ~2×10⁹; multiply two LeetCode 10⁹ constraints and you silently overflow with no warning
  • int64 holds up to ~9×10¹⁸, safe for products of any two int32-range values
  • The classic binary search midpoint bug (lo+hi)/2 overflows int32 when both values are near INT_MAX — Joshua Bloch's 2006 confession from inside the JDK
  • Signed overflow is undefined behavior in C/C++; Java, Go, and Kotlin wrap silently; Rust panics in debug mode; Python and Ruby have no overflow at all
  • Always write lo + (hi - lo) / 2 and cast to int64 before multiplying, not after

If you've spent time on LeetCode, you've met the number 2,147,483,647. It shows up in problem constraints, in edge case tests, and in the bug that silently kills your solution on the last test case. That's the maximum value of a 32-bit signed integer. The choice between int32 and int64 comes up in almost every interview that touches arithmetic, and the answer is never obvious from the constraints alone. You can try to ignore it. Test case 47 will not.

int32 vs int64 overflow bug


A Bit Width Is a Ceiling. Exceed It and Things Get Weird.

Every integer in memory is a sequence of bits. A 32-bit integer uses 32 binary digits. A 64-bit integer uses 64.

With 32 bits you can represent 2^32 distinct values. That's 4,294,967,296 total states. With 64 bits you get 2^64, roughly 1.8 × 10^19.

The bit width sets a hard ceiling on what numbers the type can hold. Exceed it and the value wraps around, crashes your program, or invokes undefined behavior depending on the language.

This matters more than most engineers think. Your average LeetCode constraint says "up to 10^9." That fits comfortably in 32 bits. Two of those constraints multiplied together produces 10^18, which does not. A lot of wrong answers come from this one mismatch.


The Four Numbers You Should Know Cold

Most languages default to signed integers, where one bit indicates the sign. The sign bit is why a 32-bit signed integer caps at 2^31 - 1 rather than 2^32 - 1.

TypeMinMax
int8 (signed, 8-bit)-128127
int32 (signed, 32-bit)-2,147,483,6482,147,483,647
uint32 (unsigned, 32-bit)04,294,967,295
int64 (signed, 64-bit)-9,223,372,036,854,775,8089,223,372,036,854,775,807

You don't need the exact values. Knowing that int32 tops out around 2 × 10^9 and int64 around 9 × 10^18 is enough to catch overflow before it bites you.

The asymmetry, one more negative value than positive, comes from two's complement encoding. The bit pattern 10000...0 (sign bit set, rest zeros) doesn't represent zero; it represents the most negative value. That's why INT_MIN is -2,147,483,648 but INT_MAX is only 2,147,483,647. The full mechanics are in Two's Complement: The Bit Trick Behind Every Negative Integer.


What Overflow Does (Your Code Just Keeps Running)

Cross INT_MAX and what happens next depends entirely on the language.

Java and C#: Silent wraparound. 2,147,483,647 + 1 becomes -2,147,483,648. No exception. No warning. Your program keeps running with an entirely wrong number, cheerful as ever.

C and C++: Signed integer overflow is undefined behavior. The compiler assumes it never happens. At high optimization levels it may generate code that doesn't wrap predictably, because the standard allows it to do anything. At least this is honest about being bad.

Go: Silent wraparound on fixed-width types (int32, int64). The platform-native int type also wraps on overflow.

Rust: Debug builds panic with a clear message. Release builds wrap silently. You also get explicit operators, wrapping_add, saturating_add, checked_add, so you can choose the behavior you actually want. Rust is the only language here that treats you like an adult.

Python 3 and Ruby: Integers are arbitrary precision. There is no overflow. The value just gets larger. This is fine and wonderful and why Python 3 is great for rapid prototyping and absolutely not an excuse to never understand this stuff.

JavaScript: Numbers are 64-bit floats. Integers up to 2^53 are exact. Beyond that, precision loss begins. Bitwise operators coerce to 32-bit signed integers, which means (2147483647 + 1) | 0 wraps to -2147483648 even though 2147483647 + 1 in float math gives the correct 2147483648. JavaScript is doing two different things and neither of them is labeled.

The most dangerous case is silent wraparound in statically typed languages. Java, C#, and Go give you a wrong value and keep running. There is no stack trace to find. No error to Google. Just a number that is subtly, catastrophically wrong. A bit-level walkthrough is in Two's Complement Overflow: Negative Numbers and the Bugs They Hide.


The Binary Search Bug That Shipped in JDK

This one has a story. In 2006, Joshua Bloch, the engineer who wrote Arrays.binarySearch for the Java standard library, published a confession: the implementation had a latent overflow bug that had been sitting in production for roughly nine years. He titled the post "Extra, Extra, Read All About It: Nearly All Binary Searches and Mergesorts Are Broken." Not "some." Nearly all.

The bug was in the midpoint calculation.

// Wrong: can overflow if lo and hi are both near Integer.MAX_VALUE int mid = (lo + hi) / 2;

If lo and hi are both close to Integer.MAX_VALUE, their sum exceeds 2,147,483,647 and wraps to a negative number. Divide that by 2 and you get a negative midpoint. Your binary search crashes or silently searches the wrong half.

// Correct: subtraction stays within bounds int mid = lo + (hi - lo) / 2;

The subtraction hi - lo is safe because both values are non-negative. You then add the safe difference to lo. The result is identical but the intermediate value never overflows.

Write every binary search with the subtraction form. There is no downside and it is immune to this class of bug. The same mistake appears in merge sort implementations and anywhere else you average two large indices. If it shipped in the JDK for nine years and you're writing it in forty minutes under interview pressure, use the safe form unconditionally. For the other off-by-one patterns that break binary search: Your Binary Search Has an Off-by-One Bug. Prove Me Wrong.


When to Reach for int64 (Before the Wrong Answer Does)

The practical interview question: which type do I need here?

A few signals that push you to int64:

  • Constraints say inputs go up to 10^9 and you are multiplying two of them. The product can reach 10^18, which overflows int32 but fits in int64.
  • The problem says "return the answer modulo 10^9 + 7." The intermediate product before taking the modulus can overflow int32 even on small inputs.
  • You are accumulating a sum over an array where individual values are up to 10^6 and there are up to 10^6 elements. Maximum sum is 10^12.
  • Timestamps in milliseconds since epoch are around 1.7 × 10^12 as of 2026. That fits in int64 but not int32.

In Java, the cast must come before the multiplication:

// Wrong: overflow happens first, then casts the wrong result long product = (long)(a * b); // Correct: a is promoted to long before the multiply long product = (long) a * b;

The same principle applies in C++, C, and Kotlin. The cast placement is a common interview mistake. Declare the accumulator as int64 from the start if the loop can produce a large sum.


When Smaller Is Better: The Cache Argument

Each int32 takes 4 bytes. Each int64 takes 8 bytes. For a single variable, the difference is irrelevant. For an array of 100 million values, it is 400 MB versus 800 MB.

The difference also affects cache performance. A 64-byte cache line holds 16 int32 values or 8 int64 values. Scanning a large array of int32 values can produce measurably fewer cache misses than the same data in int64, because you get twice as many elements per cache load. The details are in What Is Cache-Friendly Code?.

In coding interviews you will rarely be asked to reason about this explicitly. In system design discussions about storage-efficient data pipelines or large in-memory indices, it comes up directly: choosing int32 over int64 halves the per-element memory footprint and improves locality.


One Structure, Every Language

Python 3

import sys # Python 3 integers are arbitrary precision. No overflow. INT_MAX = 2**31 - 1 # 2147483647 INT_MIN = -(2**31) # -2147483648 LONG_MAX = 2**63 - 1 # 9223372036854775807 x = INT_MAX x += 1 print(x) # 2147483648, no overflow, no wrapping # sys.maxsize reflects the pointer width (platform-dependent) print(sys.maxsize) # 9223372036854775807 on 64-bit systems

Python 2

import sys # Python 2: int is fixed-width, long is arbitrary precision INT_MAX = 2**31 - 1 # 2147483647 LONG_MAX = 2**63 - 1 # 9223372036854775807 x = 2**31 - 1 print type(x) # <type 'int'> on 64-bit; may be <type 'long'> x += 1 print x # 2147483648, auto-promotes to long, no truncation

JavaScript

// JS numbers are IEEE 754 double-precision floats. No distinct int type. // Safe integer range: -(2^53 - 1) to (2^53 - 1) console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 // For LeetCode int32 range checks const INT_MAX = 2 ** 31 - 1; // 2147483647 const INT_MIN = -(2 ** 31); // -2147483648 // Bitwise operators coerce to 32-bit signed int console.log((2147483647 + 1) | 0); // -2147483648 (wraps) console.log(2147483647 + 1); // 2147483648 (float math, no wrap) // BigInt for true 64-bit arithmetic const big = 9223372036854775807n; // BigInt literal const next = big + 1n; // 9223372036854775808n, no overflow

TypeScript

// Same float model as JavaScript const INT_MAX: number = 2 ** 31 - 1; // 2147483647 const INT_MIN: number = -(2 ** 31); // -2147483648 const LONG_MAX: bigint = 2n ** 63n - 1n; // for true 64-bit // Bitwise ops wrap to 32-bit signed const wrapped: number = (INT_MAX + 1) | 0; // -2147483648 // TypeScript does not catch arithmetic overflow at compile time let x: number = INT_MAX; x = x + 1; // 2147483648, float math, no wrap without bitwise coercion

Java

// int is always 32-bit signed. long is always 64-bit signed. int INT_MAX = Integer.MAX_VALUE; // 2147483647 int INT_MIN = Integer.MIN_VALUE; // -2147483648 long LONG_MAX = Long.MAX_VALUE; // 9223372036854775807 // Silent wraparound on int overflow int x = Integer.MAX_VALUE; x += 1; // -2147483648, no exception thrown // Binary search midpoint, always use this form int mid = lo + (hi - lo) / 2; // Cast before multiply to avoid overflow long product = (long) a * b; // cast a before the multiply // Large accumulator long sum = 0L; for (int v : values) { sum += v; } // v promoted to long automatically

C++

#include <cstdint> // int32_t, int64_t #include <climits> // INT32_MAX, INT64_MAX // Prefer fixed-width types from <cstdint> for portability int32_t a = INT32_MAX; // 2147483647 int32_t b = INT32_MIN; // -2147483648 int64_t c = INT64_MAX; // 9223372036854775807 // Signed overflow is undefined behavior, the compiler may // optimize assuming it never occurs. Do not rely on wrapping. // Safe product via explicit cast: int64_t product = (int64_t)a * b; // Overflow check before addition bool add_overflows(int32_t x, int32_t y) { return (y > 0 && x > INT32_MAX - y) || (y < 0 && x < INT32_MIN - y); }

C

#include <stdint.h> // int32_t, int64_t, fixed-width types #include <limits.h> // INT32_MAX, INT64_MAX #include <stdio.h> int main() { int32_t a = INT32_MAX; // 2147483647 int32_t b = INT32_MIN; // -2147483648 int64_t c = INT64_MAX; // 9223372036854775807 printf("int32 max: %" PRId32 "\n", a); printf("int64 max: %" PRId64 "\n", c); // Signed overflow: undefined behavior. Cast first. int64_t safe = (int64_t)a + 1; // 2147483648, safe printf("safe: %" PRId64 "\n", safe); return 0; }

Fixed-width type documentation lives at cppreference: fixed-width integer types.

Go

package main import ( "fmt" "math" ) func main() { // int is platform-dependent (int64 on 64-bit systems) // int32 and int64 are always fixed-width, use these for clarity var a int32 = math.MaxInt32 // 2147483647 var b int32 = math.MinInt32 // -2147483648 var c int64 = math.MaxInt64 // 9223372036854775807 // int32 wraps silently on overflow a++ fmt.Println(a) // -2147483648 // Safe: use int64 for products that might overflow int32 var lo, hi int64 = 1_000_000_000, 2_000_000_000 fmt.Println(lo * hi) // 2000000000000000000 }

Rust

fn main() { let a: i32 = i32::MAX; // 2147483647 let b: i32 = i32::MIN; // -2147483648 let c: i64 = i64::MAX; // 9223372036854775807 // Debug build: overflow panics // Release build: wraps silently (two's complement) // Explicit overflow control, choose the behavior you want let wrapped = i32::MAX.wrapping_add(1); // -2147483648 let saturated = i32::MAX.saturating_add(1); // 2147483647 (clamped) let checked = i32::MAX.checked_add(1); // None println!("{:?}", checked); // None // Safe product let product: i64 = 1_000_000_000_i64 * 2_000_000_000; println!("{}", product); // 2000000000000000000 }

Swift

let int32Max = Int32.max // 2147483647 let int32Min = Int32.min // -2147483648 let int64Max = Int64.max // 9223372036854775807 // Int is 64-bit on 64-bit platforms let intMax = Int.max // 9223372036854775807 // Swift traps (crashes) on overflow by default // let bad = Int32.max + 1 // runtime error // Overflow operators for explicit wrapping arithmetic let wrapped = Int32.max &+ 1 // -2147483648 let (result, overflowed) = Int32.max.addingReportingOverflow(1) print(overflowed) // true // Safe: cast to Int64 before crossing the boundary let safe: Int64 = Int64(Int32.max) + 1 // 2147483648

Kotlin

fun main() { val intMax = Int.MAX_VALUE // 2147483647 val intMin = Int.MIN_VALUE // -2147483648 val longMax = Long.MAX_VALUE // 9223372036854775807 // Int is always 32-bit. Long is always 64-bit. (JVM semantics) // Silent wrap on Int overflow, same as Java var x = Int.MAX_VALUE x += 1 println(x) // -2147483648 // Safe: cast to Long before multiplying val a = 1_000_000_000 val b = 2_000_000_000 val product = a.toLong() * b // cast a before the multiply println(product) // 2000000000000000000 }

C#

using System; // int is always 32-bit signed. long is always 64-bit signed. int.MaxValue; // 2147483647 int.MinValue; // -2147483648 long.MaxValue; // 9223372036854775807 // Default unchecked context: silent overflow int x = int.MaxValue; x += 1; Console.WriteLine(x); // -2147483648 // Checked context: throws OverflowException checked { // int bad = int.MaxValue + 1; // throws OverflowException } // Safe product long product = (long)int.MaxValue * 2; Console.WriteLine(product); // 4294967294

Ruby

# Ruby integers are arbitrary precision. No overflow. INT32_MAX = 2**31 - 1 # 2147483647 INT32_MIN = -(2**31) # -2147483648 INT64_MAX = 2**63 - 1 # 9223372036854775807 x = INT32_MAX x += 1 puts x # 2147483648, no wrapping puts x.class # Integer (unified in Ruby 2.4+, was Fixnum/Bignum before) # For byte-level int32 encoding [2147483647].pack("l<") # 4-byte little-endian int32 binary

PHP

<?php // int is platform-dependent. On 64-bit systems since PHP 7, it is 64-bit. echo PHP_INT_MAX; // 9223372036854775807 on 64-bit echo PHP_INT_MIN; // -9223372036854775808 on 64-bit echo PHP_INT_SIZE; // 8 (bytes) // On overflow, PHP silently converts to float, precision loss begins $x = PHP_INT_MAX; $x += 1; var_dump($x); // float(9.2233720368548E+18) // LeetCode int32 range check (needed when the problem specifies 32-bit) $INT32_MAX = 2**31 - 1; // 2147483647 $INT32_MIN = -(2**31); // -2147483648 function isInt32(int $n): bool { return $n >= -2147483648 && $n <= 2147483647; } ?>

The Two-Second Mental Check That Prevents All of This

When you're coding live and arithmetic is involved, a brief mental check catches most overflow bugs:

  1. Check the constraints. Inputs up to 10^9 multiplied together reach 10^18. Reach for int64.
  2. Write every binary search midpoint as lo + (hi - lo) / 2. No exceptions. The JDK got this wrong for nine years.
  3. If the problem says "return modulo 10^9 + 7," your intermediate values before the modulus can overflow int32. Use int64 throughout and take the modulus at each step.
  4. In Java, Kotlin, or C#, declare the accumulator as int64 before the loop starts, not after you notice the wrong answer.
  5. In C++, signed overflow is undefined behavior, not a guaranteed wrap.

Then narrate it. Say "inputs go up to 10^9 and I'm multiplying two of them, so I'll use long here." That one sentence is evidence an interviewer can actually write down. An interviewer writing up feedback wants concrete proof of overflow awareness, not just a solution that happened to avoid it. That kind of live narration is exactly what voice-based mock interviews at SpaceComplexity are built for.


Further Reading