What is pairwise (all-pairs) test design, and why is it so much smaller than the full cross-product?
answer
- the full matrix is unaffordable
- not every combination, every pair
- one row covers many pairs at once
- size tracks the two widest parameters
- covering array of strength two
basics
~20 sPairwise test design picks a small set of configurations in which every pair of values from two different parameters appears together at least once. It is tiny compared with the full cross-product because a single row covers many pairs simultaneously.
solid answer
~50 sPairwise, or all-pairs, design replaces the exhaustive cross-product of a parameter set with a **covering array**: a set of rows where, for every two parameters you pick, every combination of one value from each appears in at least one row. Take a quote engine with six parameters carrying 4, 3, 7, 2, 3 and 5 values. The full cross-product is 2,520 configurations; there are 232 distinct value pairs across the fifteen parameter pairings, and a generator can cover all of them in 43 rows. The shrink is violent because each row scores fifteen pairs at once, so row count is driven by the two largest value counts (here 7 x 5 = 35 is the floor) and grows only logarithmically as you add more parameters. It is a selection technique, not an oracle: it tells you which configurations to run, never what to assert.
code
pseudocode · 28 linesparameters = {
"product_line": ["PL1", "PL2", "PL3", "PL4"],
"cadence": ["monthly", "quarterly", "annual"],
"region": ["R1", "R2", "R3", "R4", "R5", "R6", "R7"],
"prior_claims": ["yes", "no"],
"channel": ["direct", "broker", "partner"],
"bundle": ["B1", "B2", "B3", "B4", "B5"]
}
uncovered = set()
for (p, q) in every_unordered_parameter_pair(parameters):
for vp in parameters[p]:
for vq in parameters[q]:
uncovered.add((p, vp, q, vq))
rows = []
while uncovered is not empty:
best_row = null
best_gain = -1
for candidate in sample_candidate_rows(parameters, count = 200):
gain = count_of(pairs_in(candidate) that are in uncovered)
if gain > best_gain:
best_gain = gain
best_row = candidate
rows.append(best_row)
uncovered = uncovered - pairs_in(best_row)
print(size_of(rows)) # 43 rows for the model above, versus 2520 exhaustivego deeper
Be ready to state the criterion in one sentence — every pair of values from two parameters appears together at least once — and to show with a small example why that is far fewer rows than the full cross-product.
An interviewer expects the mechanics: the pair count, the lower bound set by the two widest parameters, why growth is logarithmic in the number of parameters, and that a generator returns a good set rather than a provably minimal one.
Demonstrate that you treat the array as a selection device only. Talk about where the values came from, what the oracle asserts on each row, and how you keep the generated set stable enough to compare failures across runs.
Own the argument for adopting it at all: what the configuration matrix costs exhaustively, what risk a strength-2 guarantee leaves on the table, and how you would defend that residual risk to people who ask why every combination is not tested.
## The problem pairwise solves Configurable systems multiply. Take the quote path of an insurance quote engine with six configuration parameters: product line (4 values), payment cadence (3), rating region (7), prior-claims flag (2), sales channel (3) and discount bundle (5). The full cross-product is 4 x 3 x 7 x 2 x 3 x 5 = **2,520** distinct configurations. If one end-to-end quote run costs about 90 seconds, exhausting that matrix is roughly 63 hours of machine time. The configuration regression window inside a 3-week release train is one evening. Exhaustive testing is not merely expensive here; it is arithmetically unavailable. The naive alternative is to hand-pick "a few sensible combinations", which is unmeasurable: nobody can say what such a set covers or what it misses. Pairwise design gives that intuition a **criterion** so the resulting set can be counted, argued about and regenerated. ## The all-pairs criterion The criterion is: *for every choice of two distinct parameters, every combination of one value from the first and one value from the second appears together in at least one row.* Not every triple. Not every full configuration. Just every pair. Two counts make the idea concrete. The number of distinct pairs to cover is the sum, over all fifteen parameter pairings, of the product of their value counts. For the model above that is 232 pairs. The number of rows is bounded below by the product of the two largest value counts, because those 7 x 5 = 35 combinations must each occupy a different row. A generator produced **43 rows** for this model. Each row carries fifteen pairs at once, so 43 rows offer 645 pair slots for 232 distinct pairs — comfortable redundancy, and 1.7% of the cross-product. A set that satisfies this criterion is called a **covering array** of strength 2. "Strength" is the number of parameters whose interactions are guaranteed: strength 2 is pairwise, strength 3 is three-way, and strength equal to the parameter count is the exhaustive cross-product again. ## Why the reduction is so aggressive The leverage comes from reuse. A single row simultaneously supplies one pair for every parameter pairing, so pairs are covered in parallel rather than one at a time. Add a seventh parameter with 4 values and the cross-product multiplies by four — 10,080 configurations — while the covering array grows by only a handful of rows, because the new parameter's values can ride along in rows that already exist. The standard result is that covering-array size grows with the product of the largest domains and only **logarithmically** in the number of parameters. That asymmetry is the whole economic argument: parameters are cheap to add, wide parameters are not. ## How the set is built Three families of construction show up in practice. **Greedy row-at-a-time** methods repeatedly build the candidate row that newly covers the most uncovered pairs, then mark those pairs covered and repeat until none remain; it is simple, fast and produces near-minimal sets. **Parameter-order growth** methods start with a covering array over two parameters and extend it, first horizontally by adding a column for the next parameter and then vertically by appending rows for pairs that extension left uncovered. **Algebraic construction** derives rows from mathematical structures with strong balance properties. Minimising a covering array exactly is computationally hard, so real tools return a good set rather than a provably smallest one — which is why two tools, or two runs, can return different row counts for the same model. ## The empirical claim behind the technique, and its status Pairwise is usually justified by the claim that most failures are triggered by one or two interacting parameters, with only a minority needing three or more. Published studies of fielded systems do report shares in that direction, but the reported proportion varies substantially by domain and depends on how someone chose to model a "parameter" in the first place. Treat it as a useful default and a reason to *start* at strength 2 — not as a proven law, and never as evidence that three-way faults do not exist. A candidate who quotes it as a fixed percentage is overclaiming. ## What pairwise does not give you Three limits matter from day one. First, it does not choose the values a parameter contributes; that is decided before the array is generated, and a covering array over badly chosen values is a neatly organised waste of an evening. Second, it produces no **oracle** — the rows say what to run, never what a correct outcome looks like, and a suite that only checks for a crash will pass on configurations that quote the wrong premium. Third, it makes no claim about *order*: it covers which values coexist, not the sequence in which operations were performed. Interaction faults that depend on three parameters, or on ordering, fall outside a strength-2 guarantee by construction and have to be handled deliberately.
- What is the theoretical minimum number of rows for a strength-2 covering array, and why?The floor is the product of the two largest value counts. Those two parameters alone contribute that many pairs, and no row can contain two different values of the same parameter, so each of those combinations needs its own row. For the six-parameter model above the floor is 7 x 5 = 35, and a generator returned 43 — close to, but not necessarily at, the minimum, because computing a provably smallest covering array is hard.
- Two generators produce different row counts for the same model. Is one of them wrong?Not necessarily. Both may satisfy the strength-2 criterion; they simply search differently and neither guarantees minimality. What you should check is the coverage report — that every pair really is covered — rather than assuming the smaller set is better. A slightly larger, stable set that changes little between runs is often more useful than a minimal set that reshuffles every generation and destroys failure comparability across a release train.
- Does covering all pairs also cover all single values?Yes. Every pair contains one value from each of two parameters, so covering all pairs necessarily places every individual value of every parameter in at least one row. Strength-2 coverage subsumes strength-1 coverage. The converse fails badly: a set that uses every value once may cover only a small fraction of the pairs.
It is like seating guests at a dinner so that every pair of departments ends up sharing at least one table. You do not need a table for every possible group, because one table of six introduces fifteen pairs at once.
saying these in an interview costs you the question
- Claims pairwise tests every combination of parameters
- Says pairwise guarantees all defects will be found
- Thinks row count scales with the full cross-product
- Quotes the one-or-two-parameter fault claim as a proven law
- Assumes the generated rows also supply expected results
- Believes the smallest generated set is always the best one