skip to content

Integers, Bit Operations and bool

Python ints grow to any size, so the overflow answers you give in other languages do not apply here. You should also know bool is literally a subclass of int, and what that does to sums and dict keys.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

Why can a Python int hold 2 ** 200 without overflowing, and what does that cost?

level: juniorimportance: must knowfreq 60%

answer

  1. One integer type, no width
  2. It grows until memory says no
  3. Sign plus a digit array in CPython
  4. bit_length tells you how big
  5. Cost is memory and non-constant-time math

basics

~20 s

Python 3 has one integer type and it is arbitrary precision: CPython stores a sign plus a variable-length array of digits and grows it on demand, so arithmetic never wraps. You pay in memory and in math that is slower than machine words.

solid answer

~50 s

There is a single integer type in Python 3 and it has no fixed width. CPython represents an `int` as a sign plus a variable-length array of 30-bit digits, and every operation allocates as many digits as the result needs, so `2 ** 200` is exact and a counter never wraps into a negative number. There is no maximum-integer constant to guard against: `sys.maxsize` is the largest index the platform can address, not a limit on `int`. What you pay is space and time - a big value costs bytes proportional to its bit count, which `int.bit_length()` reports, addition is linear in the digits and multiplication is worse than linear, so hot loops over thousand-bit numbers are visibly slower than machine arithmetic. Fixed width reappears only at boundaries: `int.to_bytes()`, the `struct` module, a 64-bit database column, or a consumer written in another language.

code

pycon · 9 lines
pycon
>>> 2 ** 200
1606938044258990275541962092341162602522202993782792835301376
>>> (2 ** 200).bit_length()
201
>>> (2 ** 200).bit_count()
1
>>> import sys
>>> sys.getsizeof(2 ** 200) > sys.getsizeof(2)
True

go deeper

for a junior

Be ready to say plainly that Python 3 has one integer type with no fixed width, that it never overflows, and that its only limit is available memory. Knowing bit_length() as the way to measure one is a bonus.

for a middle

Explain the mechanics: a sign plus a variable-length digit array, immutability so every result is a new object, and the fact that addition and multiplication are not constant time. Name sys.maxsize as an address-space value, not a numeric ceiling.

for a senior

Show where the width comes back: fixed-width columns, binary formats, JSON consumers parsing into doubles, and C extensions. Demonstrate that you guard the boundary rather than the Python-side arithmetic, and that you know the cost of large values in hot loops.

for a principal

Own the tradeoff of exactness versus throughput across a system: where unbounded integers are the right default, where numeric work should move to compiled or fixed-width representations, and how you keep a value's width contract visible at every interface it crosses.

## One integer type, no width Python 3 has exactly one built-in integer type, `int`, and it is arbitrary precision - what other ecosystems call a big integer. Python 2 had two types, a machine-width integer and an unbounded long that the interpreter promoted to automatically; PEP 237 merged them, and the survivor is the unbounded one. So the overflow answers you would give in C, Java or Go simply do not apply: there is no wraparound, no undefined behaviour, no signed/unsigned distinction and no width to choose. `2 ** 200`, a factorial of 5000, or a cryptographic-size modulus are all ordinary values. ## How CPython stores it A CPython `int` object is an object header plus a sign-and-size field plus an array of digits. On a typical 64-bit build each digit holds 30 bits (some builds use 15), so the value is stored base 2**30, little-digit-first. Growing a number means allocating a bigger object - integers are immutable, so every arithmetic result is a fresh object. Two conveniences sit on top: values from -5 to 256 are preallocated and shared, so those objects are reused rather than allocated, and `sys.getsizeof()` will show you the object growing as the value does. ```pycon >>> import sys >>> sys.getsizeof(1) < sys.getsizeof(2 ** 200) True ``` ## What that buys you The entire class of overflow bugs that dominates fixed-width languages does not exist inside Python. An id sequence does not exhaust a 32-bit space, an accumulating counter does not flip negative, an intermediate product in a sum does not silently truncate. Because the type is exact, integer arithmetic is also a safe place to do money arithmetic in minor units, checksum accumulation, or index math on very large collections without reasoning about ranges. The failure mode moves outward rather than disappearing. The size only matters when the number crosses a boundary: a `BIGINT` column, a JSON consumer whose parser stores numbers as doubles, a C extension expecting a machine word, or a fixed-width binary format. At those boundaries you either get an explicit error - `int.to_bytes()` raises `OverflowError` when the value does not fit the byte length you asked for - or silent truncation on the other side. That is why the interesting review question about a Python integer is never 'can it overflow' but 'what is the narrowest thing that will ever read it'. ## What it costs Memory: proportional to the bit count, plus the object header. A list of a million small integers is fine; a list of a million 4096-bit integers is not. Time: operations are not constant time. Addition and subtraction are linear in the number of digits, multiplication uses a schoolbook algorithm for small operands and switches to a subquadratic Karatsuba algorithm above a threshold, and division is more expensive still. Converting between binary digits and a decimal string is superlinear for very large values. None of this matters at everyday magnitudes, and all of it matters in a tight numeric loop - which is exactly why numeric-heavy work is usually pushed down into a compiled extension that uses machine-width or vectorised types. Hashing stays cheap and consistent: equal integers hash equally regardless of magnitude, because the hash reduces the value modulo a Mersenne prime rather than truncating it. ## Measuring an int `int.bit_length()` gives the number of bits needed to represent the absolute value, excluding the sign and leading zeros - `(2 ** 200).bit_length()` is 201, and `(0).bit_length()` is 0. `int.bit_count()`, added in Python 3.10, returns how many of those bits are ones, again for the absolute value. Both are the honest way to ask how big a value has become, and both are far better than comparing against an invented constant. ## Interview traps The two mistakes an interviewer listens for are 'Python ints wrap at 64 bits' and '`sys.maxsize` is the largest integer'. The first is a leftover instinct from another language; the second confuses an address-space quantity with a numeric limit. The right frame is: an `int` is bounded only by available memory, its operations are not O(1), and fixed width is something you opt into at the edges of the process rather than something the language imposes.

  • How do you ask a Python int how many bits it occupies, and how many of them are set?
    `int.bit_length()` returns the bits needed for the absolute value, excluding sign and leading zeros, so `(255).bit_length()` is 8 and `(0).bit_length()` is 0. `int.bit_count()`, added in Python 3.10, returns the number of one bits in the absolute value. Neither counts a sign bit, because a Python int has no fixed width in which a sign bit would live.
  • Does arbitrary precision mean big-integer arithmetic costs the same as machine-word arithmetic?
    No. Addition and subtraction are linear in the number of internal digits, multiplication uses a schoolbook algorithm and switches to a subquadratic one above a size threshold, and division is worse. At everyday magnitudes the difference is invisible; in a loop over thousand-bit values it dominates. Small values from -5 to 256 are preallocated and shared, so the cheap case is genuinely cheap.
  • If Python ints never overflow, why would an integer still break a production system?
    Because something narrower eventually reads it. A value that outgrows a 64-bit database column, a JSON consumer that parses numbers as doubles, a C extension expecting a machine word, or a fixed-width binary record will each fail or truncate. Python raises `OverflowError` where it can - for example `int.to_bytes()` with too small a length - but a downstream system in another language may just silently lose the top bits.

A fixed-width integer is a car odometer that rolls over at 999999; a Python int is a paper ledger where you simply write another digit on the end.

saying these in an interview costs you the question

  • Says Python ints wrap at 64 bits like C longs
  • Thinks sys.maxsize is the biggest int Python can hold
  • Assumes big-integer arithmetic is constant time
  • Believes Python 3 still has a separate long type
  • Expects a large positive value to flip negative

context

open as a page

Why is bool a subclass of int in Python, and where does that bite?

level: middleimportance: must knowfreq 55%

basics

~20 s

bool inherits from int, with True equal to 1 and False equal to 0. So booleans do arithmetic, sum() counts them as flags, isinstance(x, int) accepts them, and True collides with 1 as a dictionary key.

open as a page

With int.to_bytes, what must a binary-index writer pin so another machine decodes the same values?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Pin three things and record them in the format: the byte length, the byteorder, and whether the value is signed. int.from_bytes must use the identical three. A value too large for the length raises OverflowError; a mismatched byteorder corrupts silently.

open as a page

What does ~n return for a Python int, and how do you get fixed-width bitwise results?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

In Python, ~n is -n - 1, so ~5 is -6 and never 250. Ints behave as two's complement with an infinite run of sign bits, so mask with & ((1 << width) - 1) whenever you need a fixed-width answer.

open as a page