14. Bit Manipulation & Numeric Edge Cases

Handling fixed-width overflow, 32-bit coercion, modulo/division sign rules, and bit-counting helpers correctly in each runtime's numeric model.

Bit-level and numeric code is where each language's underlying number representation stops being an abstraction and starts mattering: fixed-width wraparound, sign-preserving shifts, and modulo direction all differ in ways that silently produce wrong answers rather than throwing. This section is the mechanical reference for wielding integers and bits correctly and fast in Python, Java, Go, and JavaScript — not why any specific bit trick or numeric identity works algorithmically.

See roadmap: Bit Manipulation

Language Verdict: Pros, Cons & Recommendation

Python

4/5
  • Arbitrary-precision int never overflows or wraps — an entire class of bit-manipulation bugs simply doesn't exist
  • % is true mathematical modulo (sign matches the divisor), giving "wrap into [0, n)" behavior for free with no normalization needed
  • int.bit_count() (3.10+) and bit_length() cover popcount and bit-width inspection without a hand-rolled loop
  • No BigInteger-style separate type needed for huge values — the same int you already use everywhere covers the full range
  • No fixed bit width means no >>> unsigned-shift operator and no native 32-bit truncation — bit tricks relying on wraparound must be simulated with explicit masking
  • Only bit_count()/bit_length() are built in — there's no direct equivalent of numberOfLeadingZeros/highestOneBit without deriving it yourself from bit_length()

Java

5/5
  • Integer.bitCount/numberOfTrailingZeros/numberOfLeadingZeros/highestOneBit are built-in, hardware-backed (POPCNT/LZCNT) static methods — no hand-rolled loop ever needed
  • int/long are natively 32-/64-bit two's complement, matching bitwise-operator semantics exactly with no separate coercion step
  • Math.floorDiv/Math.floorMod give explicit, named floor-rounding alternatives to truncating / and dividend-signed %
  • BigInteger provides a real arbitrary-precision escape hatch when a computation can exceed 64 bits
  • No arbitrary-precision native type — silent int/long overflow wraparound is a real, easy-to-miss correctness bug
  • BigInteger has no operator overloading, so arbitrary-precision code balloons into verbose .add()/.multiply() method chains

Go

5/5
  • math/bits (OnesCount64, TrailingZeros64, LeadingZeros64, Len64) is a first-class, hardware-backed toolkit
  • 1 << i is well-defined on uint/uint64 (logical, zero-fill); ^uint64(0) is the all-bits-set idiom
  • Typed int64/uint64 make width explicit; large shifts zero the value instead of wrapping the count modulo width
  • No 32-bit coercion detour — bitwise ops use the operand's actual type
  • int/uint size is platform-dependent — prefer int64/uint64 when width matters, and prefer bits.*64 so leading-zero counts don't change underfoot
  • % follows the dividend's sign (like Java, unlike Python) — circular-index code needs ((a%n)+n)%n
  • Bitwise NOT is unary ^, not ~; there is no >>> — unsigned-ness is the type, not the operator

JavaScript

2/5
  • Math.clz32 gives a built-in, hardware-backed leading-zero count for 32-bit values
  • BigInt provides an arbitrary-precision escape hatch with concise 10n literal syntax once you opt in
  • >>> (unsigned right shift) is available directly, matching Java's semantics for treating a value as unsigned 32-bit
  • Math.floor/Math.trunc give explicit control over rounding direction since / always returns a float
  • Every bitwise operator (&, |, ^, ~, <<, >>) silently coerces both operands through 32-bit ToInt32 and back — a genuinely JS-specific detour absent from the other two languages
  • No built-in popcount, trailing-zero-count, or highest-set-bit helper — only Math.clz32 exists, everything else (e.g. Kernighan's x &= x - 1) must be hand-rolled
  • Numbers silently lose precision past Number.MAX_SAFE_INTEGER (2^53 - 1) instead of raising or wrapping, with no clean overflow threshold to test against
  • BigInt cannot be mixed with Number in arithmetic — 1n + 1 throws TypeError, requiring explicit conversions everywhere
Recommendation: Python remains the typical DSA default — arbitrary precision removes overflow bugs. Java's Integer/Long helpers and Go's math/bits plus int64/uint64 are the most ergonomic for heavy bit manipulation; prefer Go when you want well-defined 1 << i on uint. Treat JS bitwise operators as an implicit 32-bit coercion.

Coding Mechanics, Side by Side

Fixed-width overflow: wraparound vs. arbitrary precision vs. silent precision loss

Must-know

The four languages fail in genuinely different ways once a sum/product gets large — none of them raise a catchable exception, which is exactly what makes this dangerous. See the Language Fundamentals section for the base-level int/float representation details; this is the bit-manipulation-flavored version, focused on what happens during shifts and packed-bit arithmetic specifically.

a = 2 ** 62 print(a + 1) # keeps growing exactly, never wraps print((1 << 100) * 4) # still exact, arbitrary precision throughout

Python ints have arbitrary precision and never overflow, full stop — there is no wraparound to guard against, even for huge shift amounts or products. This is a genuine simplification during interviews (one less class of bug to worry about), but mention out loud that a Java/C++ port of the same code would need overflow handling, since interviewers sometimes probe for that awareness even in a Python interview.

Bitwise operators force 32-bit signed coercion (JS-specific quirk)

Must-know

JS numbers are 64-bit floats everywhere else in the language, but its bitwise operators secretly convert both operands to 32-bit signed integers first — a genuinely JS-specific detour that Java and Go don't need since their bitwise semantics already match their native integer width, and Python doesn't need because it has no fixed width at all.

print(~5) # -6 -- true arbitrary-precision two's complement, no fixed width at all print(5 & 3) # 1 print(-1 >> 1) # -1 -- arithmetic shift, conceptually infinite sign-extension

Python's bitwise operators work on conceptually infinite-precision two's complement integers — ~5 is still -6 (same result as Java/JS), but there is no 32-bit truncation happening anywhere, and no unsigned-shift operator at all (>>> doesn't exist in Python) because there's no fixed bit width for "unsigned" to be relative to. This means Python is immune to the 32-bit truncation surprises covered later in this section, but it also means porting a bit-manipulation solution from Python to Java/JS requires actively thinking about the 32-bit boundary that Python let you ignore.

Arbitrary-precision workarounds once you exceed native integer range

Optional

Java, Go, and JS need an explicit escape hatch to arbitrary precision — Python's native int already covers this case, no special type required.

# No special type needed -- native int already handles this exactly. huge = 2 ** 200 print(huge * huge) # still exact, still a plain int, no import or suffix required

This is the one place where Python's simplicity is the whole story: there is no separate arbitrary-precision type to learn, no literal suffix, no mixing restriction — the native int you already use for everything else already covers the full range other languages need BigInt/BigInteger for. The only cost is that this convenience is easy to forget mid-interview — the exact same code would need an explicit workaround in Java or JS.

Negative-number modulo: true modulo vs. remainder

Must-know

One of the most interview-relevant numeric gotchas there is: Python's % and Java/Go/JS's % disagree on the sign of the result for negative operands, and it silently breaks circular-buffer, hashing, and clock-arithmetic code that assumes one behavior universally.

print(-7 % 3) # 2 -- Python's % always matches the sign of the DIVISOR print(7 % -3) # -2 -- divisor is negative, result is negative

Python's % is true mathematical modulo: the result always has the same sign as the divisor, regardless of the dividend's sign. -7 % 3 == 2 (not -1), which is exactly the "wrap into [0, n)" behavior most circular-index or hashing code silently assumes — Python gives you that behavior for free, with no extra adjustment needed.

Integer division: which way does it round?

Recommended

Three different rounding directions for the exact same operation on negative operands — a classic, easy-to-miss source of off-by-one bugs when porting code between languages.

print(-7 // 2) # -4 -- floor division, rounds toward negative infinity print(7 // -2) # -4 print(7 // 2) # 3

// always floors toward negative infinity, for either operand's sign. -7 // 2 == -4 (not -3) — worth double-checking any binary-search midpoint or divide-and-conquer split that touches negative ranges, since this differs from Java's truncating /.

Built-in bit-counting & inspection helpers

Recommended

Java ships a rich Integer/Long toolkit for this; Go's math/bits is equally complete (prefer the 64-suffixed functions); Python has a couple of built-ins plus an easy manual fallback; JS gives you almost nothing and expects you to hand-roll it.

x = 0b10110 print(x.bit_count()) # 3 -- popcount, Python 3.10+ print(bin(x).count('1')) # 3 -- portable fallback for < 3.10 print(x.bit_length()) # 5 -- number of bits needed to represent x

int.bit_count() (3.10+) is the direct popcount equivalent to Java's bitCount; on older Python, bin(x).count('1') is the standard fallback (allocates a string, so it's slower, but fine for interview-scale inputs). bit_length() gives you the number of bits needed to represent the number — one more than the index of the highest set bit — useful for the same purposes as Java's highestOneBit/numberOfLeadingZeros, though the exact value returned differs (a count vs. a bit-position vs. a masked value), so don't treat them as drop-in equivalents.

Checking, setting, clearing, and toggling a single bit

Must-know

The mechanical syntax for the four basic single-bit operations is essentially identical across Python, Java, Go, and JavaScript — this is a case where the pattern, not the language, is the thing to memorize:

is_set = (x & (1 << i)) != 0 x |= (1 << i) # set bit i x &= ~(1 << i) # clear bit i x ^= (1 << i) # toggle bit i
boolean isSet = (x & (1 << i)) != 0; x |= (1 << i); // set bit i x &= ~(1 << i); // clear bit i x ^= (1 << i); // toggle bit i
isSet := x&(1<<i) != 0 x |= 1 << i // set bit i; well-defined on uint/uint64 x &^= 1 << i // clear bit i (AND-NOT); or x &= ^(1 << i) x ^= 1 << i // toggle bit i
const isSet = (x & (1 << i)) !== 0; x |= (1 << i); // set bit i x &= ~(1 << i); // clear bit i x ^= (1 << i); // toggle bit i

The one caveat, specific to JavaScript: since every bitwise operator coerces through a 32-bit signed integer first (see the coercion block above), this exact syntax silently stops working correctly once i reaches 31 or higher — 1 << 31 is already the minimum 32-bit signed integer (negative), and 1 << 32 wraps back around to 1 (covered in the next block). Java's int version hits the identical 32-bit ceiling for the same underlying reason; Go's uint64/int64 version is well-defined (1 << i zeros once i reaches the bit width, and &^= is the dedicated AND-NOT/clear operator). Only Python's arbitrary-precision integers have no such limit on how large i can be.

Shifting by an amount >= the type's bit width

Recommended

A genuinely surprising point of agreement between Java and JS: both silently reduce the shift amount modulo the type width instead of producing zero — Go zeros the value instead, and Python has no such surprise because it has no fixed width to wrap around.

print(1 << 32) # 4294967296 -- no wraparound, just a large exact integer print(1 << 100) # a genuinely huge (but exact) integer, no ceiling at all

Python has no fixed integer width to wrap the shift amount against, so 1 << 32 is simply the large arbitrary-precision integer you'd mathematically expect — no surprise, no special-casing. This is consistent with every other "no fixed width" behavior in this section, but it's the one case where Java and JS's shared quirk (not Python's) is the one to actively remember when porting shift-heavy code between languages.

Further Reading