Python
Programming
String Manipulation
Substring
Coding Skills

How to check if a string is a substring of items in a list of strings

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Python, the simplest way to check whether a string appears inside any item in a list is to combine the substring operator in with any(). That gives you a short, readable solution and short-circuits as soon as a match is found.

The Basic Pattern

If you only need a yes-or-no answer, use a generator expression with any().

python
1def contains_substring(needle: str, items: list[str]) -> bool:
2    return any(needle in item for item in items)
3
4
5words = ["pineapple", "banana", "grapefruit"]
6print(contains_substring("apple", words))  # True
7print(contains_substring("pear", words))   # False

This is the standard Python answer because it is direct and efficient. The generator does not build an intermediate list, and any() stops on the first True.

Return the Matching Items Instead of a Boolean

Sometimes you need more than a boolean. If you want the actual matches, use a list comprehension.

python
1def matching_items(needle: str, items: list[str]) -> list[str]:
2    return [item for item in items if needle in item]
3
4
5words = ["pineapple", "banana", "grapefruit", "crabapple"]
6print(matching_items("apple", words))

That prints:

python
['pineapple', 'crabapple']

This version is useful for filtering search results, validating input against allowed strings, or building suggestion lists.

Case-Insensitive Matching

A frequent bug comes from case sensitivity. The in operator is case-sensitive, so "api" is not found in "APIClient" unless you normalize both sides first.

python
1def contains_substring_case_insensitive(needle: str, items: list[str]) -> bool:
2    normalized = needle.casefold()
3    return any(normalized in item.casefold() for item in items)
4
5
6items = ["APIClient", "DatabasePool", "UserService"]
7print(contains_substring_case_insensitive("api", items))  # True

casefold() is better than lower() for robust text comparison because it handles more Unicode case-conversion rules.

Exact Match Versus Substring Match

Be clear about the requirement. These two checks are not the same:

python
needle in item
needle == item

The first checks whether the text appears anywhere inside the item. The second checks whether the entire string is identical. Many bugs come from solving the wrong problem because the difference was never stated clearly.

When Regular Expressions Make Sense

If you need more flexible matching, use re. For example, if you want to find strings that start with "user_" followed by digits, regular expressions are more appropriate than a plain substring check.

python
1import re
2
3pattern = re.compile(r"user_\d+")
4items = ["user_12", "guest", "user_test"]
5
6matches = [item for item in items if pattern.search(item)]
7print(matches)

Use regex only when you need pattern logic. For plain substring checks, in is simpler and faster.

A Useful Helper for Repeated Searches

If the operation appears in multiple places, wrap it in a helper so the intent is obvious.

python
1from collections.abc import Iterable
2
3def first_match(needle: str, items: Iterable[str]) -> str | None:
4    for item in items:
5        if needle in item:
6            return item
7    return None
8
9
10result = first_match("apple", ["pear", "pineapple", "banana"])
11print(result)  # pineapple

Returning the first match is often more useful than returning only True, especially when you need to display or process the matching string immediately.

Complexity Notes

For a list of n strings, Python may inspect each one until it finds a match. Inside each string, substring search depends on the string length and the pattern. In practice, the plain in solution is fast enough for most application code, and the main performance win comes from short-circuiting early with any().

If you are searching the same large dataset repeatedly, the better optimization is often to rethink the data structure or precompute an index rather than micro-optimizing the loop.

Common Pitfalls

  • Using a list comprehension inside any() and creating an unnecessary list. Prefer a generator expression.
  • Forgetting about case sensitivity when matching user-facing text.
  • Mixing up exact match and substring match.
  • Reaching for regular expressions when a plain in check is enough.
  • Assuming any() tells you which item matched. It only returns a boolean.

Summary

  • Use any(needle in item for item in items) for a clean boolean answer.
  • Use a list comprehension when you need all matches.
  • Normalize with casefold() for case-insensitive matching.
  • Use regex only when the requirement is pattern matching, not plain substring search.
  • Be explicit about whether you want a boolean, the first match, or all matching items.

Course illustration
Course illustration

All Rights Reserved.