Skip to content

Python · Memory & Internals

Python == vs is — Equality vs Identity and the Interning Trap

5 min read Updated 2026-06-21 Share:

Practice == vs is — Equality vs Identity interview questions

Two questions that look the same but aren't

Every Python developer eventually writes if x is "something" or if count is 0 and is surprised when it sometimes works and sometimes doesn't. The root cause is that == and is ask completely different questions — and CPython's internal optimisations quietly make is appear correct on certain values, hiding the real distinction until production.

Quick-reference comparison

== (equality)is (identity)
Question asked"Do these have the same value?""Are these the exact same object in memory?"
Calls__eq__ on the left operandCompares id() — no method call
Two equal objectsTrueFalse (usually)
Same objectTrueTrue (always)
Can be overridden?Yes — __eq__ can return anythingNo — pure identity, unoverridable
Use forValues, data, contentNone, True, False, sentinels

What == does

== calls __eq__ on the left operand. The object decides what "equal" means. Two completely separate objects with the same content compare as equal:

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

a == b      # True  — same contents
a is b      # False — two different objects in memory
id(a) == id(b)   # False — different addresses

Because __eq__ is a method, it can be overridden to return anything — including lying:

class Trickster:
    def __eq__(self, other): return True   # always "equal"

t = Trickster()
t == None      # True  — misleading!
t is None      # False — identity can't be fooled

What is does

is compares object identity — specifically id(a) == id(b). In CPython, id() is the memory address. Two names refer to the same object if and only if they point at the same address.

a = [1, 2, 3]
b = a          # b is an alias for a, not a copy

b is a         # True  — same object
b == a         # True  — same contents too

c = a[:]       # shallow copy — new object
c is a         # False — new list
c == a         # True  — same contents

The interning trap — when is appears to work on values

CPython pre-creates singleton objects for performance. This makes is return True on certain values even when comparing two separate "literals" — a dangerous coincidence that breaks outside the cached range:

# Small integers (-5 to 256) are cached singletons
a = 100; b = 100
a is b          # True  — same cached object (lucky!)

a = 300; b = 300
a is b          # False — outside the cache, two objects

# Short identifier-like strings are auto-interned
x = "hello"; y = "hello"
x is y          # True  — auto-interned (lucky!)

x = "hello world!"; y = "hello world!"
x is y          # often False — not auto-interned
x == y          # True  — always the correct comparison

Why this is dangerous: code with is 100 passes all tests (small values) and silently breaks in production with larger inputs.

The correct uses of is

is is correct exactly when you're checking against a true singleton — an object of which only one instance can ever exist:

# None is a singleton — is is idiomatic and unambiguous
if result is None:
    ...

# PEP 8 mandates is/is not for None and the bool singletons
if flag is True:    # rarely needed — usually just `if flag:`
    ...
if flag is not False:
    ...

# Custom sentinels — identity is the whole point
_MISSING = object()

def get(mapping, key, default=_MISSING):
    value = mapping.get(key, _MISSING)
    if value is _MISSING:   # distinguishes "key absent" from "value is None"
        return default
    return value

is is also correct as a fast-path optimisation inside __eq__ itself:

class Point:
    def __eq__(self, other):
        if self is other:           # same object → trivially equal
            return True
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

The one-rule decision guide

Use == to compare values. Use is only for None, True, False, and your own sentinel objects.

If you're ever tempted to write is with a number, string, list, or any other value type, replace it with ==. If swapping is for == would change the behaviour on any input, you should have used ==.

# Wrong — relies on interning coincidence
if name is "admin":     # SyntaxWarning in Python 3.8+
    ...

# Right
if name == "admin":
    ...

# Wrong
if count is 0:
    ...

# Right
if count == 0:
    ...

# Right — singletons only
if response is None:
    ...

Recap

== tests value equality by calling __eq__ — use it for any comparison of data or content. is tests object identity by comparing id() — use it exclusively for None, the bool singletons, and custom sentinels. CPython's integer cache (−5..256) and string interning make is appear to work on plain values inside the cache range, but this is a coincidence that breaks at the boundary and in non-CPython implementations. PEP 8 mandates is None / is not None; everything else should be ==.

Deep dive: Identity, is vs == & Interning — interview questions

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel