# Equivalence Partitioning

Dividing inputs into classes expected to behave identically, then testing one representative from each.

**Leads to:** API Testing, Boundary Value Analysis, Pairwise Testing

**Rests on nothing.** A place to start.

*Software Engineering · Software Testing*


## Why learn this

Imagine you are writing a test for a simple age-based discount rule: under 5 is free, 5–60 is full price, over 60 is half-price. If you check ages 3 and 45 and both work, you might assume every age works—but the rule changes at 5 and 60. These change-points are called boundary points. Boundary Value Analysis is a testing method that checks the inputs exactly at these limits and just inside or outside them to catch errors where behavior shifts. Equivalence Partitioning complements this by letting you group ages into ranges where the behavior stays the same, so you test one sample per range plus those critical boundary points. This way you replace checking 120 possible ages with just a few smart choices.


## The intuition

Consider the same train ticket function in India. The input is age. The rule: children under 5 travel free; people aged 5 to 60 pay full fare; senior citizens over 60 get a 50% discount. Without a method you might pick ages 3, 27, 45, and 72. That misses what happens at the turning points: age 5 exactly, age 60 exactly, age 0, age 120. The function changes its logic at those edges.

Group the ages into classes where the outcome is identical:
- Under 5: free ticket. Sample: age 3. Edge tests: age 4 (last free), age 5 (first full).
- 5 to 60: full fare. Sample: age 30 or 45. Edge tests: age 5 (first full), age 60 (last full).
- Over 60: half-price. Sample: age 72. Edge tests: age 61 (first half), age 60 (last full).

If the code handles age 30 correctly, it will almost certainly handle age 45 correctly because both are in the same class with no logic change between them. Three or four well-chosen tests cover the behavior that would otherwise require checking 120 ages. The coverage is nearly the same as testing every value; only the boundary points carry the real risk, and Boundary Value Analysis focuses there.


## An analogy

A customs officer at an airport sorts passengers into groups like diplomats, crew, business-class travelers, and economy passengers with no checked bags, then inspects one representative per group. The group acts as a stand-in for everyone inside it because the processing rules are the same within each group; testing one reveals how all behave. A short caveat: this only works when the groups truly share the same handling logic, which in software means relying on the function's rules rather than visible surface traits.


## Definition

Equivalence Partitioning is a test design technique that splits all possible inputs into ranges where the system behaves the same, then picks one sample from each range to represent it. This lets you replace many tests with a small set of representative values while still covering every distinct behavior.


## How it connects

It sits alongside Boundary Value Analysis (testing the exact limits and just-inside/outside points where rules change) and Pairwise Testing (combining choices from multiple input ranges instead of testing each alone).


## The mathematics

Equivalence Partitioning is a method for choosing test cases by grouping inputs that should behave the same way. It relies on two basic ideas from arithmetic and logic: sets and subsets.

A set is just a collection of items. In software testing, we start with the set of all possible inputs a program might receive. Let us call this set D. For example, if the program asks for a person's age, D contains every valid age number the system accepts, such as 0, 1, 2, up to 120.

Partitioning means splitting this big set D into smaller groups called subsets. We do this by drawing lines between numbers based on how the program works. The rules for splitting are strict:

1. Every number in D must belong to exactly one subset. No number is left out, and no number is in two groups at once.
2. The groups must not overlap.

Imagine the set of ages from 0 to 120. We split it into three groups based on ticket pricing rules:
- Group A: Ages 0 through 4. (The rule says these are free.)
- Group B: Ages 5 through 60. (The rule says these pay full price.)
- Group C: Ages 61 through 120. (The rule says these get a half-price discount.)

Notice that every age from 0 to 120 is in one of these three groups. Age 4 is only in Group A. Age 5 is only in Group B. There is no gap and no double-counting.

The core assumption of this method is that if the program works correctly for one number in a group, it will work correctly for all numbers in that group. This is because the program's logic (its code) treats all numbers in that range identically. For example, the code likely uses an 'if' statement like 'if age < 5'. This condition is true for 0, 1, 2, 3, and 4. The computer does not check each number individually; it checks the condition once per input. So, testing age 2 tells us about the behavior for ages 0, 1, 3, and 4 as well.

To create a test suite, we pick one representative number from each group. We do not need to test every single age. We only need to test enough to prove the logic holds for each distinct path through the code.

Mathematically, if D is our set of all inputs, and we have created n subsets P1, P2, ..., Pn, then:
- The union of all subsets equals D. This means every possible input is covered.
- The intersection of any two different subsets is empty. This means no input belongs to more than one group.

We choose one number xi from each subset Pi. The collection of these chosen numbers forms our test suite T. If the program handles each representative correctly, we assume it handles the entire range of inputs in that partition correctly.


## A worked example

Let us walk through a concrete example using ages and ticket prices.

The rules for the ticket price are:
- If age is less than 5, the price is 'free'.
- If age is between 5 and 60 (inclusive), the price is 'full'.
- If age is greater than 60, the price is 'half'.

We assume the valid input range for age is integers from 0 to 120.

Step 1: Identify the valid partitions based on the logic.

The code likely looks like this:
if (age < 5) {
  return 'free';
} else if (age >= 5 AND age <= 60) {
  return 'full';
} else {
  return 'half';
}

This logic creates three distinct paths. We must create one partition for each path.

Partition 1: The 'free' path.
The condition is 'age < 5'. The valid ages in our range that satisfy this are 0, 1, 2, 3, and 4.
Subset P1 = {0, 1, 2, 3, 4}.

Partition 2: The 'full' path.
The condition is 'age >= 5 AND age <= 60'. The valid ages are 5, 6, ..., 60.
Subset P2 = {5, 6, ..., 60}.

Partition 3: The 'half' path.
The condition is 'age > 60'. The valid ages are 61, 62, ..., 120.
Subset P3 = {61, 62, ..., 120}.

Check coverage: Do these subsets cover all inputs from 0 to 120? Yes. 0-4 is covered by P1. 5-60 is covered by P2. 61-120 is covered by P3. There are no gaps.

Step 2: Identify invalid partitions (error handling).
Good testing also checks what happens when the input is wrong. The code might crash or give an error for ages outside 0-120.

Partition 4: Negative ages.
Subset P4 = {ages < 0}. For example, -5.

Partition 5: Ages above 120.
Subset P5 = {ages > 120}. For example, 150.

Step 3: Choose representatives.
We pick one number from each subset to test.

From P1 (0-4), we choose 2. Why? Because the logic 'age < 5' is true for 2. If it works for 2, it should work for 0, 1, 3, and 4 too.
From P2 (5-60), we choose 30. The logic '5 <= age <= 60' is true for 30.
From P3 (61-120), we choose 70. The logic 'age > 60' is true for 70.
From P4 (<0), we choose -5.
From P5 (>120), we choose 150.

Our test suite T is the set of these five numbers: {2, 30, 70, -5, 150}.

Step 4: Compare effort.
Without equivalence partitioning, we might test every integer from 0 to 120. That is 121 test cases.
With equivalence partitioning, we test only 5 cases.

Why is this safe? Because the code does not treat age 2 differently from age 3. It treats them both as 'less than 5'. Therefore, testing one representative proves the logic for the whole group. 

Note on boundaries: While equivalence partitioning picks a number in the middle (like 30), it is also good practice to test the edges of the groups (like 4, 5, 60, 61) separately. This is called boundary value analysis. It catches errors where the code might accidentally include or exclude the edge numbers. But for the core logic of 'free', 'full', and 'half', the five representatives above are sufficient to verify the main paths.


## From scratch, in Python

```python
# We will write a Python function that represents the ticket price logic,
# then partition its inputs and test each partition.

# First, the function we are testing. It takes an integer age and returns a string.
def ticket_price(age):
    if age < 5:
        return 'free'
    elif age <= 60:
        return 'full'
    else:
        return 'half'

# Now we write a simple test helper that runs a test case and reports pass/fail.
# In Python, we can define a function that takes a description and a result.
def test_case(description, actual, expected):
    if actual == expected:
        print('PASS:', description)
    else:
        print('FAIL:', description, '- got', actual, '- expected', expected)

# Based on equivalence partitioning, we choose representatives from each class:
# Partition 1: ages < 5 (free) -> representative 2
# Partition 2: ages 5-60 (full) -> representative 30
# Partition 3: ages > 60 (half) -> representative 70
# Partition 4: negative ages (invalid) -> representative -5
# Partition 5: ages > 120 (invalid) -> representative 150

# We also test boundary values to catch edge cases, although that is the next topic.
# For now, we stick to the partitions.

test_case('child under 5', ticket_price(2), 'free')
test_case('adult 5-60', ticket_price(30), 'full')
test_case('senior over 60', ticket_price(70), 'half')
test_case('negative age', ticket_price(-5), 'free')  # Unexpected! The function returns 'free' because -5 < 5. This is a bug to fix later.
test_case('age 150', ticket_price(150), 'half')  # Works, but may not be intended.

# Output will show that the negative age test FAILS, revealing a flaw in the logic.
# This is exactly why invalid partitions matter.
```


## The same thing, with a framework

```python
In real projects, you would use a testing framework like pytest. Below is the same set of tests written with pytest, which is the standard tool. The framework runs the test functions and reports failures automatically. Underneath, pytest is doing exactly what our manual test_case function did: comparing actual to expected and reporting.
```


## What the code does

This code introduces two Python basics: functions and if-else conditions. A function is a block of re-usable code that takes input (called a parameter) and returns a value. The line `def ticket_price(age):` defines a function named ticket_price that takes one input called age. The indented lines below it are the function body. `if age < 5:` checks a condition; if it is true, the next line returns the string 'free'. If not, `elif age <= 60:` checks another condition, and so on. The `test_case` function takes three inputs: a description, the actual result from calling ticket_price, and the expected result. It compares them with `==` and prints PASS or FAIL. The test cases themselves call ticket_price with a representative value and pass the expected result. Running this code shows that the negative age case fails, which is the point: invalid partitions often reveal bugs. The library note says 'Invalid partitions matter as much as valid ones' — this is the demonstration.


## Common misconceptions

**Testing one representative is enough for the whole partition** — If I test age 30 and it works, I don't need to test age 31 or 29.

The partition assumption holds only when the code actually treats all values in the class the same way. If the logic has an off-by-one error (for example, the condition is age > 60 instead of age >= 60), then age 60 and age 61 belong to different classes, but you would not know unless you test the boundary. Partitioning itself does not catch boundary bugs — that is what boundary value analysis is for. In fact: Partitioning reduces the number of test cases, but you should still test the edges of each partition (the boundary values). The interior of a partition can be represented by one value, but the edges deserve their own tests.

**Invalid partitions are optional** — I only need to test the things a normal user would enter. Negative ages will never happen.

Real users do enter unexpected data — by accident, or through malicious input, or through integration bugs. If the code does not handle invalid input gracefully, it can crash, corrupt data, or become a security hole. Testing invalid partitions reveals these vulnerabilities before they reach production. In fact: Invalid partitions are just as important as valid ones. You should test at least one representative from each invalid class, and the expected behaviour is often an error or a safe default, not a crash.

**Partitioning is the same as boundary testing** — Equivalence partitioning and boundary value analysis are the same thing — you pick values from everywhere.

They are complementary. Partitioning finds the ranges of input that share behaviour; boundary analysis looks specifically at the edges of those ranges, where off-by-one errors cluster. Partitioning without boundaries misses edge bugs; boundaries without partitioning waste effort on values that are all alike. In fact: Use partitioning to choose the ranges, then use boundary analysis to choose the exact values at the edges of each range. Combined, they give a small, powerful test suite.

**Any representative from a valid partition works, so I can always pick the middle** — If 5 to 60 is a partition, age 30 is a safe choice — but I'll also need to test every age in that range to be sure.

The whole point of partitioning is that if the code handles one value in the range correctly, it is highly likely to handle all of them — because they all follow the same code path. Testing every value is redundant and wasteful. But the trick is that you must confirm the partition actually represents a single code path, which requires reading the logic. In fact: Choose one representative from each valid partition, but verify that the partition reflects the code's branching. If you are not sure, look at the conditions that control the function to see where the boundaries are.


## In short

Equivalence partitioning is a systematic way to choose test cases: divide the input domain into classes that behave the same, test one representative from each, and never forget the invalid classes. It cuts testing effort dramatically without losing much coverage, but only if the partitions reflect the real logic of the code. Use it as the first step in any testing effort, and pair it with boundary value analysis for the edges. The library's key points — partition by logic, not interface; invalid partitions matter — are the difference between a test suite that catches bugs and one that only looks busy.


## Where this is used

Equivalence partitioning is used every day in software testing across the AI stack. In large language model (LLM) development, it is used to design test suites for prompt evaluation and model behaviour. For example, when testing whether a model correctly refuses harmful prompts, developers create partitions of prompts: benign queries, malicious instructions, borderline attempts, and so on. Each partition is tested with a few representative prompts rather than thousands of possible ones. Similarly, when evaluating the quality of generated text, developers partition inputs by domain (legal, medical, casual chat) and test a sample from each. In small language models running on phones or laptops, the constraints are tighter. Testing must be efficient because there is little compute. Partitioning helps choose a representative set of inputs that exercises all the code paths in the model's tokenizer and inference logic. For example, a keyword-spotting model that runs on a smart speaker has input audio; partitions include short phrases, long phrases, background noise, different accents. Testing one clip from each partition verifies the model's coverage without exhausting the test budget. In retrieval-augmented generation (RAG), the system combines a retrieval step and a generation step. Partitioning applies to the queries: questions that have a clear answer in the knowledge base, questions without an answer, ambiguous queries, multi-part queries. Each partition reveals different failure modes — for instance, a query with no answer should trigger a 'I don't know' response, not a hallucination. Testing one representative from each partition catches these classes of errors. In agentic systems that plan, call tools and act in loops, partitioning is crucial because the input space is enormous: sequences of actions, tool results, user interruptions. Developers partition the space into states — normal operation, tool failure, repeated failures, user cancels, and so on — and test one scenario from each. This is how they verify the agent handles a tool timeout without crashing. In everyday applied machine learning — recommendation, vision, forecasting, fraud detection, speech — equivalence partitioning is used when testing the data pipeline and the model evaluation. For a fraud detection model, input partitions include normal transactions, flagged transactions, edge cases like zero amount or foreign currency. A developer writes a unit test that feeds one representative from each partition into the model and checks the output class. For a forecasting model, partitions might be days of the week, holidays, and anomalies. The library note 'Partitioning correctly requires understanding the logic, not just the interface' is the key: in AI systems, the logic is often the model's behaviour, which is learned from data, not written by hand. So partitions reflect expected behaviour classes. In practice, a developer writing an AI application reaches for pytest or a similar test framework, defines a test function for each partition, and asserts the model's output satisfies a property. When done badly — partitions that do not reflect the logic — the tests miss whole classes of failures. The classic example is testing only typical inputs and never invalid ones, so a negative age slips through and breaks the system. This is why the library insists invalid partitions matter as much as valid ones.


---

Read this on the web: https://basavapurushottam.com/learn-ai/equivalence-partitioning
