04 / 12

Why can `a = 256; b = 256; a is b` return `True`, but `a = 257; b = 257; a is b` return `False` in some Python implementations?

Difficulty: 8/10
Variables, Data Types & Memory Model, Small Integer Caching, Interning and Identity

CPython small-integer cache and why it must not be relied on

CPython preallocates one int object for each value from -5 to 256 at startup and reuses those objects everywhere, because small ints are extremely common and allocating and freeing them constantly would be wasteful. So 256 always resolves to the same object and is returns True, while 257 is created as a fresh object each time it is computed, so two separate creations give two different objects. This is a CPython implementation detail, not language semantics: PyPy, other runtimes and even other CPython versions can behave differently.

The subtlety that trips up good candidates: even 257 can show True. The compiler stores literals in a code object's constants, and identical constants in the same compilation unit are merged. So a = 257; b = 257 on one line, or inside a single script or function, usually prints True, while typing the two assignments on separate lines in the REPL (each compiled separately) prints False. Constant folding matters too: 2**8 + 1 is folded at compile time. To demonstrate the cache without compile-time effects, build the ints at run time.

javascript

Version-dependent notes: since 3.12 small ints and some interned strings are 'immortal' (PEP 683), meaning their reference counts are never modified, which helps copy-on-write sharing after fork and is also needed for free-threaded builds. The cached range is an implementation constant, not a documented guarantee. Strings have an analogous but separate mechanism: identifier-like literals are interned automatically and sys.intern lets you intern others explicitly.

The practical takeaway: never use is to compare numbers or strings. Use ==. Valid uses of this knowledge are interview explanations, memory optimization via explicit interning of repeated strings, and recognising flaky tests that compare with is. Python 3.8+ warns on code like x is 200 with a SyntaxWarning for precisely this reason.

Scenario Questions

0-2 years experience

  1. 1A unit test using assert a is b on two integers passes locally but fails in CI. What could explain that, and what would you advise?
  2. 2Which integer range does CPython cache, and what do int('256') is int('256') versus int('257') is int('257') demonstrate?

2-5 years experience

  1. 1Compare 'hello' is 'hello' with two strings built at run time using join. How does string interning differ from the small-int cache, and when would you use sys.intern?
  2. 2A code review finds if status is 200. Explain the risk, mention the warning newer Python versions emit, and give the fix.

5-8 years experience

  1. 1Would you expect the same is results on PyPy or in a free-threaded 3.13 build? How do you write code that never depends on these implementation details?
  2. 2A memory profile shows millions of duplicate strings after parsing a large CSV. What interning or deduplication strategies would you try and what are their trade-offs?

8+ years experience

  1. 1Explain how immortal objects (PEP 683, Python 3.12) change reference counting for small ints and interned strings, and why that matters for forked workers and free-threading.
  2. 2Predict and justify the result of is for equal ints produced by (a) two literals in one function, (b) a literal at module level versus inside a function, and (c) 2**8 + 1 versus int('257'). What compiler behaviour explains each?

Follow-up Questions

  • What is string interning, and how does sys.intern differ from the small-int cache?
  • How did PEP 683 (immortal objects) change the lifecycle of cached objects in 3.12?
Share

Share via WhatsApp, X, Facebook, LinkedIn or copy link. Open Graph preview enabled.