How to use 'is' in Python

Learn how to use Python's 'is' operator. This guide covers different methods, tips, real-world applications, and debugging common errors.

How to use 'is' in Python
Published on: 
Tue
Mar 3, 2026
Updated on: 
Wed
Mar 4, 2026
The Replit Team Logo Image
The Replit Team

In Python, the is operator is a crucial tool for identity comparison. It confirms if two variables reference the exact same object, unlike the == operator which only checks for value equality.

In this guide, you'll explore techniques and tips to use is correctly. You'll also find real-world applications and advice to debug common errors, so you can avoid pitfalls and write more robust code.

Using the is operator for identity comparison

x = [1, 2, 3]
y = x
print(x is y) # Check if x and y refer to the same object
print(id(x), id(y)) # Show memory addresses--OUTPUT--True
140584100329856 140584100329856

In the example, the assignment y = x doesn't create a copy of the list. Instead, it makes y another reference to the same list object that x points to. This is a crucial distinction because both variables now target the identical object in memory.

Consequently, the is operator returns True, as it's verifying that x and y are one and the same object. The id() function confirms this by showing that their memory addresses are identical, proving they aren't just equal in value but are the same entity.

Understanding identity vs equality

This distinction becomes clearer when you compare the is operator with == and observe its behavior with different data types, including the special value None.

Comparing is with the == operator

a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # Values are equal
print(a is b) # But they are different objects
print(id(a), id(b)) # Different memory addresses--OUTPUT--True
False
140584100329792 140584100335552

In this case, a == b returns True because the == operator checks if the values inside the lists are the same. Since both lists contain [1, 2, 3], they are considered equal.

However, the is operator tells a different story:

  • It returns False because Python has created two separate list objects in memory. Even though they look alike, they aren't the same instance.
  • The id() function confirms this, showing that a and b have distinct memory addresses.

Behavior of is with mutable and immutable objects

# Mutable objects (lists)
list1 = [1, 2]
list2 = [1, 2]
print(list1 is list2)

# Immutable objects (small integers)
num1 = 42
num2 = 42
print(num1 is num2)--OUTPUT--False
True

How the is operator behaves depends on whether an object is mutable or immutable. With mutable objects like lists, Python creates distinct instances even if their contents match. That's why list1 is list2 is False—they're separate objects in memory.

  • Conversely, Python often optimizes immutable types like small integers by reusing objects to save memory.
  • Both num1 and num2 point to the same integer object for 42, which is why num1 is num2 returns True.

Using is with the None value

def find_value(data, target):
result = None
for item in data:
if item == target:
result = item
break
return result

value = find_value([1, 2, 3], 4)
print(value is None) # Proper way to check for None--OUTPUT--TRUE

In Python, None is a singleton, meaning there's only one instance of it. When your find_value function can't find a target, it returns None, and the value variable points to this single, unique object.

  • This is why you should always use value is None. The is operator confirms that your variable is the exact same object as the one and only None.
  • While value == None often works, is is the idiomatic and guaranteed way to check for None, as it avoids potential issues with objects that might have custom equality behaviors.

Advanced patterns and caveats

Beyond the basics of identity, the is operator has nuances tied to Python's internal optimizations and special values that are crucial for writing bug-free code.

Understanding Python's object interning

# Small integers are interned
a = 256
b = 256
print(a is b) # True for small integers (-5 to 256)

# Large integers are not interned
c = 1000
d = 1000
print(c is d) # May be False (implementation-dependent)--OUTPUT--True
False

Python uses a memory optimization called object interning for certain immutable types. It pre-allocates and reuses objects for commonly used values, like small integers and short strings, to save memory and improve performance.

  • This is why a is b returns True. Both variables point to the same pre-existing object for the number 256, as it falls within the common interned range of -5 to 256.
  • Conversely, c is d is often False because larger integers like 1000 are not automatically interned. Python creates two separate objects, so they have different identities.

Using is with boolean values and singletons

x = True
print(x is True)
print(x is not False)

# Check for identity with empty collections
empty_list = []
print(empty_list is not None)
print([] is []) # Each empty list is a different object--OUTPUT--True
True
True
False

Just like None, the boolean values True and False are singletons, meaning there's only one instance of each. That's why using the is operator for identity checks like x is True is the standard, reliable approach. It confirms you're working with the actual boolean object.

  • This singleton pattern doesn't apply to mutable objects like lists.
  • Even an empty list is a distinct object. So, when you compare [] is [], the result is False because Python creates two separate empty list objects in memory.

Common pitfalls when using the is operator

# String interning can be unpredictable
s1 = "hello"
s2 = "hello"
print(s1 is s2) # Often True due to interning

# But with string operations, new objects are created
s3 = "he" + "llo"
s4 = "hello"
print(s3 is s4) # May vary by implementation--OUTPUT--True
True

Relying on the is operator with strings is a common pitfall because string interning isn't always predictable. While Python often reuses identical string literals, making an identity check like s1 is s2 return True, this is an optimization, not a language guarantee.

  • Operations like concatenation can create new string objects, so you can't be sure if an expression like s3 is s4 will be True across different Python implementations.
  • The safe and correct approach is to always use == to compare string values. This checks if their contents are the same, which is almost always what you actually need.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

For the identity comparison techniques we've explored, Replit Agent can turn them into production-ready tools:

  • Build a configuration parser that safely checks for missing settings using is None to apply default values.
  • Create a state machine where states are singletons, using the is operator for fast and reliable comparisons.
  • Deploy a debugging utility that visualizes object identities to help you track down bugs related to mutable objects.

Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser.

Common errors and challenges

Even with a solid grasp of identity, the is operator presents common challenges that can introduce subtle bugs into your code.

Mistakenly using is for value comparison

The most frequent mistake is using is when you really mean to use ==. Remember, is checks if two variables point to the exact same object in memory, while == checks if their values are equivalent. This is a critical distinction, especially with mutable types like lists or dictionaries.

For example, two separate lists containing the same elements will be considered equal by ==, but is will return False because they are different objects. Always use == for comparing values unless you specifically need to confirm that two variables are the very same instance.

Issues with mutable default parameters and is identity

A classic Python pitfall involves using mutable objects like a list or dictionary as a default argument in a function. Python creates the default object only once, when the function is defined. Every subsequent call that relies on that default will share and modify the exact same object.

This shared identity can lead to baffling bugs where changes from one function call leak into another. Using is can help diagnose this issue by confirming that the default argument in different calls refers to the same object, but the best practice is to avoid mutable defaults altogether. A common pattern is to use None as the default and create a new list or dictionary inside the function if the argument is None.

Confusion between is None and equality checks

While is None is the correct and idiomatic way to check for the None singleton, confusion can arise when developers use == None instead. Although == None often works as expected, it's less reliable because it can be overridden by a class's custom equality logic.

An object could be programmed to evaluate as equal to None even when it isn't the None object itself. Using is bypasses this potential issue entirely by performing a direct identity check. Sticking to is None ensures your code is both safer and communicates its intent more clearly.

Mistakenly using is for value comparison

A common mistake is using the is operator to compare numbers when you should be using ==. While this might seem to work for small integers due to Python's interning, it's unreliable and can lead to unexpected bugs.

The following check_score function demonstrates how this subtle error can cause your logic to fail when you least expect it.

def check_score(score):
if score is 100: # Bug: using identity instead of equality
return "Perfect score!"
return "Keep trying!"

print(check_score(100)) # May work on some implementations
print(check_score(50 + 50)) # Will likely fail

The check_score function fails because the expression 50 + 50 creates a new integer object for 100. Since is compares identity, not value, the check returns false. The following example shows the correct way to write this comparison.

def check_score(score):
if score == 100: # Fixed: using equality instead of identity
return "Perfect score!"
return "Keep trying!"

print(check_score(100))
print(check_score(50 + 50))

The corrected check_score function now works reliably by using the == operator. This change ensures the comparison checks for value equality, so it doesn't matter if the input is the literal 100 or the result of an expression like 50 + 50. The function now behaves as expected because it focuses on the number's value, not its specific object identity in memory. Always use == when comparing numbers or strings by their content.

Issues with mutable default parameters and is identity

Using a mutable default argument, like an empty list, is a notorious source of bugs. Because Python creates the default object only once, every call to the function modifies the same list, causing changes in one call to leak into others.

The add_item function below illustrates this perfectly. Watch what happens when you call it twice—the inventories unexpectedly merge because they're, in fact, the same object.

def add_item(item, inventory=[]):
inventory.append(item)
return inventory

inv1 = add_item("sword")
inv2 = add_item("shield")
print(inv1 is inv2) # Unexpectedly True
print(inv1) # Contains both items

The add_item function reuses the same default list for every call. As a result, both inv1 and inv2 reference the same list, causing the second item to be added to the first inventory. The following code demonstrates the fix.

def add_item(item, inventory=None):
if inventory is None:
inventory = []
inventory.append(item)
return inventory

inv1 = add_item("sword")
inv2 = add_item("shield")
print(inv1 is inv2) # Correctly False
print(inv1) # Contains only "sword"

The corrected add_item function solves the problem by setting the default inventory to None. Inside the function, it checks if inventory is None and creates a fresh list each time. This guarantees that every call to add_item without a second argument gets its own separate list, preventing unintended side effects. Be mindful of this whenever you use mutable types like lists or dictionaries as default arguments in your functions.

Confusion between is None and equality checks

Using == None instead of the idiomatic is None can introduce subtle bugs. While it often works, the == operator can be overridden by custom classes, leading to unpredictable behavior where an object might falsely equal None.

The following process_data function shows how this seemingly harmless choice can create confusion and potential errors in your code.

def process_data(data):
if data == None: # Bug: using equality instead of identity
return "No data provided"
return f"Processing {len(data)} items"

result = process_data(None)
print(result)

While the code works here, using == None is risky because custom objects can be designed to equal None falsely. This makes your check unreliable. The corrected code below demonstrates the proper, guaranteed method for this comparison.

def process_data(data):
if data is None: # Fixed: proper way to check for None
return "No data provided"
return f"Processing {len(data)} items"

result = process_data(None)
print(result)

The corrected process_data function now reliably checks for missing data by using data is None. This is the idiomatic approach because None is a singleton, meaning there's only one instance of it in your program. The is operator confirms you're working with that exact object, which is a safer bet than using ==. An object could be programmed to equal None, but it can't *be* None, which is why this identity check is so important.

Real-world applications

After navigating its common pitfalls, you can leverage the is operator to implement powerful software patterns for efficiency and structural integrity.

Using is for memoization in recursive functions

In memoization, the is operator helps you verify that a recursive function is returning the exact same cached object, saving you from redundant calculations.

memo = {}
def factorial(n):
if n not in memo:
memo[n] = n * factorial(n-1) if n > 1 else 1
return memo[n]

result1 = factorial(5)
result2 = factorial(5)
print(result1 is result2)

The factorial function uses a dictionary, memo, to store previously computed values. This technique is called memoization. The first time you call factorial(5), it performs the full calculation and saves the result.

  • On the second call, the function skips the calculation and retrieves the stored result directly from memo.
  • The final print statement returns True because both result1 and result2 reference the identical object that was retrieved from the cache, proving that the optimization worked.

Using is in the singleton design pattern

The singleton design pattern uses the is operator to ensure a class is only ever instantiated once, making it the ideal way to confirm that different parts of your code are working with the exact same object.

class Singleton:
_instance = None

@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance

instance1 = Singleton.get_instance()
instance2 = Singleton.get_instance()
print(instance1 is instance2)

The Singleton class uses a class method, get_instance, to control object creation. The first time you call it, the method checks if the internal _instance variable is None. Since it is, a new object is created and stored.

  • On every subsequent call, the method finds that _instance already holds an object, so it skips creating a new one.
  • Instead, it returns the existing object. This is why instance1 is instance2 returns True—both variables reference the exact same object.

Get started with Replit

Turn your knowledge of the is operator into a real tool. Describe what you want to build, like “a config manager using the singleton pattern” or “a memoization cache for a Fibonacci sequence calculator” to Replit agent.

The agent writes the code, tests for errors, and deploys your app right from your browser. Start building with Replit and bring your idea to life.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.