So what does in mean in Python? It’s the membership operator: x in y asks whether x is inside y and gives back True or False. Its opposite, not in, returns the reverse:
"Ohio" in ["Texas", "Ohio", "Utah"] # True
"Iowa" not in ["Texas", "Ohio"] # True
Screenshots here are real runs of Python 3.12.5 in the Windows Command Prompt, so the output is exactly what you’ll get.
Below I’ll show how in behaves with lists, strings, dictionaries and sets, how it differs from the in in a for loop, why sets make it fast, and the gotchas worth knowing.
How the in operator works in Python
You can put in between almost any value and any collection. For lists and tuples it compares items, and for strings it looks for a substring:
states = ["Texas", "Ohio", "Utah"]
print("Ohio" in states) # True
print("Iowa" in states) # False
print("Iowa" not in states) # True
print("York" in "New York") # substring check
print("york" in "New York") # case-sensitive, so False
print(3 in range(1, 10)) # works on ranges too
print((2, 3) in [(1, 2), (2, 3)])
Output:
True
False
True
True
False
True
True

"york" in "New York" is False.Need a case-insensitive match? Lower both sides first, like "york" in city.lower(). Checking if a string contains a substring covers more ways.
Using in and not in with a dictionary
With a dictionary, in only looks at the keys. That surprises a lot of people, so check .values() or .items() when you mean those:
tax_rate = {"TX": 0.0625, "CA": 0.0725, "NY": 0.04}
print("CA" in tax_rate) # looks at keys
print(0.0725 in tax_rate) # False: values aren't checked
print(0.0725 in tax_rate.values()) # True
print(("NY", 0.04) in tax_rate.items())
state = "FL"
if state not in tax_rate:
print(f"No rate on file for {state}")
Output:
True
False
True
True
No rate on file for FL

0.0725 in tax_rate is False because 0.0725 is a value, not a key.Checking key in d before reading d[key] avoids a KeyError. Python dictionary contains goes deeper on this pattern.
Replacing long or chains with the in operator
One of my favorite uses is tidying up if statements. Instead of comparing the same variable against several values with or, put the values in a tuple:
order_status = "shipped"
if order_status in ("shipped", "delivered"):
print("Order is on its way or done")
# the long way, which does the same thing
if order_status == "shipped" or order_status == "delivered":
print("Same result, more typing")
email = "jane.doe@example.com"
if "@" not in email or "." not in email:
print("Invalid email")
else:
print("Email looks OK")
Output:
Order is on its way or done
Same result, more typing
Email looks OK
Both versions print the same thing, but the in one is shorter and easier to extend. If else in Python has more on writing clean conditions.
Is the in in a for loop the same as the in operator?
Not quite. In for city in cities the word in is part of the loop syntax and hands you each item in turn. Anywhere else, it’s the membership test that returns True or False:
cities = ["Denver", "Austin", "Boise"]
for city in cities: # here "in" is part of the for loop
print("Visiting", city)
print("Austin" in cities) # here "in" is the membership test
print([c for c in cities if "o" in c.lower()])
Output:
Visiting Denver
Visiting Austin
Visiting Boise
True
['Boise']
The comprehension at the end uses both: the loop in and a membership in as the filter. See the Python for loop guide for the loop side.
Why the in operator is faster on a set than a list
A list has to check items one by one until it finds a match, so a miss scans the whole thing. A set or dict uses hashing and jumps almost straight to the answer:
import timeit
nums_list = list(range(100_000))
nums_set = set(nums_list)
t_list = timeit.timeit(lambda: 99_999 in nums_list, number=200)
t_set = timeit.timeit(lambda: 99_999 in nums_set, number=200)
print(f"list: {t_list * 1000:.2f} ms for 200 lookups")
print(f"set: {t_set * 1000:.3f} ms for 200 lookups")
print(f"the set was about {t_list / t_set:,.0f}x faster")
Output:
list: 85.25 ms for 200 lookups
set: 0.011 ms for 200 lookups
the set was about 8,119x faster

If you’re testing membership inside a loop over a big collection, convert it to a set once up front. It’s one of the easiest speedups in Python.
Common gotchas with the in operator
in is a comparison operator, so it chains with == just like a < b < c does. It also uses equality, not type, to decide what matches:
print("a" in "abc" == True) # False! this is a chained comparison
print(("a" in "abc") == True) # True with parentheses
print(1 in [1.0, 2.0]) # True: in uses ==
print(True in [1, 2]) # True, because True == 1
nan = float("nan")
print(nan in [nan]) # True: in checks identity before ==
print(nan == nan) # False
try:
print(5 in 12345)
except TypeError as err:
print("TypeError:", err)
Output:
False
True
True
True
True
False
TypeError: argument of type 'int' is not iterable

"a" in "abc" == True is False because Python reads it as two comparisons joined by and.The fix for the first one is simple: never compare to True at all. Just write if "a" in "abc":. And an integer isn’t a container, so 5 in 12345 raises a TypeError.
Making your own class work with the in operator
Python calls a class’s __contains__ method when you use in on it. Define it and your objects support membership tests like any built-in:
class ZipRange:
def __init__(self, low, high):
self.low, self.high = low, high
def __contains__(self, zip_code):
return self.low <= int(zip_code) <= self.high
texas = ZipRange(75001, 79999)
print("78701" in texas) # Austin
print("10001" in texas) # New York
print("10001" not in texas)
Output:
True
False
True
If a class has no __contains__, Python falls back to iterating it with __iter__, and failing that, __getitem__.
in operator in Python: quick reference
| Container | What x in y checks | Speed |
|---|---|---|
| list, tuple | Any item equal to x | Slower as it grows |
| str | x is a substring | Fast |
| dict | x is a key | Fast (hashing) |
| set, frozenset | x is a member | Fast (hashing) |
| range | x is one of the numbers | Fast (math) |
Want to keep going? These pair well with the in operator:
- Check if an element is not in a list
- Check if any element of a list is in another list
- Set vs tuple in Python
- List of lists in Python
- Search a dictionary by value
Frequently asked questions
What does in mean in Python?
It’s the membership operator. x in y returns True when x is found inside y, such as an item in a list or a substring in a string.
What does not in mean in Python?
It’s the opposite test. x not in y returns True when x isn’t inside y, and it reads better than not x in y.
Does in check keys or values in a dictionary?
Keys only. Use value in d.values() to check values, or (key, value) in d.items() for a pair.
Is the in operator case-sensitive for strings?
Yes. Convert both sides with .lower() or .casefold() if you want a case-insensitive check.
Why is in slow on my list?
A list is searched item by item. Convert it to a set once and every lookup after that is close to instant.
Where is the in operator documented?
It’s covered under membership test operations in the Python language reference.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn