Python provides two operators that look similar but answer completely different questions about your data. The double-equals operator asks whether two objects have the same value. Two lists containing the same items in the same order are equal. Two strings with the same characters are equal. The is operator asks a sharper question: whether two names point to the exact same object in memory. It checks identity, not content. Understanding the Python is vs equals distinction is essential because using the wrong operator produces code that appears to work most of the time but fails in ways that are difficult to debug.
If you have read about Python variables and object references, you already know that Python variables are labels attached to objects rather than boxes that contain values. Two labels can attach to the same object, and when they do, changes made through one label are visible through the other. The is operator detects this shared attachment. The double-equals operator does not care about attachment. It only cares about content. This article explains when to use each operator, the situations where they produce different results, and the classic pitfalls that trap programmers who confuse identity with equality. Many of these pitfalls are also covered in the article on common data type mistakes in Python.
How the is operator works
The is operator compares the memory addresses of two objects. If the addresses are identical, the objects are the same object and is returns True. If the addresses differ, the objects are distinct and is returns False, regardless of whether their contents happen to match. The is operator cannot be customized or overridden by class definitions. Its behavior is fixed at the language level, which makes it the only reliable way to check whether you are dealing with the singleton None object or with a custom sentinel value that your code uses as a signal.
The most common and important use of is in everyday Python code is the None check. Because there is exactly one None object in the entire Python runtime, comparing any variable to None with is tells you definitively whether that variable points to the None singleton. The pattern of using is None appears in virtually every Python codebase and is enforced by linters, style guides, and code review checklists. The alternative of using double-equals to compare against None works in most cases but can be fooled by objects that define a custom equality method returning True when compared to None. Since is cannot be overridden, it provides an absolute guarantee.
The is operator is also the right tool when you define your own sentinel objects. If you create a module-level constant using the object constructor to represent a value that was never provided, checking identity against that sentinel distinguishes it from None, from zero, and from every other possible value. This pattern appears in libraries that need to differentiate between the caller passed None deliberately and the caller did not pass anything at all.
How the double-equals operator works
The double-equals operator compares the values of two objects by calling the first object's special equality method with the second object as the argument. For built-in types, this comparison is well-defined and intuitive. Two integers with the same numeric value are equal. Two strings with the same sequence of characters are equal. Two lists with the same items in the same order are equal. Two dictionaries with the same key-value pairs are equal, regardless of insertion order as long as the pairs match.
The double-equals operator can be customized by any class that defines its own equality method. This is both a feature and a risk. Custom equality allows objects that represent the same real-world entity, like two database model instances with the same primary key, to compare as equal even though they are different Python objects in different memory locations. The risk is that a poorly written equality method can violate the expectations that Python and other code have about how equality should behave, such as symmetry (if a equals b then b equals a) and transitivity (if a equals b and b equals c then a equals c).
For numeric types, double-equals performs cross-type comparison correctly. The integer 1 and the float 1.0 compare as equal because they represent the same mathematical value, even though they are different types stored in different memory with different internal representations. The boolean True and the integer 1 also compare as equal because bool is a subclass of int. This cross-type equality is usually what you want, but it can mask type differences if you are not aware of it.
When is and double-equals give different answers
The classic demonstration of the difference between identity and equality uses two separate lists with identical contents. Create two lists, each containing the same three numbers. Double-equals returns True because the contents match. Is returns False because the lists occupy different memory locations. Appending to one list does not affect the other because they are independent objects whose contents happen to be the same at the moment of comparison.
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)
print(a is b)
b.append(4)
print(a)
print(b)Running this code prints True for the equality check because both lists contain 1, 2, and 3. It prints False for the identity check because a and b are separate list objects. After appending 4 to b, list a still contains three elements while list b now contains four. If is had returned True, modifying one would have modified the other, but it did not, and they diverged.
Integer caching complicates the picture for small numbers. Python pre-allocates integer objects from -5 through 256 at interpreter startup and reuses them whenever those values are needed. Writing x = 100 and y = 100 makes both x and y point to the same cached integer object, so is returns True. Writing x = 1000 and y = 1000 typically creates two separate integer objects, so is returns False even though double-equals returns True. This caching is an implementation detail of CPython, the reference Python implementation, and you should never depend on is working for integer comparisons in portable code.
The None pattern and sentinel values
The rule for None is simple and universal: always use is, never use double-equals. This rule exists because None is a singleton and because is cannot be fooled by custom equality methods. Every Python style guide, every linter configuration, and every experienced Python developer will tell you the same thing. The pattern is so ingrained that many developers write it automatically without thinking, which is exactly how reliable code should work.
Beyond None, there is a broader pattern of using is for sentinel values that must be distinguishable from all possible valid data values. If a function needs to know whether a caller provided an argument at all, as opposed to providing None explicitly, define a sentinel object at module level and check for it with is. The object function creates a unique, featureless object that cannot be confused with anything else in the program. Comparing against this sentinel with is is the only way to detect it reliably.
_UNSET = object()
def get_config(key, default=_UNSET):
if default is _UNSET:
return load_from_file(key)
return defaultThis function uses a sentinel to distinguish between get_config("timeout") where no default was provided and get_config("timeout", None) where the caller explicitly passed None as the default. Without the sentinel pattern, the function would not be able to tell the difference, and the caller's explicit None would be silently overwritten by the file-based fallback. This pattern appears in many Python libraries and frameworks, and understanding it helps you read and write more robust Python code.
Rune AI
Key Insights
- is checks object identity (same memory location); == checks value equality (same content).
- Always use
is Nonerather than== Nonefor None checks. - Use == for comparing numbers, strings, lists, and all other value comparisons.
- Python caches small integers (-5 to 256), which can make is appear to work when it should not.
- is cannot be overridden by custom classes; == can be customized via the eq method.
Frequently Asked Questions
What is the difference between is and == in Python?
Why should I use 'is None' instead of '== None'?
What is integer caching and why does it affect is?
Conclusion
The is operator and the double-equals operator answer fundamentally different questions. Is asks whether two names point to the same object. Double-equals asks whether two objects hold the same value. Using the right one for each situation prevents subtle bugs. Use is for None, for sentinel objects, and when you genuinely need to know whether two references point to identical memory. Use double-equals for comparing values, which is what you want in virtually every other situation.
More in this topic
Create and Assign Variables in Python
Learn every way to create and assign variables in Python, from basic single assignment to expressions, type hints, and practical patterns beginners use every day.
Python Variable Naming Rules
Learn Python's variable naming rules, including allowed characters, reserved keywords, PEP 8 conventions, and how to choose names that make your code readable.
Python Variables Explained
Understand what Python variables are, how they store and reference data, and why Python's variable model is different from other programming languages.