Identity, is vs ==, & Interning Interview Questions & Answers
6 questions Updated 2026-06-18
Python interview questions on object identity: is vs ==, the id() function, the small-int cache, string interning, and the correct use of is for None and sentinels.
== tests value equality — "do these represent the same data?" — by
calling the object's __eq__. is tests identity — "are these the
exact same object in memory?" — which is effectively an id() comparison. They
frequently agree, but conceptually they ask completely different questions.
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True — equal contents
a is b # False — two distinct list objects
c = a
c is a # True — same object, just another name
Use == whenever you care about the value, which is almost always. Reserve
is for identity checks against singletons. Rule of thumb: if you're
comparing data, use ==; if you're checking "is this literally that object," use
is.
id(obj) returns a unique integer identity for an object that's constant
for the object's lifetime. In CPython it's the object's memory
address, though that's an implementation detail. Two names with the same id
refer to the same object, and is is essentially an id comparison.
a = [1, 2]
b = a
id(a) == id(b) # True — same object
a is b # True — equivalent check
c = [1, 2]
id(a) == id(c) # False — equal value, different object
id is handy for understanding aliasing — why mutating through one name
shows up through another. Don't rely on the actual numeric value (it can be
reused after an object is garbage-collected); use it only to reason about
sameness.
CPython pre-creates and caches integers from −5 to 256 as singletons at
startup. Any time a value in that range is needed, the same cached object is
reused — so equal small ints share identity and is returns True. Outside that
window, equal integers are typically distinct objects.
a = 256
b = 256
a is b # True — both point at the cached 256
c = 257
d = 257
c is d # often False — separate objects
c == d # True — always compare values with ==
This is purely a memory/performance optimization and a CPython
implementation detail — not a language guarantee. It's the single biggest reason
is "appears" to work on numbers. Rule of thumb: never use is to compare ints,
use ==.
Interning stores a single shared copy of a string so identical strings
can be the same object, saving memory and making equality checks a fast
pointer comparison. CPython auto-interns short, identifier-like string
literals (and compile-time constants); other strings may not be. You can force it
with sys.intern.
a = "hello"
b = "hello"
a is b # True — auto-interned literal
c = "hello world!"
d = "hello world!"
c is d # often False — not auto-interned
import sys
e = sys.intern("hello world!")
f = sys.intern("hello world!")
e is f # True — explicitly interned
sys.intern is useful when you compare the same strings repeatedly (parsing,
tokenizing, dict keys) and want the speed/memory win. Like int caching, which
strings auto-intern is an implementation detail — never rely on it for
correctness.
is sometimes returns True for equal values only because CPython caches or
interns those particular objects — small ints (−5..256) and many short string
literals share one object. It's an accident of optimization, not equality, so it
breaks the moment you leave the cached range.
x = 100
x is 100 # True — cached small int (deceiving!)
y = 1000
y is 1000 # often False — outside the cache
y == 1000 # True — the correct comparison
The danger is that code passes during testing with small values and then fails in
production with larger ones. Treat any is-on-a-value that works as a lucky
coincidence. Rule of thumb: if swapping is for == would change behavior on
some input, you should have used ==.
Use is to test identity against singletons — objects of which there is
exactly one — most commonly None, but also True, False, and your own
sentinel objects. For singletons is is correct, fast, and can't be fooled by
a custom __eq__.
if x is None: # idiomatic, recommended by PEP 8
...
_MISSING = object() # unique sentinel
def get(d, key, default=_MISSING):
val = d.get(key, _MISSING)
if val is _MISSING: # distinguishes "absent" from "value is None"
return default
return val
A sentinel like object() is ideal precisely because is checks identity — no
other object can ever match it. Rule of thumb: is/is not for None and
sentinels; ==/!= for everything else.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.