How to use 'is' in Python
Learn how to use Python's is operator. Explore different methods, tips, real-world applications, and how to debug common errors.

In Python, the is operator is a crucial tool for identity comparison. It confirms if two variables reference the exact same object, which is different from the == operator's check for value equality.
In this article, you'll explore techniques and real-world applications. We'll also provide tips and debugging advice to help you master the is operator and write more robust, error-free Python 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
When you assign y = x, you're not making a copy of the list. You're simply giving the existing list another name. Both x and y now point to the same object in memory.
- The
isoperator returnsTruebecause it confirms they are the identical object. - The
id()function outputs the same memory address for both variables, providing concrete proof of this shared identity.
This behavior is central to understanding how Python handles mutable objects and references.
Understanding identity vs equality
This distinction is more than academic—it affects how is compares to == and behaves 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 example, you've created two separate lists, a and b, that happen to hold the same values. The == operator compares the contents of the lists, so it correctly returns True.
- The
isoperator, however, checks ifaandbare the very same object. Since they were created independently, they occupy different spots in memory. - This is why
a is bevaluates toFalse, and theid()function reveals two 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
The behavior of the is operator changes depending on whether an object is mutable or immutable. For mutable objects like lists, Python creates a new object in memory each time. That's why list1 is list2 returns False; they are separate objects despite having the same content.
- With immutable objects like small integers, Python often reuses existing objects to save memory—an optimization known as interning.
- This is why
num1 is num2evaluates toTrue. Both variables point to the same pre-existing object for the number 42.
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
The value None is a special case in Python because it's a singleton—only one instance of it ever exists. In the example, the find_value function returns this unique object when the target isn't found. Since any variable assigned None points to this exact same object, you can reliably check for it using identity.
- Using
is Noneis the standard, most efficient way to perform this check. - This practice is recommended by PEP 8, Python's official style guide, ensuring your code is both fast and idiomatic.
Advanced patterns and caveats
That same logic with None and small integers reveals deeper Python optimizations and common pitfalls you'll want to avoid when using the is operator.
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's object interning is a memory-saving trick where it reuses common immutable objects. This optimization explains why you see different results with small and large integers.
- For numbers in a common range, typically
-5to256, Python keeps a single object ready. That's whya is bisTruewhen both variables equal256; they point to the same object. - Integers outside this range, like
1000, don't get this special treatment. Python creates new objects each time, soc is devaluates toFalse.
This behavior is an implementation detail, so you shouldn't rely on it for comparing numbers.
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. This means only one instance of each exists, so you can reliably use is True or is not False for identity checks. It's the standard, most Pythonic way to handle booleans.
- This singleton behavior doesn't apply to mutable collections like lists.
- Each time you create an empty list with
[], Python makes a new object. That's why[] is []evaluates toFalse. They look the same but aren't the same object.
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 for string comparison is a common trap because string interning is unpredictable. Python often reuses identical string literals to save memory, which is why s1 is s2 evaluates to True. However, this behavior is an optimization, not a guarantee you can depend on.
- Strings created through operations, like concatenating
"he" + "llo", might not point to the same object as a literal"hello"across different Python implementations. - The safe and correct way to check if strings have the same value is to always use the
==operator.
Move faster with Replit
Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. With Agent 4, you can move from piecing together techniques like using the is operator to building complete applications.
Instead of just learning the mechanics, you can describe the app you want to build, and the Agent will take it from an idea to a working product. For example, you could build:
- A state management utility that only triggers updates when an object's identity actually changes, preventing unnecessary re-renders.
- A caching tool that returns the exact same singleton object, like a default configuration, to save memory and ensure consistency.
- A data validation script that confirms a function returns a specific default object instance, not just an object with the same value.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
Even with a solid grasp of identity, you can run into a few common pitfalls when using the is operator in your code.
Mistakenly using is for value comparison
The most frequent mistake is using is when you should be using ==. The is operator checks if two variables point to the exact same object in memory, whereas the == operator checks if their values are equivalent.
- For example, if you create two separate lists,
a = [10, 20]andb = [10, 20], the expressiona == bisTruebecause their contents are the same. - However,
a is bwill beFalsebecause they are two distinct objects. Unless you are intentionally checking for object identity, always use==to compare values.
Issues with mutable default parameters and is identity
A classic Python trap involves using a mutable object, like a list, as a default argument in a function. Python creates this default object only once—when the function is defined—not each time it's called. This means that every call to the function that relies on the default will share and modify the very same list object, which can lead to unexpected behavior.
- You could use the
isoperator to confirm that the list object remains the same across different function calls. - To avoid this issue, the standard practice is to use
Noneas the default value and then create a new list inside the function if the argument isNone.
Confusion between is None and equality checks
While using == None might seem to work correctly, it's not the recommended approach. An object can define its own rules for equality, potentially making it equal to None even when it isn't the None object itself. This can introduce subtle and hard-to-find bugs.
- The expression
is Noneis the correct and safest way to check forNone. - Because
Noneis a singleton (meaning there's only one instance of it), an identity check withisis both faster and guaranteed to be accurate.
Mistakenly using is for value comparison
It's a frequent mistake to use the is operator for value comparison, especially with numbers. This bug can be deceptive because of Python's interning, sometimes working as expected and other times failing silently. The following code demonstrates this exact problem.
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 expression 50 + 50 creates a new integer object at runtime. Since the is operator checks for identity, it sees a different object than the pre-existing 100, causing the comparison to fail. The corrected code demonstrates the fix.
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 uses the == operator, which properly compares the numerical value of score to 100. This works reliably because == checks for equality, not identity, so it doesn't matter if the number was created from an operation like 50 + 50.
This is a crucial takeaway. Unless you're intentionally checking for object identity—like with None—you should always use == for comparing values like numbers and strings to avoid bugs from Python's internal optimizations.
Issues with mutable default parameters and is identity
A function with a mutable default argument, like a list, can cause baffling bugs. The default object is shared across all calls, so changes made in one call unexpectedly affect the next. The following code demonstrates this classic Python trap.
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 second call to add_item doesn't create a new list but modifies the existing one. That's why inv1 and inv2 are the same object, and "shield" is unexpectedly added to both. The following code shows the idiomatic solution.
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 fixes this by setting the default argument to None. An if inventory is None check then creates a new list only when one isn't provided. This simple change ensures each function call works with a separate list, so inv1 and inv2 are no longer the same object. You should always watch for this behavior when using mutable types like lists or dictionaries as default arguments in your functions.
Confusion between is None and equality checks
Using the == operator to check for None seems logical, but it opens the door to subtle bugs. Because custom objects can redefine equality, a check with == isn't guaranteed. The code below shows how this can cause problems.
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)
The process_data function uses data == None, a check that isn't foolproof. This comparison can cause the function to incorrectly treat a custom object as None, leading to bugs. The following code shows the correct, idiomatic approach.
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 uses data is None because None is a singleton—only one instance of it ever exists in Python. This makes an identity check with is the most reliable and efficient approach.
- Using
== Noneis risky because custom objects can be designed to equalNone, leading to unexpected bugs.
You should always use is None to guarantee you're checking for the actual None object itself, not just a value that happens to equal it.
Real-world applications
The is operator isn't just for avoiding bugs; it's also key to powerful design patterns like memoization and singletons.
Using is for memoization in recursive functions
You can use the is operator to verify that memoization in a recursive function returns the identical cached object, not just a new one with the same value.
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)
This code uses memoization to optimize the factorial function by caching its results in the memo dictionary. This avoids re-calculating values that have already been computed, making subsequent calls much faster.
- The first time you call
factorial(5), it performs the full recursive calculation and stores the result. - The second call finds the answer in
memoand returns the stored object directly.
Because both result1 and result2 end up referencing the exact same object from the cache, the expression result1 is result2 returns True.
Using is in the singleton design pattern
The singleton pattern ensures a class has only one instance, and the is operator is the perfect tool to confirm that every reference points to that single 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 get_instance method uses an identity check with is None to control object creation. It looks at the class attribute _instance to see if an object has already been made.
- On the first call,
_instance is Noneevaluates toTrue, so the method creates and saves a new object. - On every subsequent call, this check fails because
_instancenow holds an object. The method simply returns the existing object instead of creating another one.
This is why both instance1 and instance2 refer to the exact same object, causing instance1 is instance2 to be True.
Get started with Replit
Put your knowledge of the is operator into practice. Describe a tool to Replit Agent, like “a config loader that uses a singleton pattern” or “a script that checks for mutable default arguments.”
Replit Agent will write the code, test for errors, and deploy your app. Start building with Replit.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.
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.

.png)
.png)
.png)