Operation on every pair of element in a list
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Applying an operation to every pair of elements in a list comes up in similarity scoring, collision checks, graph building, recommendation systems, and many other algorithmic tasks. The correct implementation depends on what kind of pairs you need: unique unordered pairs, ordered pairs, or pairs that include an element with itself. Once that rule is clear, Python gives you clean ways to express it.
Decide Which Pairs You Actually Need
Before writing loops, answer these questions:
- Does order matter?
- Should an element be paired with itself?
- Do you want each pair once or both directions?
Those choices change the number of pairs dramatically. For a list of size n:
- unique unordered pairs:
n * (n - 1) / 2 - ordered pairs without self-pairs:
n * (n - 1) - ordered pairs with self-pairs:
n * n
If you do not answer that up front, it is easy to write code that silently duplicates work.
Unique Unordered Pairs With combinations
If the operation is symmetric, meaning f(a, b) is the same as f(b, a), use itertools.combinations.
This produces each pair once and never includes self-pairs. That makes it the usual default for distance calculations, overlap checks, and pairwise comparisons where direction does not matter.
Ordered Pairs With product
If direction matters, use the Cartesian product.
This includes both draft -> review and review -> draft, which is correct for transitions or dependency edges where the relationship is directional.
Nested Loops Still Have a Place
Do not assume itertools is always the answer. Plain index-based loops are still the clearest tool when you need positions, not just values.
That pattern is useful when you need to update a matrix, correlate two parallel arrays, or keep track of where each element came from.
Wrap the Operation in a Reusable Function
If the pairing rule shows up more than once, hide it behind a helper function so callers do not have to repeat the loop shape.
This keeps the pair semantics in one place, which makes the code easier to test and harder to misuse.
Think About Complexity Early
Pairwise work is usually quadratic. A list of 10 items gives you 45 unique unordered pairs. A list of 10,000 items gives you almost 50 million. That growth catches people off guard because the loop looks harmless on toy data.
If your input can get large, consider:
- filtering candidates before pairing
- grouping items into buckets first
- using vectorized libraries for numeric work
- streaming results instead of storing them all
A generator can help if you only need to consume pair results incrementally.
That avoids building a large intermediate list.
Common Pitfalls
The most common mistake is producing duplicate pairs accidentally. If you write two nested loops from 0 to n, you will usually get both (a, b) and (b, a) whether you wanted them or not.
Another issue is forgetting to exclude self-pairs. In some problems they are harmless; in others they completely distort the result.
Developers also underestimate the cost of quadratic algorithms. What works instantly for a list of 100 elements can become unusable for 100,000.
Finally, choose readability over cleverness. A direct combinations call or a simple nested loop is usually better than a dense comprehension nobody wants to debug.
Summary
- Decide first whether order matters and whether self-pairs are allowed.
- Use
itertools.combinationsfor unique unordered pairs. - Use
itertools.productfor ordered pairs. - Use index-based loops when positions matter as much as values.
- Remember that pairwise operations usually grow quadratically with input size.
Related reading
- Optimal algorithm for returning top k values from an array of length N
- Optimal Algorithm for Winning Hangman
- optimal algorithm grouping data in javascript
- Optimal algorithm to calculate the result of a continued fraction
- Operation Queue vs Dispatch Queue for iOS Application
- Optimal algorithm to return largest k elements from an array of infinite number of elements in running stream
- Optimizing Celery for third party HTTP calls
- Orange vs NLTK for Content Classification in Python

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 courseTrack 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.