Generating all sublists (also called subsets or subsequences) of a list is a fundamental operation in combinatorics, algorithm design, and data analysis. Whether you need contiguous sublists (consecutive slices) or all possible subsets (including non-contiguous combinations), Python provides several approaches to accomplish this.
In this guide, you will learn multiple methods to generate sublists, understand the difference between contiguous sublists and all subsets, and choose the right approach based on your specific needs.
Understanding the Difference: Sublists vs. Subsets
Before diving into methods, it's important to distinguish between two related but different concepts:
Contiguous sublists (subarrays): consecutive elements from the original list:
[1, 2, 3] → [1], [2], [3], [1, 2], [2, 3], [1, 2, 3]All subsets (subsequences): any combination of elements, maintaining order:
[1, 2, 3] → [], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]Notice that [1, 3] is a subset but not a contiguous sublist because 1 and 3 are not adjacent in the original list.
Generating All Subsets
Method 1: Using itertools.combinations (Recommended)
The most efficient and Pythonic way to generate all subsets uses itertools.combinations for each possible subset length:
from itertools import combinations
a = [1, 2, 3]
subsets = []
for r in range(len(a) + 1):
for combo in combinations(a, r):
subsets.append(list(combo))
print(subsets)Output:
[[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]Or as a compact one-liner:
from itertools import combinations
a = [1, 2, 3]
subsets = [list(combo) for r in range(len(a) + 1) for combo in combinations(a, r)]
print(subsets)Output:
[[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]How it works:
range(len(a) + 1)generates lengths from 0 (empty subset) tolen(a)(full list).combinations(a, r)produces all subsets of lengthr, maintaining element order.- The nested comprehension collects all subsets into a single flat list.
Method 2: Using Recursion
A recursive approach builds subsets by deciding for each element whether to include it or not:
def get_subsets(a, index=0):
"""Generate all subsets recursively."""
if index == len(a):
return [[]]
remaining_subsets = get_subsets(a, index + 1)
with_current = [[a[index]] + subset for subset in remaining_subsets]
return with_current + remaining_subsets
a = [1, 2, 3]
subsets = get_subsets(a)
print(f"Total subsets: {len(subsets)}")
print(subsets)Output:
Total subsets: 8
[[1, 2, 3], [1, 2], [1, 3], [1], [2, 3], [2], [3], []]How it works:
- Base case: When the index reaches the end of the list, return
[[]](only the empty subset). - Recursive step: Get all subsets of the remaining elements, then create new subsets by prepending the current element to each.
- Return both: subsets with and without the current element.
Method 3: Using Bit Manipulation
Each subset can be represented as a binary number where each bit indicates whether the corresponding element is included:
a = [1, 2, 3]
n = len(a)
subsets = []
for i in range(2 ** n):
subset = [a[j] for j in range(n) if i & (1 << j)]
subsets.append(subset)
print(f"Total subsets: {len(subsets)}")
print(subsets)Output:
Total subsets: 8
[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]How it works:
- For
[1, 2, 3], binary101(decimal 5) means include elements at index 0 and 2:[1, 3]. i & (1 << j)checks if bitjis set in numberi.- Iterating from
0to2ⁿ - 1covers all possible combinations.
Generating Contiguous Sublists Only
Method 4: Using List Comprehension
For contiguous sublists (consecutive slices), nested iteration over start and end indices is the cleanest approach:
a = [1, 2, 3]
sublists = [a[i:j] for i in range(len(a)) for j in range(i + 1, len(a) + 1)]
print(sublists)Output:
[[1], [1, 2], [1, 2, 3], [2], [2, 3], [3]]Method 5: Using Nested Loops
The explicit loop version for clarity:
a = [1, 2, 3]
sublists = []
for i in range(len(a)):
for j in range(i + 1, len(a) + 1):
sublists.append(a[i:j])
print(f"Total contiguous sublists: {len(sublists)}")
for sub in sublists:
print(f" {sub}")Output:
Total contiguous sublists: 6
[1]
[1, 2]
[1, 2, 3]
[2]
[2, 3]
[3]Common Mistake: Confusing Contiguous Sublists with All Subsets
A frequent error is using the slicing approach and expecting it to produce all subsets:
Wrong expectation:
a = [1, 2, 3]
sublists = [a[i:j] for i in range(len(a)) for j in range(i + 1, len(a) + 1)]
print([1, 3] in sublists) # False ([1, 3] is NOT contiguous)Output:
FalseIncluding the Empty Sublist
Some applications require the empty sublist []. Here's how to include it with each method:
from itertools import combinations
a = [1, 2, 3]
all_subsets = [list(c) for r in range(len(a) + 1) for c in combinations(a, r)]
print("With empty:", all_subsets)
non_empty = [list(c) for r in range(1, len(a) + 1) for c in combinations(a, r)]
print("Without empty:", non_empty)Output:
With empty: [[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]
Without empty: [[1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]Performance Considerations
for n in [3, 5, 10, 15, 20]:
contiguous = n * (n + 1) // 2
all_subsets = 2 ** n
print(f"n={n:2d}: Contiguous sublists={contiguous:>6,} | All subsets={all_subsets:>10,}")Output:
n= 3: Contiguous sublists= 6 | All subsets= 8
n= 5: Contiguous sublists= 15 | All subsets= 32
n=10: Contiguous sublists= 55 | All subsets= 1,024
n=15: Contiguous sublists= 120 | All subsets= 32,768
n=20: Contiguous sublists= 210 | All subsets= 1,048,576Comparison of Methods
| Method | Produces | Includes Non-Contiguous | Count Formula | Time Complexity |
|---|---|---|---|---|
itertools.combinations |
All subsets | ✅ Yes | 2ⁿ | O(2ⁿ) |
| Recursion | All subsets | ✅ Yes | 2ⁿ | O(2ⁿ) |
| Bit manipulation | All subsets | ✅ Yes | 2ⁿ | O(n × 2ⁿ) |
| Slicing (comprehension) | Contiguous only | ❌ No | n(n+1)/2 | O(n²) |
| Nested loops | Contiguous only | ❌ No | n(n+1)/2 | O(n²) |
Summary
Generating sublists in Python depends on whether you need contiguous slices or all possible subsets:
- For all subsets (including non-contiguous): Use
itertools.combinations: the fastest, most Pythonic approach. - For contiguous sublists only: Use list comprehension with slicing: clean and efficient.
- For educational purposes or custom logic: Use recursion or bit manipulation to understand the underlying algorithms.
Key reminders:
- All subsets grow exponentially (2ⁿ): be cautious with large lists.
- Contiguous sublists grow quadratically (n²): much more manageable.
- Use
range(len(a) + 1)to include the empty subset, orrange(1, len(a) + 1)to exclude it. - Don't confuse contiguous sublists with all subsets: they produce different results.