Skip to main content

What is the difference between == and is operators

These two operators test fundamentally different things. Confusing them leads to subtle bugs that may only appear in certain conditions.

Equality with == (Value Comparison)​

The == operator checks if two objects have the same value. It calls the object's __eq__ method internally.

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

print(a == b) # True - same contents

Both lists contain identical elements, so they're equal even though they're separate objects.

Identity with is (Object Comparison)​

The is operator checks if two references point to the exact same object in memory. It compares id() values.

a = [1, 2, 3]
b = [1, 2, 3]
c = a # c references the same object as a

print(a is b) # False - different objects
print(a is c) # True - same object

print(id(a), id(b), id(c))
# 140234866534720 140234866535040 140234866534720

The Integer Interning Trap​

Python caches small integers (-5 to 256), making is comparisons appear to work but fail unpredictably:

# Small integers are cached
x = 100
y = 100
print(x is y) # True (same cached object)

# Larger integers are not
x = 1000
y = 1000
print(x is y) # False (different objects!)

# But they're still equal
print(x == y) # True
warning

Never use is to compare numbers or strings. The result depends on Python's internal caching behavior, which varies between implementations and versions.

# ❌ Unreliable
if user_count is 0:
print("No users")

# ✅ Correct
if user_count == 0:
print("No users")

Correct Usage: Checking for None​

The primary use case for is is checking for None, since None is a singleton:

result = some_function()

# ✅ Correct - None is a singleton
if result is None:
print("No result")

if result is not None:
print(f"Got: {result}")

This is preferred over == None because:

  • It's faster (no method call)
  • It's explicit about checking identity
  • It avoids issues with objects that override __eq__
# A class with unusual equality
class Quirky:
def __eq__(self, other):
return True # Equals everything!

q = Quirky()
print(q == None) # True (misleading!)
print(q is None) # False (correct)

Checking Boolean Singletons​

Similarly, True and False are singletons:

# ✅ Acceptable for explicit boolean check
if flag is True:
print("Explicitly True")

# ✅ More common and Pythonic
if flag:
print("Truthy")
tip

In practice, prefer truthiness checks (if flag:) over identity checks (if flag is True:). The identity check fails for truthy non-boolean values:

value = 1
print(value is True) # False
print(bool(value)) # True

Mutable Default Argument Gotcha​

Understanding is helps explain a common Python pitfall:

def append_item(item, items=[]):
items.append(item)
return items

a = append_item(1)
b = append_item(2)

print(a) # [1, 2] - unexpected!
print(b) # [1, 2]
print(a is b) # True - same object!

The default list is created once and reused. Fix with:

def append_item(item, items=None):
if items is None:
items = []
items.append(item)
return items

Quick Reference​

ScenarioOperatorExample
Compare values==if x == 10:
Compare strings==if name == "Alice":
Compare lists/dicts==if data == expected:
Check for Noneisif result is None:
Check not Noneis notif result is not None:

Summary​

Use == for value comparisons-it's the right choice 99% of the time. Reserve is for identity checks, primarily when comparing against None. Never use is with integers, strings, or other values where Python's caching behavior could cause inconsistent results.