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.