sets
superset
algorithm
data structures
programming

Quickly checking if set is superset of stored sets

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

If you need to check whether a query set is a superset of many stored sets, the main question is how often you will run the query and what the element universe looks like. A nave subset check against every stored set can be fine for small data, but for repeated queries you usually want either bitset encoding or an inverted index that narrows the candidate sets before you test them exactly.

The Direct Baseline

Suppose you store many sets and receive a query set Q. A stored set S matches if Q is a superset of S, which is equivalent to S being a subset of Q.

In Python, the direct baseline is:

python
1stored_sets = [
2    {1, 2},
3    {2, 3, 4},
4    {5},
5    {1, 4},
6]
7
8query = {1, 2, 4, 5}
9
10matches = [s for s in stored_sets if query.issuperset(s)]
11print(matches)

This is already quite good when the number of stored sets is small or when queries are infrequent.

Why The Nave Scan Gets Expensive

If you have:

  • many stored sets
  • many queries
  • medium or large stored-set sizes

then checking every stored set on every query becomes wasteful. Most stored sets can often be eliminated quickly if you use indexing.

The optimization goal is not to make the subset test itself magical. It is to reduce how many stored sets need to be tested at all.

Option 1: Bitset Representation

If elements come from a reasonably small fixed universe, bitsets are extremely effective.

Map each element to a bit position. Then a stored set S is a subset of query set Q exactly when:

  • '(S_mask & Q_mask) == S_mask'

Example:

python
1universe = {"a": 0, "b": 1, "c": 2, "d": 3, "e": 4}
2
3
4def to_mask(items):
5    mask = 0
6    for item in items:
7        mask |= 1 << universe[item]
8    return mask
9
10stored = [
11    to_mask({"a", "b"}),
12    to_mask({"b", "c", "d"}),
13    to_mask({"e"}),
14]
15
16query = to_mask({"a", "b", "d", "e"})
17
18matches = [mask for mask in stored if (mask & query) == mask]
19print(matches)

This is very fast because subset checks become bit operations.

Bitsets are often the best answer when the universe is dense and bounded.

Option 2: Inverted Index For Sparse Universes

If the universe is large or sparse, an inverted index can be better.

Build a mapping from element to the IDs of stored sets containing that element.

python
1from collections import defaultdict
2
3stored_sets = [
4    {1, 2},
5    {2, 3, 4},
6    {5},
7    {1, 4},
8]
9
10index = defaultdict(set)
11for i, s in enumerate(stored_sets):
12    for item in s:
13        index[item].add(i)

For a query set Q, you can gather promising candidates by looking only at stored sets whose elements are all present in Q. A simple way is to count how many elements of each stored set are covered by Q.

python
1from collections import Counter
2
3query = {1, 2, 4, 5}
4counts = Counter()
5
6for item in query:
7    for set_id in index[item]:
8        counts[set_id] += 1
9
10matches = [
11    stored_sets[set_id]
12    for set_id, covered in counts.items()
13    if covered == len(stored_sets[set_id])
14]
15
16print(matches)

This avoids checking every stored set explicitly.

Which Structure Should You Choose

Use bitsets when:

  • the universe is small enough to map compactly to bits
  • you want very fast repeated queries
  • memory layout matters more than flexibility

Use an inverted index when:

  • the universe is large or sparse
  • stored sets vary a lot in content
  • you want candidate pruning without dense bit vectors

Use the nave scan when:

  • the dataset is small
  • code simplicity matters most
  • performance is already acceptable

The best solution depends more on the data model than on the subset operation itself.

Size-Based Pre-Filtering Helps Too

A simple optimization that works with any strategy is to discard stored sets larger than the query set immediately.

If |S| > |Q|, then Q cannot be a superset of S.

So even a basic implementation can group stored sets by size or filter them by length before checking membership.

That is cheap and often worthwhile.

Common Pitfalls

  • Overengineering the solution before measuring whether a plain subset scan is already fast enough.
  • Using bitsets when the universe is huge and sparse, which wastes memory.
  • Forgetting that the real relation is stored_set query_set, not the other way around.
  • Building an inverted index but still scanning every stored set afterward and losing most of the benefit.
  • Ignoring simple filters such as stored-set size.

Summary

  • A query set is a superset of a stored set exactly when the stored set is a subset of the query.
  • For small workloads, a direct subset scan is often fine.
  • For small fixed universes, bitsets make superset checks extremely fast.
  • For large sparse universes, an inverted index helps prune candidates efficiently.
  • Pick the data structure based on query volume and universe size, not only on the abstract set operation.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.