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.