Why is `a is b` True for `a = 256; b = 256` but False for 257 in CPython?
answer
- Identity, not value, is being asked
- Some values are made once at startup
- A fixed window of small integers
- Runs from -5 up to 256 inclusive
- An implementation detail, never a rule
basics
~20 sCPython pre-allocates one shared int object for every value from -5 to 256, so all names holding 256 point to that one object. 257 is built fresh each time, so two such names hold equal but distinct objects.
solid answer
~40 sAt startup CPython creates a fixed array of `int` objects for the small values **-5 through 256 inclusive** and hands the same object out every time one of those values is produced. So two names bound to 256 are the same object and `is` reports True. 257 falls outside that cache, so each evaluation allocates a new `int`, and two names holding 257 are equal in value but different objects. None of this is part of the language: it is a CPython memory optimisation, other implementations may cache different ranges, and the cache is invisible when you compare with `==`. Since 3.12 these cached ints are also immortal objects, so their reference counts never change. Treat identity of numbers as an implementation detail you never build logic on.
code
pycon · 10 lines>>> a = 256
>>> b = 256
>>> a is b
True
>>> c = 257
>>> d = 257
>>> c is d
False
>>> c == d
Truego deeper
Recall the shape of the answer: CPython makes one shared object for a small range of integers, so equal small numbers can be the identical object while larger ones are not. Say the range is roughly -5 to 256 and that you compare numbers with ==.
Explain the mechanics: a pre-allocated array consulted whenever an integer object is created, covering -5 through 256 inclusive, independent of whether the value came from a literal or from arithmetic. Note that it is an unspecified implementation detail.
Show why it bites in production: identity checks that pass on small test fixtures and fail on real values above the window, and why such failures read as data corruption rather than as a bug in the comparison. Mention the compile-time warning that catches the literal form.
Own the policy angle: identity-dependent behaviour is a portability and upgrade hazard across implementations and versions, so make the compiler's SyntaxWarning an error in CI and lint for it rather than relying on reviewers to spot is used against a number.
### Two different questions about one value Every Python object has a value and an identity. `==` asks the objects whether their values are equal; `is` asks whether two expressions produced the *same object*, which you can see directly with `id()`. For integers those two questions usually give the same answer by accident, and the accident is a CPython memory optimisation called the small-integer cache. ### What CPython actually pre-allocates When the interpreter starts it builds a fixed array of `int` objects covering the values **-5 through 256 inclusive** — 262 objects in total. Whenever any code path would produce an integer in that window, whether by evaluating a literal, by arithmetic, by parsing a string, or by returning from C code, CPython hands back a pointer to the pre-built object instead of allocating a new one. That is why every 256 in a program is the very same object. 257 is one past the top of the window. Each time a 257 is produced at run time, CPython allocates a fresh `int` object, so two independently produced 257s are distinct objects and `is` is False even though `==` is True. ### Why that range The window is chosen from how real programs behave: loop counters, list indices, small counts, flags, exit codes and character values are overwhelmingly small and positive, and a handful of small negatives (-1 as a sentinel, -1 from comparisons) recur constantly. Allocating and freeing a heap object for every loop counter would be pure waste, so the common values are made once and shared. The lower bound of -5 and the upper bound of 256 are historical tuning constants, not a principle; the exact numbers have moved before and could move again. ### It is an implementation detail, not a language rule The Python language reference guarantees nothing about integer identity. It says only that two names may or may not refer to the same object for immutable values — precisely so implementations are free to cache. Another Python implementation may cache a different range, may cache nothing, or may cache aggressively across the whole program. Code that depends on `x is 256` is not portable, and it is not even stable across CPython versions. ### The confusing part: a script can print True for 257 If you put `x = 257` and `y = 257` on the same line, or in the same function body, a script may print True. That is a *different* mechanism: the compiler stores each distinct constant once inside a compiled code object, so both names load the identical constant. Type the same two assignments as separate lines in the interactive interpreter and you get False, because each statement is compiled on its own. So integer identity depends on the cache, on the compiler, and on how the code was entered — three reasons never to depend on it. ### Immortality since 3.12 Python 3.12 made these cached small integers immortal objects (PEP 683): their reference counts are never incremented or decremented. That matters mostly for the free-threaded build, officially supported since 3.14, where every reference-count update on a shared object is contention between threads. Immortal small ints let many threads read the same 5 without touching a shared counter. From ordinary code the effect is invisible. ### The practical rule Compare numbers with `==`. Reserve `is` for `None`, `True`, `False` and unique sentinel objects, where identity is exactly what you mean. CPython helps you here: writing `is` against a numeric or string literal raises a `SyntaxWarning` at compile time that names the literal type and suggests `==`. If a test or a piece of production logic passes only because two equal small numbers happened to be the same object, it will fail the first time the data grows past 256 — and that failure looks like a data bug, not a code bug.
- Does the cache apply to integers your program computes, or only to literals you type?To any integer CPython produces whose value lands in the window, however it was produced: arithmetic, `int()` on a string, or a return value from C code all hand back the shared object. The cache is checked when the `int` is created, not when the source is parsed, so a computed 100 and a literal 100 are the same object.
- If `x is 256` works today, why is it still a bug?Because nothing promises it. The range is an unspecified CPython tuning constant, another implementation may cache differently, and the moment the value can exceed 256 the comparison silently starts returning False. CPython even warns at compile time when you write `is` against a literal. `==` is both correct and no slower for ints.
- What does `id()` tell you about two equal integers?`id()` returns a value unique among currently-alive objects — in CPython, the object's address. Equal `id()` for two names means they are the same object; different `id()` means two objects that may still compare equal. Because objects can be freed and the address reused, an `id()` captured after the object dies is meaningless.
A hotel keeps 262 pre-printed key cards for its most-requested rooms and hands out the same card each time; ask for anything outside that set and it prints a brand-new card every time.
saying these in an interview costs you the question
- Claims Python caches all integers, or all small objects
- States the cache range as 0 to 255
- Says 257 is a different value, not a different object
- Treats the cache as a documented language guarantee
- Concludes that `is` is a valid way to compare numbers
- Assumes `id()` values are stable and reusable identifiers