Data Filtering
List Management
Resource Constraints
Algorithm Efficiency
Programming Techniques

Filtering list under limitations

Master System Design with Codemia

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

Introduction

Filtering a list is easy when you can hold everything in memory and evaluate every rule freely. It becomes harder when the task has limitations such as memory budgets, time limits, bounded output size, or expensive predicates. The right design is usually not a single clever expression; it is a filtering pipeline that spends work only where it matters.

Define the Actual Limitation First

The phrase "under limitations" is too vague unless you name the constraint. Common ones are:

  • the list is too large to load fully
  • only the first N matches are needed
  • each predicate call is expensive
  • filtering must finish within a time budget
  • output must fit into a bounded buffer

Once the limitation is explicit, the implementation becomes much easier to reason about.

For example, if you only need the first five matches, do not filter the entire list and slice afterward:

python
1from itertools import islice
2
3values = range(1, 1000)
4result = list(islice((x for x in values if x % 7 == 0), 5))
5print(result)

That stops as soon as enough matches are found.

Prefer Streaming over Materializing

When memory is the limiting factor, avoid building intermediate lists. Generators let you process one item at a time.

python
1def read_numbers():
2    for i in range(1, 1_000_001):
3        yield i
4
5
6filtered = (x for x in read_numbers() if x % 2 == 0 and x % 5 == 0)
7
8for item in filtered:
9    print(item)
10    if item >= 50:
11        break

This approach keeps memory usage small because the entire dataset is never stored at once.

The same principle applies to files and database cursors. If the data source already supports streaming, keep the pipeline streaming all the way through.

Order Predicates by Cost and Selectivity

If some conditions are much cheaper than others, evaluate cheap rejects first. That can drastically reduce total work.

python
1def expensive_check(x):
2    print(f"expensive check for {x}")
3    return x % 11 == 0
4
5
6values = range(1, 30)
7result = [x for x in values if x > 10 and x % 2 == 0 and expensive_check(x)]
8print(result)

Here, the cheap conditions x > 10 and x % 2 == 0 eliminate most candidates before the expensive check runs.

This is one of the simplest ways to optimize filtering without changing the output semantics.

Bound the Result Size Explicitly

Another common limitation is that only a fixed number of results can be stored or returned. Use a loop that stops once the budget is reached.

python
1def filter_with_limit(items, predicate, limit):
2    result = []
3    for item in items:
4        if predicate(item):
5            result.append(item)
6            if len(result) == limit:
7                break
8    return result
9
10
11numbers = [4, 9, 12, 15, 18, 21, 24, 27]
12print(filter_with_limit(numbers, lambda x: x % 3 == 0, 3))

This is clearer than building the full filtered list and trimming it later.

If you are working with streams, return an iterator instead of a list so downstream code can apply its own limit lazily.

Offload Filtering Earlier When Possible

If the input comes from a database, API, or file parser, the cheapest list filter is often the one you never run locally. Push filtering down to the source when it can do the work more efficiently.

For example, instead of loading all rows and filtering in Python:

python
1rows = [
2    {"id": 1, "status": "open"},
3    {"id": 2, "status": "closed"},
4    {"id": 3, "status": "open"},
5]
6
7open_rows = [row for row in rows if row["status"] == "open"]
8print(open_rows)

in a real database-backed system you would usually ask the database for open rows directly. The same logic applies to paginated APIs and search indexes.

Make Failure Modes Explicit

Limitations often imply compromises. If the filter can stop early due to time or memory budget, expose that behavior instead of silently returning partial results as if they were complete.

A practical interface might return both matches and a completion flag:

python
1import time
2
3
4def filter_with_deadline(items, predicate, seconds):
5    deadline = time.time() + seconds
6    result = []
7
8    for item in items:
9        if time.time() > deadline:
10            return result, False
11        if predicate(item):
12            result.append(item)
13
14    return result, True
15
16
17matches, completed = filter_with_deadline(range(100_000), lambda x: x % 997 == 0, 0.01)
18print(len(matches), completed)

That is much safer than returning partial data without context.

Common Pitfalls

  • Filtering the full dataset when only the first few matches are actually required.
  • Materializing large intermediate lists when a generator pipeline would work.
  • Running expensive predicates before cheap elimination checks.
  • Applying local filtering after loading data that could have been filtered at the source.
  • Returning partial results under time or memory limits without telling the caller that the result is incomplete.

Summary

  • The correct filter strategy depends on the specific limitation, not on the list itself.
  • Use streaming pipelines when memory is tight.
  • Order predicates so cheap rejects happen before expensive checks.
  • Stop early when the result size or time budget allows it.
  • Make truncated or partial filtering outcomes explicit to the caller.

Course illustration
Course illustration

All Rights Reserved.