Skip to content

How to Generate All Pairs from a List in Python

Learn efficient methods to generate all unique pairs from a list in Python using itertools.combinations, nested loops, and list comprehensions with examples and performance considerations.

Generating all unique pairs from a list is a common task in data processing, algorithm challenges, and statistical analysis. Given [1, 2, 3], the goal is to produce (1, 2), (1, 3), and (2, 3) without duplicates or reversed pairs.

In this guide, you will learn how to generate all unique pairs using itertools.combinations, nested loops, and list comprehensions. Each approach is explained with examples and output so you can choose the right method for your situation.

Using itertools.combinations

The itertools.combinations function is the standard and most efficient solution. It is implemented in C and returns a memory-efficient iterator:

from itertools import combinations

data = [1, 2, 3, 4]
pairs = list(combinations(data, 2))

print(pairs)

Output:

[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]

The second argument 2 specifies the size of each combination. Every unique pair appears exactly once, and no pair contains the same element twice.

For large datasets, iterate directly over the result without converting to a list. This keeps memory usage constant regardless of how many pairs exist:

from itertools import combinations

data = range(1000)

count = 0
for pair in combinations(data, 2):
    count += 1

print(f"Total pairs: {count}")

Output:

Total pairs: 499500

Using Nested Loops

When itertools is not available or not allowed (a common constraint in coding interviews), nested loops produce the same result:

data = ["A", "B", "C", "D"]

pairs = []
for i in range(len(data)):
    for j in range(i + 1, len(data)):
        pairs.append((data[i], data[j]))

print(pairs)

Output:

[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]

The key detail is range(i + 1, len(data)). Starting the inner loop at i + 1 ensures two things:

  • No self-pairing: an element is never paired with itself.
  • No duplicate pairs: (A, B) is generated but (B, A) is not.

Using a List Comprehension

The same nested loop logic can be written as a list comprehension:

data = ["A", "B", "C", "D"]

pairs = [(data[i], data[j])
         for i in range(len(data))
         for j in range(i + 1, len(data))]

print(pairs)

Output:

[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]

A Common Mistake: Generating Duplicate Pairs

A frequent error is starting the inner loop at 0 instead of i + 1, which produces both (A, B) and (B, A) as well as self-pairs like (A, A):

data = ["A", "B", "C"]

bad_pairs = [(data[i], data[j])
             for i in range(len(data))
             for j in range(len(data))
             if i != j]

print(bad_pairs)

Output:

[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]

This produces 6 results instead of the expected 3 unique pairs. The if i != j condition prevents self-pairing but does not prevent reversed duplicates. The correct approach starts the inner loop at i + 1:

data = ["A", "B", "C"]

good_pairs = [(data[i], data[j])
              for i in range(len(data))
              for j in range(i + 1, len(data))]

print(good_pairs)

Output:

[('A', 'B'), ('A', 'C'), ('B', 'C')]

Understanding the Number of Pairs

The number of unique pairs from n elements follows the binomial coefficient formula:

pairs = n * (n - 1) / 2
def expected_pair_count(n):
    return n * (n - 1) // 2

print(f"5 elements:   {expected_pair_count(5)} pairs")
print(f"10 elements:  {expected_pair_count(10)} pairs")
print(f"100 elements: {expected_pair_count(100)} pairs")

Output:

5 elements:   10 pairs
10 elements:  45 pairs
100 elements: 4950 pairs

You can verify this against itertools.combinations:

from itertools import combinations

data = list(range(10))
actual = len(list(combinations(data, 2)))
expected = 10 * 9 // 2

print(f"Actual: {actual}, Expected: {expected}, Match: {actual == expected}")

Output:

Actual: 45, Expected: 45, Match: True

Working with Pairs in Practice

Once you have generated pairs, common operations include filtering, aggregating, or comparing elements:

from itertools import combinations

numbers = [3, 7, 1, 9, 4]

pairs_sum_10 = [(a, b) for a, b in combinations(numbers, 2) if a + b == 10]
print(f"Pairs summing to 10: {pairs_sum_10}")

min_pair = min(combinations(sorted(numbers), 2), key=lambda p: p[1] - p[0])
print(f"Closest pair: {min_pair}")

Output:

Pairs summing to 10: [(3, 7), (1, 9)]
Closest pair: (3, 4)

Performance Comparison

Method Speed Memory Best For
itertools.combinations() Fast (C implementation) Lazy iterator Production code
Nested loop / comprehension Slower (Python-level) Builds full list Interviews, learning
enumerate with slicing Moderate Builds full list Readable one-liners

Conclusion

Use itertools.combinations(data, 2) for production code. It is the idiomatic, most efficient, and most readable approach.

  • Fall back to nested loops or list comprehensions when the standard library is not available or when you need to understand the underlying logic for an interview.
  • Always start the inner loop at i + 1 to avoid duplicate and reversed pairs, and iterate directly over the combinations object without converting to a list when processing large datasets.
Navigation