The in operator is one of Python’s most intuitive keywords. It checks whether a value exists inside a container — a list, tuple, set, dictionary, or string — and returns True or False. Simple as it sounds, understanding when and how to use it (and its performance implications) makes a real difference in your code.
Lists and Tuples#
fruits = ['apple', 'banana', 'cherry']
if 'apple' in fruits:
print("Apple is in the list")
# Output: Apple is in the list
Tuples work identically:
fruits = ('apple', 'banana', 'cherry')
if 'banana' in fruits:
print("Banana is in the tuple")
# Output: Banana is in the tuple
For both lists and tuples, in performs a linear search — it checks each element one by one until it finds a match or reaches the end. This is O(n) time complexity.
Sets#
fruits = {'apple', 'banana', 'cherry'}
if 'cherry' in fruits:
print("Cherry is in the set")
# Output: Cherry is in the set
Sets use a hash table internally, so in runs in O(1) average time — constant regardless of size. If you’re checking membership frequently on a large collection, convert it to a set first.
Dictionaries#
By default, in checks keys, not values:
fruits = {'apple': 1, 'banana': 2, 'cherry': 3}
if 'apple' in fruits:
print("Apple is a key in the dictionary")
# Output: Apple is a key in the dictionary
To check values, use .values():
if 2 in fruits.values():
print("Value 2 is in the dictionary")
# Output: Value 2 is in the dictionary
Like sets, dictionary key lookups are O(1). Checking .values() is O(n).
Strings#
The in operator checks for substrings, not just single characters:
text = "Python is an amazing programming language."
if "Python" in text:
print("Found 'Python' in the text")
# Output: Found 'Python' in the text
if "amazing" in text:
print("Found 'amazing' too")
# Output: Found 'amazing' too
This is cleaner than using text.find("Python") != -1 and more readable than regex for simple substring checks.
The not in Operator#
The negation not in reads naturally and avoids wrapping the whole expression in not:
banned = {'admin', 'root', 'superuser'}
username = 'carl'
if username not in banned:
print(f"Welcome, {username}")
# Output: Welcome, carl
Prefer not in over not (x in y) — it’s more Pythonic and PEP 8 compliant.
Using in with List Comprehensions#
in combines naturally with comprehensions for filtering:
fruits = ['apple', 'banana', 'cherry', 'orange']
selected = ['apple', 'cherry']
filtered = [f for f in fruits if f in selected]
print(filtered)
# Output: ['apple', 'cherry']
For better performance with large selected lists, convert to a set:
selected_set = set(selected)
filtered = [f for f in fruits if f in selected_set]
Performance: O(1) vs O(n)#
This matters when your collection is large:
| Container | in Time Complexity |
|---|---|
list | O(n) — linear scan |
tuple | O(n) — linear scan |
set | O(1) — hash lookup |
dict | O(1) — hash lookup |
str | O(n*m) — substring search |
# Slow: checking membership in a large list
large_list = list(range(1_000_000))
999_999 in large_list # scans up to 1M elements
# Fast: convert to set first
large_set = set(large_list)
999_999 in large_set # instant hash lookup
Custom __contains__#
You can make in work with your own classes by implementing __contains__:
class IPRange:
def __init__(self, start, end):
self.start = start
self.end = end
def __contains__(self, ip):
return self.start <= ip <= self.end
private = IPRange(100, 200)
print(150 in private) # True
print(250 in private) # False
If __contains__ is not defined, Python falls back to iterating with __iter__.
See also:
- Python List Comprehension — combine
inwith comprehensions for expressive list filtering - List Slicing in Python — another core technique for working with sequences