To filter a list in Python, use a list comprehension with an if clause: [x for x in items if condition] builds a new list holding only the items that pass. The built-in filter(function, items) does the same job lazily, so wrap it in list():
prices = [42.50, 199.99, 87.25, 310.00]
[p for p in prices if p >= 100] # [199.99, 310.0]
list(filter(lambda p: p >= 100, prices)) # same result
My test machine for this page had Python 3.12.5; each output block is pasted straight from a Windows Command Prompt run.
In this tutorial, we’ll filter order amounts, products, customer records and strings, look at why filter() hands back an object, keep the rejected items too, filter a list in place without skipping elements, and time the three main approaches.
Run the filter examples yourself: a quick setup
If you’re new to running scripts, here’s the whole routine. You only need Python 3 installed; nothing else from pip is used on this page.
- Create a folder such as
C:\pyguidesand open it in any editor (Notepad works, VS Code is nicer). - Save the first example below as
filter_orders.pyin that folder. - Open Command Prompt (press Windows key, type
cmd, press Enter) and runcd C:\pyguides. - Run
python filter_orders.py. The printed lines should match the output block under the example. - Edit the condition, save, and run it again to see how the filtered list changes.
A predicate is just the test that returns True (keep) or False (drop). If loops still feel new, my walkthrough on looping through a list in Python covers the basics this page builds on.
Filter a list with a list comprehension
A list comprehension reads almost like English: give me each amount from orders if it’s at least 100. You can also change each kept item in the same expression, like adding sales tax:
orders = [42.50, 199.99, 87.25, 310.00, 15.75, 129.40]
big_orders = [amount for amount in orders if amount >= 100]
print("Orders of $100 or more:", big_orders)
# transform and filter in one pass
with_tax = [round(amount * 1.0825, 2) for amount in orders if amount >= 100]
print("With 8.25% Texas sales tax:", with_tax)
print("Original list untouched:", orders)
Output:
Orders of $100 or more: [199.99, 310.0, 129.4]
With 8.25% Texas sales tax: [216.49, 335.57, 140.08]
Original list untouched: [42.5, 199.99, 87.25, 310.0, 15.75, 129.4]

Notice the original list is untouched. Filtering builds a new list, which is usually what you want, because other code may still need the full data.
Comprehensions can do a lot more than filtering. The full syntax is in my Python list comprehension tutorial, and if you need an else branch that replaces items instead of dropping them, see list comprehension with if-else.
Filter a list with filter() and a lambda
filter() takes a function and an iterable and keeps each item for which the function returns something truthy. A lambda is a one-line nameless function, handy for short tests:
orders = [42.50, 199.99, 87.25, 310.00, 15.75, 129.40]
result = filter(lambda amount: amount >= 100, orders)
print(result) # a lazy filter object, not a list
print(list(result)) # convert it to see the items
def is_small(amount):
return amount < 50
print(list(filter(is_small, orders))) # a named function works too
mixed = [0, 25.0, None, "", "NY", [], 99.5, False]
print(list(filter(None, mixed))) # None keeps only truthy values
Output:
<filter object at 0x0000020DCEE79120>
[199.99, 310.0, 129.4]
[42.5, 15.75]
[25.0, 'NY', 99.5]

Three things to take from that run. A named function like is_small works exactly like a lambda. Passing None as the function drops every falsy value, so 0, None, empty strings, empty lists and False all go.
That None trick is handy, but careful: it also drops a real 0 or 0.0. filter() belongs to the same family as the map() function and reduce() in Python, and the three chain together nicely.
Why does filter() return a filter object instead of a list?
filter() is lazy. It returns an iterator that tests items only when something asks for them, which saves memory on big inputs. The catch is that an iterator can be used once:
zips = ["10001", "94105", "60601", "73301", "02134"]
west = filter(lambda z: z.startswith("9"), zips)
print("First pass: ", list(west))
print("Second pass:", list(west)) # already used up
west = list(filter(lambda z: z.startswith("9"), zips))
print("Stored list:", west, "length", len(west))
Output:
First pass: ['94105']
Second pass: []
Stored list: ['94105'] length 1
The second pass prints an empty list because the first list() call consumed every item. If you need the results more than once, or you need len() or indexing, store them in a list right away.
Laziness pays off when you only want the first match. next(filter(test, items), None) stops at the first hit instead of scanning everything. For picking items by position rather than by a condition, see selecting items from a list in Python.
How to filter a list with multiple conditions
Join tests with and or or inside the comprehension. Here the products are tuples of name, price and quantity on hand:
products = [
("Laptop stand", 49.99, 120),
("USB-C hub", 34.50, 0),
("Monitor arm", 129.00, 18),
("Desk lamp", 22.75, 64),
("Webcam", 89.99, 7),
]
in_stock_under_100 = [p for p in products if p[1] < 100 and p[2] > 0]
print("In stock and under $100:")
for name, price, qty in in_stock_under_100:
print(f" {name:<13} ${price:>6.2f} qty {qty}")
low_or_out = [p[0] for p in products if p[2] == 0 or p[2] < 10]
print("Reorder:", low_or_out)
rules = [lambda p: p[1] >= 30, lambda p: p[2] >= 5]
print("Pass every rule:", [p[0] for p in products if all(rule(p) for rule in rules)])
Output:
In stock and under $100:
Laptop stand $ 49.99 qty 120
Desk lamp $ 22.75 qty 64
Webcam $ 89.99 qty 7
Reorder: ['USB-C hub', 'Webcam']
Pass every rule: ['Laptop stand', 'Monitor arm', 'Webcam']
Python checks and conditions left to right and stops at the first false one, so put the cheap or most selective test first. Wrap mixed and/or logic in parentheses so nobody has to remember precedence rules.
When the rules live in a list, as in the last line, all() applies every one of them. Swap in any() to keep items that pass at least one rule; I cover both in the Python all() function and the Python any() function.
Filter a list of dictionaries by key value
Real data often arrives as a list of dictionaries, for example rows from a JSON API or a CSV. Filtering works the same way; you just read keys inside the condition:
customers = [
{"name": "Emily Carter", "state": "TX", "balance": 1250.00, "active": True},
{"name": "James Miller", "state": "CA", "balance": 0.00, "active": True},
{"name": "Olivia Brown", "state": "TX", "balance": 320.75, "active": False},
{"name": "Noah Davis", "state": "NY", "balance": 4180.40, "active": True},
{"name": "Ava Wilson", "state": "TX", "balance": 89.99, "active": True},
]
texas = [c for c in customers if c["state"] == "TX" and c["active"]]
for c in texas:
print(f'{c["name"]:<13} ${c["balance"]:>9,.2f}')
owing = [c["name"] for c in customers if c.get("balance", 0) > 1000]
print("Balance over $1,000:", owing)
Output:
Emily Carter $ 1,250.00
Ava Wilson $ 89.99
Balance over $1,000: ['Emily Carter', 'Noah Davis']

Olivia is in Texas but inactive, so she’s left out. Using c.get("balance", 0) instead of c["balance"] keeps the filter from crashing with a KeyError when a record is missing that key.
Filtering the keys and values of a single dictionary is a different job; I cover it in filtering a dictionary in Python.
Filter strings, empty values and None from a list
Text lists need a bit more care because blank strings, whitespace and None sneak in. Clean them first, then filter by prefix, substring or a regular expression:
import re
cities = ["Austin", "", "Boston", None, " ", "Denver", "Dallas", "boise"]
clean = [c for c in cities if c and c.strip()]
print("Non-empty:", clean)
starts_d = [c for c in clean if c.lower().startswith("d")]
print("Start with D:", starts_d)
has_on = [c for c in clean if "on" in c]
print("Contain 'on':", has_on)
codes = ["INV-2026-0042", "REF-77", "INV-2026-0108", "inv-2025-0003", "INV-26-9"]
pattern = re.compile(r"INV-2026-\d{4}")
print("2026 invoices:", [c for c in codes if pattern.fullmatch(c)])
Output:
Non-empty: ['Austin', 'Boston', 'Denver', 'Dallas', 'boise']
Start with D: ['Denver', 'Dallas']
Contain 'on': ['Boston']
2026 invoices: ['INV-2026-0042', 'INV-2026-0108']
c and c.strip() removes None, empty strings and whitespace-only entries in one test. Lowercasing before startswith() makes the match case-insensitive, which is why lowercase “boise” isn’t picked up by the D filter but would be by a B one.
Note that "on" in c is case-sensitive; only Boston contains a lowercase “on” here. More on that operator in the in operator in Python. For codes, fullmatch() rejects the lowercase and short-year entries a looser check would let through.
Keep the rejected items with filterfalse() or a single loop
Sometimes you want both halves: what passed and what failed. itertools.filterfalse() is the mirror image of filter(), and a plain loop can split the list in one pass:
from itertools import filterfalse
scores = [91, 58, 77, 45, 88, 62, 99, 70]
passed = lambda s: s >= 70
print("Passed:", list(filter(passed, scores)))
print("Failed:", list(filterfalse(passed, scores)))
# one loop, two lists
keep, drop = [], []
for s in scores:
(keep if passed(s) else drop).append(s)
print("keep =", keep, " drop =", drop)
Output:
Passed: [91, 77, 88, 99, 70]
Failed: [58, 45, 62]
keep = [91, 77, 88, 99, 70] drop = [58, 45, 62]
Using the same passed function for both calls guarantees the two lists never overlap or miss an item. The single loop version reads the data only once, which matters when the input is a file or generator you can’t rewind.
Filter a list in place without skipping items
A common bug is calling remove() inside a for loop over the same list. Each removal shifts the remaining items left, so the loop skips the next one:
ids = [101, 102, 102, 103, 104, 104, 105]
broken = ids.copy()
for x in broken: # removing while looping skips items
if x % 2 == 0:
broken.remove(x)
print("Loop + remove():", broken)
alias = ids # another name for the same list
ids[:] = [x for x in ids if x % 2 != 0]
print("Slice assignment:", ids)
print("Alias sees it too:", alias)
Output:
Loop + remove(): [101, 102, 103, 104, 105]
Slice assignment: [101, 103, 105]
Alias sees it too: [101, 103, 105]