
Finding a string in a list is one of the most common tasks in Python. You might do it to filter log data, check user input, look up configuration values, or work through text. The quickest way to check if a string is in a list is the in operator. It looks through the list one item at a time and returns True or False:
my_list = ["apple", "banana", "cherry"]
if "banana" in my_list:
print("Found!")
Running this prints the following output:
Found!
That one line answers the basic question, but real code often needs more. You might need the index of a match, all the positions where it matches, a substring match instead of an exact one, a case-insensitive search, or a pattern match using regular expressions.
This tutorial covers each approach with examples you can run, compares their speed and return types in one table, and shows three real-world cases (filtering logs, checking input, and matching file paths) so you can pick the right method for what you need. Every example runs in a standard Python 3.6+ environment (tested on the latest stable release, Python 3.14) and uses only the standard library, with no extra packages.
Key Takeaways
in operator ("target" in my_list) for a fast, easy-to-read check; it returns True or False.in checks for an exact match, not a partial one. So "app" in ["apple"] is False.list.index("target") to get the position of the first match. Wrap it in a try/except ValueError block, since it raises an error when the string is not there.enumerate(): [i for i, val in enumerate(my_list) if val == "target"].any("sub" in item for item in my_list). To collect those elements, use a list comprehension.next((item for item in my_list if "sub" in item), None) to get the first matching element (or a default) without checking the rest..lower(), or use re.search(pattern, item, re.IGNORECASE) for pattern searches.set for O(1) average lookups, as noted in the Python Wiki time-complexity reference.in or str.find() is faster and easier to read.in operator to check if a string exists in a listThe in operator is the simplest and most common way to check whether a string is in a list, and it is the fastest choice for a one-time check. It returns True when a matching element exists and False when it does not. The matching not in operator checks that a string is missing.
l1 = ['A', 'B', 'C', 'D', 'A', 'A', 'C']
# Check whether a string is present in the list
if 'A' in l1:
print('A is present in the list')
# Check whether a string is absent from the list
if 'X' not in l1:
print('X is not present in the list')
This produces the following output:
A is present in the list
X is not present in the list
The same pattern works well for checking user input. The following example asks the user for a value and reports whether it appears in the list:
l1 = ['A', 'B', 'C', 'D', 'A', 'A', 'C']
s = input('Please enter a character A-Z:\n')
if s in l1:
print(f'{s} is present in the list')
else:
print(f'{s} is not present in the list')
If the user types A, the program prints:
Please enter a character A-Z:
A
A is present in the list
One thing to watch out for: on a list, in checks for an exact match, not a partial one. "app" in ["apple", "banana"] returns False because no element equals "app", even though "apple" starts with it. If you need to match a substring inside list elements, see the section on Searching for a substring within list elements. For more on the string formatting used here, see f-strings in Python.
index()When you need the position of a string instead of a yes/no answer, use the list index() method. It returns the index of the first match, counting from zero.
my_list = ["apple", "banana", "cherry"]
index = my_list.index("banana")
print(index)
This returns the position of the first match:
1
The result 1 means "banana" is the second element, since list indexes start at 0. The key thing to remember is that index() raises a ValueError if the value is not there, so production code should wrap the call in a try/except block:
my_list = ["apple", "banana", "cherry"]
try:
index = my_list.index("mango")
print(f"Found at index {index}")
except ValueError:
print("Not found")
Because "mango" is absent, the except branch runs and prints:
Not found
The index() method only returns the first match. To get every position where a string appears, combine enumerate() with a list comprehension. This is a short, clean replacement for a manual while loop:
my_list = ["apple", "banana", "cherry", "banana"]
indexes = [i for i, val in enumerate(my_list) if val == "banana"]
print(indexes)
This returns a list of all matching indexes:
[1, 3]
The comprehension checks each element and keeps the index when it matches the target. Learn more in the guides on list comprehensions and enumerate().
A common point of confusion is that in on a list checks for an exact match, when you may just want to know whether any element contains a target substring. The following patterns solve this and let you choose the result you need: the matching elements, a True/False answer, or just the first match.
Use a list comprehension with the in operator (which does check for substrings inside individual strings) to collect every element that contains the target text:
my_list = ["apple", "banana", "cherry"]
matches = [item for item in my_list if "an" in item]
print(matches)
This keeps only the elements that contain "an":
['banana']
any()When you only need to know whether at least one element contains the substring, any() with a generator expression is the most efficient choice, since it stops at the first match:
my_list = ["apple.log", "banana.txt", "cherry.csv"]
has_log = any(".log" in item for item in my_list)
print(has_log)
This reports whether any element contains ".log":
True
next()If you want only the first matching element and nothing else, next() over a generator expression returns it right away. It also takes a default value, which avoids a StopIteration error when there is no match:
my_list = ["app.log", "error.log", "data.csv"]
first_error = next((item for item in my_list if "error" in item), None)
print(first_error)
This returns the first element containing "error":
error.log
By default, every method above is case-sensitive, so "BANANA" will not match "banana". There are two reliable ways to make a search case-insensitive.
.lower()The simplest approach is to convert both the target and each element to the same case with .lower() (or .upper()) before you compare them:
names = ["Alice", "BOB", "carol"]
target = "bob"
matches = [n for n in names if target.lower() in n.lower()]
print(matches)
This matches "BOB" regardless of case:
['BOB']
re.IGNORECASE for pattern searchesWhen your search uses a pattern rather than a fixed string, pass the re.IGNORECASE flag to re.search() inside a comprehension. This is the cleaner choice when you also need regular-expression features:
import re
names = ["Alice", "BOB", "carol"]
matches = [n for n in names if re.search("bob", n, re.IGNORECASE)]
print(matches)
This returns the same case-insensitive result:
['BOB']
Regular expressions are the right tool when you are searching for a pattern (a date, a file extension, an ID format) rather than a fixed string. The re module from the standard library handles these searches. For an introduction to the syntax, see An Introduction to Regular Expressions.
re.search() and a list comprehensionre.search() returns a match object (which counts as True) when the pattern is found anywhere in the string, and None otherwise. Combine it with a comprehension to filter a list:
import re
logs = ["2024-01-01 OK", "2024-01-02 ERROR", "no date here"]
dated = [line for line in logs if re.search(r"\d{4}-\d{2}-\d{2}", line)]
print(dated)
This keeps only the lines that contain an ISO-style date:
['2024-01-01 OK', '2024-01-02 ERROR']
re.findall()When you want the matched text itself rather than the whole element, re.findall() returns every match of the pattern in a string. Use a comprehension to combine the results across the list into one flat list:
import re
logs = ["2024-01-01 OK", "2024-01-02 ERROR", "no date here"]
dates = [match for line in logs for match in re.findall(r"\d{4}-\d{2}-\d{2}", line)]
print(dates)
This collects the date strings themselves:
['2024-01-01', '2024-01-02']
Use regex only when you truly need pattern matching. For an exact match, use the in operator or index(). For a fixed substring, use in on the element or str.find(). These plain methods are faster and much easier to read than a regular expression, and they avoid hard-to-spot bugs caused by unescaped special characters. Use regex when the target follows a flexible structure (for example, matching any .log or .txt extension, checking a date format, or pulling out numeric IDs) and a plain string cannot describe the rule.
filter()Filtering a list down to the elements that match a string condition is so common that Python gives you two standard ways to do it. Both give the same result; the difference is mostly style.
A list comprehension reads naturally and is the most common choice among Python developers for filtering a list:
words = ["cat", "dog", "caterpillar", "cobra"]
starts_with_cat = [w for w in words if w.startswith("cat")]
print(starts_with_cat)
This keeps the words that start with "cat":
['cat', 'caterpillar']
filter() with a lambdaThe built-in filter() function checks each element and returns an iterator, which you then turn into a list:
words = ["cat", "dog", "caterpillar", "cobra"]
starts_with_cat = list(filter(lambda w: w.startswith("cat"), words))
print(starts_with_cat)
This produces the identical result:
['cat', 'caterpillar']
As a rule of thumb, use the list comprehension in most cases because it is easier to read. filter() can use slightly less memory for very large inputs because it processes items only as needed, and it reads cleanly when you already have a named function to pass instead of a lambda.
count()When you need to know how many times a string appears rather than where it is, the list count() method returns the number of matches. A result of 0 means the string is not in the list.
l1 = ['A', 'B', 'C', 'D', 'A', 'A', 'C']
s = 'A'
count = l1.count(s)
if count > 0:
print(f'{s} is present in the list {count} times.')
This reports the frequency of 'A':
A is present in the list 3 times.
The built-in methods cover almost every case, but a plain loop is worth knowing when you need to do extra work on each element during the search or want more control over how you loop through the list.
my_list = ["apple", "banana", "cherry", "banana"]
found = False
for item in my_list:
if item == "banana":
found = True
break
print(found)
This stops at the first match and prints:
True
This approach is longer and usually slower than the in operator for simple checks, so use the built-in methods unless the loop body does something a comprehension cannot express cleanly.
Each method fits a different goal. The following table matches common use cases to the recommended approach, what it returns, and its time complexity. The complexity figures follow the official Python Wiki time-complexity reference, where n is the number of elements in the list.
| Method | Use case | Returns | Time complexity | Notes |
|---|---|---|---|---|
in operator |
Check if an exact element exists | bool |
O(n) | Most readable membership test; exact match only |
not in operator |
Check that an element is absent | bool |
O(n) | Negative membership test |
list.index() |
Position of the first exact match | int (or ValueError) |
O(n) | Wrap in try/except ValueError |
enumerate() + comprehension |
All positions of an exact match | list[int] |
O(n) | Returns every matching index |
List comprehension with in |
All elements containing a substring | list[str] |
O(n) | Substring match on each element |
any() |
Whether any element contains a substring | bool |
O(n) | Short-circuits at the first match |
next() |
First element containing a substring | str (or default) |
O(n) | Returns one match, then stops |
count() |
Number of occurrences | int |
O(n) | 0 means not present |
re.search() in comprehension |
Pattern-based filtering | list[str] |
O(n * m) | m is pattern cost; use only for patterns |
set membership (in) |
Repeated exact-match lookups | bool |
O(1) average | No substring matching; build the set once |
When you work with large datasets, the cost of an O(n) scan adds up if you search the same data many times. A few changes to how you store the data can cut that cost a lot.
The most effective change is to use a set for repeated checks. Converting a list to a set with set(my_list) gives O(1) average lookups instead of O(n), because sets use hash tables instead of arrays. The trade-off is that sets do not keep order, drop duplicates, and cannot do substring matching, so they fit exact-match checks rather than pattern searches.
allowed = ["admin", "editor", "viewer", "guest"]
allowed_set = set(allowed)
print("editor" in allowed_set)
Because the lookup uses a hash table, this returns instantly even for very large collections:
True
Two more options are worth knowing. Dictionaries give the same O(1) average lookup as sets, but they also map each key to a value, which is ideal when you need extra data and not just a yes/no answer. For data that is already sorted, the standard library bisect module finds elements in O(log n) time using binary search, which is faster than a full scan when you can keep the list in order.
The patterns above are common in real Python code. Here are three practical examples of how you might use these string search methods in everyday tasks.
Scanning logs for lines that mention an error is a classic substring-filtering task, and a list comprehension solves it cleanly:
log_lines = [
"INFO startup complete",
"ERROR database connection failed",
"WARNING disk almost full",
"ERROR timeout while reading socket",
]
errors = [line for line in log_lines if "ERROR" in line]
print(errors)
This isolates the two error lines:
['ERROR database connection failed', 'ERROR timeout while reading socket']
Checking that a submitted value is one of a known set is an exact-match check. Building a set first keeps the check fast even when you validate many inputs:
allowed_roles = {"admin", "editor", "viewer"}
submitted = "editor"
if submitted in allowed_roles:
print(f"{submitted} is a valid role")
else:
print(f"{submitted} is not allowed")
Because "editor" is in the set, this prints:
editor is a valid role
Picking files by extension or naming convention is a job for regular expressions, since the target is a pattern rather than a fixed string:
import re
paths = ["report.csv", "image.png", "data.csv", "notes.txt"]
csv_files = [p for p in paths if re.search(r"\.csv$", p)]
print(csv_files)
This keeps only the paths ending in .csv:
['report.csv', 'data.csv']
The fastest way to search for an exact string is the in operator, which returns True or False. Use it whenever you only need to know whether the string is there:
my_list = ["apple", "banana", "cherry"]
if "banana" in my_list:
print("Found!")
If you need the position instead of a True/False answer, use index() inside a try/except block so a missing value does not crash your program:
my_list = ["apple", "banana", "cherry"]
try:
index = my_list.index("banana")
print(f"Found at index {index}")
except ValueError:
print("Not found")
Use the in operator, which is the standard way to check membership and returns True or False directly. It reads almost like plain English and is the best default for a yes/no check:
my_list = ["apple", "banana", "cherry"]
if "banana" in my_list:
print("Exists")
else:
print("Does not exist")
To find elements that contain a substring (rather than equal it exactly), use a list comprehension with the in operator, because in checks for substrings inside individual strings. This returns every element that contains the target text:
my_list = ["apple", "banana", "cherry"]
part = "an"
filtered_list = [item for item in my_list if part in item]
print(filtered_list) # ['banana']
For a simple yes/no answer instead of the matching items, use any(part in item for item in my_list).
To find a specific string and get its position, use the index() method, which returns the index of the first match and raises ValueError if the string is missing. Always wrap it in try/except so a missing value is handled gracefully:
my_list = ["apple", "banana", "cherry"]
try:
index = my_list.index("banana")
print(f"Found at index {index}")
except ValueError:
print("Not found")
Use the list count() method, which returns how many times a value appears. A return value of 0 means the string is not in the list:
my_list = ["apple", "banana", "cherry", "banana"]
count = my_list.count("banana")
print(f"Count: {count}") # 2
Use a list comprehension with enumerate() to collect every index where the string appears, since index() returns only the first one. This gives you a list of all matching positions:
my_list = ["apple", "banana", "cherry", "banana"]
indexes = [i for i, val in enumerate(my_list) if val == "banana"]
print(indexes) # [1, 3]
The simplest approach is to convert both the target and each element to the same case with .lower() (or .upper()) before comparing them. This matches no matter how the text is capitalized:
items = ["Apple", "BANANA", "Cherry"]
target = "banana"
matches = [item for item in items if target.lower() in item.lower()]
print(matches) # ['BANANA']
When the search uses a pattern, use re.search("target", item, re.IGNORECASE) inside a comprehension instead.
Use regular expressions when the target is a pattern rather than fixed text, such as a date format, a file extension, or a structured ID. For exact or simple substring matching, the plain in operator or str.find() is faster and easier to read:
import re
files = ["a.log", "b.txt", "c.log"]
logs = [f for f in files if re.search(r"\.log$", f)]
print(logs) # ['a.log', 'c.log']
Methods that scan the whole list, such as the in operator, index(), count(), and list comprehensions, are O(n), which means the cost grows with the length of the list. If you need to run many checks on the same data, convert the list to a set once for O(1) average lookups, as documented in the Python Wiki time-complexity reference. Keep in mind that sets support only exact matching, not substring searches.
list.index() and list.find() in Python?Python lists do not have a find() method, so calling my_list.find(...) raises an AttributeError. The find() method belongs to strings and returns -1 when the substring is missing. For lists, use index() (which raises ValueError if the value is missing) or a list comprehension when you need to find elements:
text = "banana"
print(text.find("na")) # 2 (string method)
fruits = ["apple", "banana"]
print(fruits.index("banana")) # 1 (list method)
In this tutorial, you learned how to find a string in a Python list using the right tool for each goal: the in operator for fast membership checks, index() and enumerate() for positions, list comprehensions and any()/next() for substring searches, .lower() and re.IGNORECASE for case-insensitive matching, regular expressions for pattern matching, and filter() as an alternative to comprehensions. You also saw how a set turns repeated O(n) scans into O(1) lookups, and how these patterns apply to log filtering, input validation, and file-path matching.
For most situations, the in operator is the best default. Use index() when you need a position, comprehensions when you need substring matches, and regular expressions only when you are matching a pattern.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Java and Python Developer for 20+ years, Open Source Enthusiast, Founder of https://www.askpython.com/, https://www.linuxfordevices.com/, and JournalDev.com (acquired by DigitalOcean). Passionate about writing technical articles and sharing knowledge with others. Love Java, Python, Unix and related technologies. Follow my X @PankajWebDev
I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer(Team Lead) @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix
With over 6 years of experience in tech publishing, Mani has edited and published more than 75 books covering a wide range of data science topics. Known for his strong attention to detail and technical knowledge, Mani specializes in creating clear, concise, and easy-to-understand content tailored for developers.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.