in operator
The in keyword is a membership operator in Python. It checks whether a value exists within a sequence such as a string, list, tuple, set, or dictionary.
Syntax
value in sequence
Returns True if the value is found, False otherwise.
Checking membership in different types
Lists
Membership testing in a list scans each element from left to right until a match is found or the end is reached. This linear scan means the time grows proportionally with the list length.
fruits = ["apple", "banana", "cherry"]
print("apple" in fruits)
# True
print("orange" in fruits)
# False
The same membership test works identically on tuples. Since tuples are immutable sequences, the lookup behavior is the same linear scan as lists, but the guarantee that the collection will not change during iteration makes tuples a safe choice when the data should remain fixed.
Tuples
colors = ("red", "green", "blue")
print("red" in colors)
# True
print("yellow" in colors)
# False
Sets offer membership testing with fundamentally different performance characteristics. While lists and tuples scan elements sequentially, sets use a hash table internally, which means checking whether a value is present takes the same amount of time regardless of how many items the set contains. For membership-heavy workloads, converting a list to a set first can dramatically improve performance.
Sets
vowels = {"a", "e", "i", "o", "u"}
print("a" in vowels)
# True
print("b" in vowels)
# False
When you use the membership operator with strings, Python searches for a substring rather than a single character. Unlike sequences where each element is a discrete item, a string is treated as a continuous sequence of characters and the operator checks whether the given pattern appears anywhere within the text.
Strings
The in operator checks for substrings:
text = "Hello, World!"
print("Hello" in text)
# True
print("Python" in text)
# False
print("o" in text)
# True
Dictionaries behave differently from other collections when used with the membership operator. By default, Python checks only the dictionary keys and ignores the values entirely. This design choice makes dictionary membership testing fast because key lookup uses the same hash-based algorithm as sets, but it also means you must explicitly ask for values if that is what you intend to search.
Dictionaries
By default, in checks for keys, not values:
person = {"name": "Alice", "age": 30, "city": "NYC"}
print("name" in person)
# True
print("Alice" in person)
# False
print("age" in person)
# True
To shift the membership test from keys to values, call the .values() method on the dictionary. This returns a view object that behaves like a collection of the dictionary’s values, and the in operator checks against that collection instead of the keys. The view reflects live changes to the dictionary, so you do not need to rebuild it after each modification.
To check for values, use .values():
print("Alice" in person.values())
# True
When you need to verify that a specific key-value pair exists in the dictionary, the .items() method returns a view of two-element tuples, each containing a key and its corresponding value. The in operator then checks whether a tuple matching the one you provide appears among those pairs. This is useful for confirming that both the key and the expected value are present simultaneously.
To check for key-value pairs, use .items():
print(("name", "Alice") in person.items())
# True
The membership operator integrates naturally with control flow. Using in inside an if statement lets you conditionally execute code based on whether a value belongs to a collection, which is a common pattern for permission checks, input validation, and feature toggles.
Using in conditional statements
Basic if statement
user_permissions = ["read", "write", "delete"]
if "delete" in user_permissions:
print("Can delete files")
# Can delete files
The complementary not in operator checks for the absence of a value. This reads more naturally than wrapping in with not because the intent is stated directly: “if user is not in the banned list.” It is the idiomatic way to express exclusion logic in Python.
Not in
Use not in to check absence:
banned_users = ["alice", "bob", "charlie"]
user = "dave"
if user not in banned_users:
print("Access granted")
# Access granted
Membership testing also works with dictionary-like structures from the standard library. The os.environ mapping stores environment variables as key-value pairs, and using in lets you safely check whether a variable is set before attempting to read its value. This avoids the need for a try/except block around a direct key access.
Checking environment variables
import os
if "PYTHONPATH" in os.environ:
print(f"Python path: {os.environ['PYTHONPATH']}")
The speed of membership testing depends heavily on which data structure you use. Understanding the performance characteristics of each collection type helps you choose the right one for membership-heavy workloads and avoid accidental O(n) operations on large datasets.
Performance characteristics
Sets and dictionaries: O(1) average
Looking up membership in sets and dictionaries is very fast:
allowed_ids = {1, 2, 3, 4, 5}
# Fast - O(1) average case
print(3 in allowed_ids)
# True
Lists and tuples use a fundamentally different lookup strategy. Since they are ordered sequences with no hash index, Python must walk through each element one by one until it either finds a match or reaches the end. For a list with a million items, checking membership near the end of the list means examining nearly every element.
Lists and tuples: O(n)
Linear search through lists is slower for large collections:
names = ["alice", "bob", "charlie", "dave"]
# Slower for large lists - O(n)
print("charlie" in names)
# True
If you plan to perform multiple membership checks against the same collection, converting the list to a set before the loop eliminates the repeated linear scans. The one-time cost of building the set is amortized across all subsequent O(1) lookups, making this a worthwhile trade-off when the number of checks exceeds a few dozen.
When to convert list to set
If you’re checking membership multiple times, convert to a set first:
# Slow - converts on every check
while user_input:
if user_input in user_list: # O(n) each time
process(user_input)
# Fast - convert once, O(1) each check
user_set = set(user_list)
while user_input:
if user_input in user_set: # O(1) each time
process(user_input)
Python’s range objects support the membership operator directly without iterating. The range implementation overrides the containment check so that determining whether a number falls within the range is a constant-time arithmetic operation rather than a linear scan. This makes in on ranges efficient even for enormous bounds.
in with Range
The in keyword also works with range() objects:
print(5 in range(10))
# True
print(10 in range(10))
# False
print(0 in range(-5, 5))
# True
Generators and iterators do not support the membership operator the same way sequences do. Because a generator produces values lazily and can only be traversed once, using in on a generator would consume it partially and leave the remaining values inaccessible. Instead of applying in directly to a generator, use built-in functions like any() with a generator expression to test a condition against each produced value without destroying the iteration state.
In with generators and iterators
You can check membership in any iterable using a generator expression:
import itertools
# Check if any number greater than 10 exists
numbers = [1, 5, 8, 3, 9]
has_large = any(n > 10 for n in numbers)
print(has_large)
# False
# But note: in doesn't work with generators directly
# gen = (n for n in numbers)
# print(5 in gen) # This would consume the generator
The membership operator exists in many programming languages under different names, but Python’s version is notably consistent across all built-in collection types. Whether you are searching a string, a list, a dictionary, or a set, the syntax remains value in collection, which reduces the cognitive overhead of switching between data structures.
Comparison with other languages
Python’s in is similar to .includes() in JavaScript:
# Python
"a" in "abc" # True
"a" in ["a", "b", "c"] # True
// JavaScript
"abc".includes("a") // true
["a", "b", "c"].includes("a") // true
In SQL, the equivalent functionality appears in the WHERE clause with the IN operator, which checks whether a column value matches any member of a parenthesized list. The concept is the same as Python’s operator but operates on database rows rather than in-memory collections, and SQL additionally supports NOT IN for exclusion the same way Python supports not in.
In SQL, similar functionality uses IN and LIKE:
-- SQL equivalent
SELECT * FROM users WHERE role IN ('admin', 'moderator');
The membership operator appears in everyday Python code more often than many developers realize. Beyond the obvious collection lookups, it supports common patterns like input validation, feature flag checking, and defensive dictionary access. The following examples show practical uses that combine membership testing with Python’s other control flow and data access idioms.
Common patterns
Validate user input
valid_choices = {"yes", "no", "maybe"}
choice = input("Enter choice: ").lower()
if choice in valid_choices:
print(f"You chose: {choice}")
else:
print("Invalid choice")
Feature flags are another natural fit for the membership operator. By keeping the set of active features in memory and checking it with in, you can conditionally enable UI components or backend code paths. The set-based approach stays fast even as the number of flags grows, and the code reads as a straightforward membership question.
Check feature flags
enabled_features = {"dark-mode", "notifications", "beta"}
if "beta" in enabled_features:
enable_beta_ui()
Dictionaries raise a KeyError when you access a key that does not exist using bracket notation. Checking membership with in before the access provides a clean way to handle missing keys without wrapping the lookup in a try/except block. For cases where you simply want a default value, the .get() method is a more concise alternative, but the in test is clearer when the fallback logic involves multiple statements.
Safe dictionary access
config = {"host": "localhost", "port": 8080}
# Use in to avoid KeyError
if "timeout" in config:
print(f"Timeout: {config['timeout']}")
else:
print("Using default timeout")
See Also
- not keyword — logical negation
- for keyword — iterate over sequences
- set types — efficient membership testing
- dictionary methods — safe dictionary access