datarekha
Python Easy Asked at AmazonAsked at GoogleAsked at Microsoft

What is the difference between `is` and `==` in Python, and when does `is` give a surprising result?

The short answer

`==` calls `__eq__` and tests value equality. `is` tests object identity — whether two names point to the exact same object in memory. The surprise comes from CPython's interning: small integers (-5 to 256) and many short strings are cached, so `is` returns True even for separately created objects, but this is an implementation detail you must never rely on.

How to think about it

This is a classic probe of whether you understand Python’s object model, not just its syntax. Every value in Python is an object living somewhere in memory, and a variable is just a name pointing at one. == asks whether two objects represent the same thing; is asks whether two names point at the exact same object.

Picture two houses built from one floor plan. == asks “do the layouts match?” — yes. is asks “is this literally the same building?” — no. Concretely, == calls the object’s __eq__ method (which any type can override to define equality), while is compares identity directly — no method call, nothing to override.

a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b   # True  — same values
a is b   # False — different objects in memory
a is c   # True  — c is just another name for the same list

A worked example — including the interning surprise

# Identity vs equality
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print("a == b :", a == b)        # same values
print("a is b :", a is b)        # different objects
print("a is c :", a is c)        # same object, two names

print()

# CPython caches the small integers -5..256, so they're shared singletons.
# int("...") forces a fresh object each time, exposing the cache boundary:
print("256:", int("256") is int("256"))   # True  — 256 is cached
print("257:", int("257") is int("257"))   # False — 257 is built fresh

print()

# The only correct use of `is`: comparing against a singleton
result = None
print("result is None:", result is None)   # idiomatic and safe
a == b : True
a is b : False
a is c : True

256: True
257: False

result is None: True

That middle block is the trap interviewers love. 256 comes back True and 257 comes back False — not because of anything about the numbers, but because CPython happens to cache the integers from −5 to 256. It’s a memory optimisation, not a language guarantee.

Why interning exists

Those small integers — loop counters, lengths, flags — turn up constantly, so CPython pre-builds them once and hands out the same object every time, saving memory and construction cost. The same trick applies to many short, identifier-like strings fixed at compile time. But it is a runtime implementation detail: PyPy or Jython may differ, and even CPython can change it between releases. The honest one-liner is that is boils down to “do these two have the same identity?”, and identity is only stable for an object’s lifetime.

The only safe uses of is

if result is None: ...
if obj is NotImplemented: ...
# (and prefer a plain `if flag:` over `if flag is True:`)
Learn it properly Variables & Types

Keep practising

All Python questions

Explore further

Skip to content