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:

Containerin Time Complexity
listO(n) — linear scan
tupleO(n) — linear scan
setO(1) — hash lookup
dictO(1) — hash lookup
strO(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: