Definition
Two names can point at the very same thing, or at two different things that happen to look identical. Just reading the code, the difference rarely jumps out: if one list equals another, is that the same list, or a perfect copy? Python needs a way to answer that exact question, separate from comparing values, and that is the job of is.
is compares the identity of two objects: it answers true when both names point at the same object in memory. That is a different question from the one == asks, which compares values. Two lists can hold exactly the same items without being the same object, which is exactly what the example below shows.
a = [1, 2, 3]
b = [1, 2, 3]
c = a
a == b # True : same values
a is b # False : two separate objects
a is c # True : the same object, under two namesThe practical consequence is direct: changing c also changes a, since they are the same object under a different name, whereas changing b leaves a untouched. Keeping that distinction in mind avoids a fair share of silent bugs, the kind where a value changes while nothing, on the surface, appears to have touched it.
The one genuinely recommended use
Once that difference clicks, a question follows right away: in real code, when does anyone actually reach for it? The answer comes down to one common case, testing whether a value is None. That is not a matter of taste: only one None object exists across an entire Python program, so comparing by identity always gives the right answer, where equality could in theory be fooled by a class that redefines its own comparison method.
if result is None:
...
if result is not None:
...The same logic applies to True and False, but writing it that way is discouraged: a condition already tests the truth value of an expression directly. Writing if active: is enough, whereas if active is True: adds a step that buys nothing.
The small number trap
This is where is starts producing results that feel wrong, to the point where many people learning the language wonder if they missed something in the previous section. The problem shows up like this: two integers built separately, with exactly the same value, sometimes answer true to an identity check, and sometimes false, with no rule visible in the code to explain why.
a = 256
b = 256
a is b # True, the integer is cached
a = 257
b = 257
a is b # False, two separate objectsThe explanation comes down to an internal optimisation in the interpreter: Python keeps a single copy of the most common small integers in memory, roughly between -5 and 256, along with certain short strings written directly in the code. Two names assigned the value 256 end up pointing at the same object, while 257 falls outside the cached range and triggers the creation of two separate objects.
This behaviour is guaranteed by no language specification and can change between Python versions, or even between implementations. Relying on it to compare values means writing code that works by accident.
The lesson reaches beyond this one case: comparing values with is gives an answer that looks right, until the day it is not. The resulting bug is painful to track down, since the code runs fine for weeks against small test values before failing on real production data.
What it reveals about copies
There is a classic bug where a value changes even though nothing in the code just read seems to have touched it. The cause often sits in a line written earlier: assigning a list to a new name does not copy it, it only adds one more label onto the same object.
original = ["a", "b"]
alias = original # same object
copy = original.copy() # separate object
alias.append("c")
original # ['a', 'b', 'c'] : changed
copy # ['a', 'b'] : untouchedA test with is answers the question that comes up next: was this data actually copied, or was a second name just created for the same thing? It is often the first diagnostic move worth trying when a value changes unexpectedly.
The .copy() method only solves part of the problem: it creates a new top-level object, but the items it contains still stay shared with the original if those items are themselves mutable, such as nested lists.
Frequently asked questions
When should is be used rather than ==?
For None, and to check that two names deliberately point at the same object. Everywhere else, including numbers, strings and lists, == gives the right answer, because it is the value that matters, not the location in memory.
Why do my two identical strings give False with is?
Because they were built separately, for instance through concatenation or by reading a file. Python only shares certain string literals written as-is in the code, never those computed while the program runs.
How is the negation written?
With is not, as two words, rather than not x is y, which reads awkwardly and invites confusion. Python treats is not as an operator in its own right, exactly as in has its counterpart not in.