Decision Tables
How to turn business rules with several conditions into a simple table, collapse it, derive test cases from it, and use it to find gaps and contradictions in requirements.
A food delivery app launches two features in the same sprint: free delivery for members on orders of €30 or more, and a €1 surcharge on rainy days. Both are tested separately, both pass. On the first rainy Saturday, the support team is flooded: members with big orders are being charged €1 for “free” delivery. Is that a bug? Nobody knows, because nobody ever decided what happens when both rules apply at once.
That missing decision would have been obvious in five minutes with a decision table.
What is a decision table?
A decision tableDecision tableA table listing combinations of conditions and the action the software should take for each.Open in glossary → is a grid that lists every combination of conditions in a business rule, and the action the system should take for each one. Each column is one rule: “if these conditions are true, do this”.
Here’s the smallest possible example, a login screen with two conditions:
| Conditions | R1 | R2 | R3 | R4 |
|---|---|---|---|---|
| Email registered? | Y | Y | N | N |
| Password correct? | Y | N | Y | N |
| Actions | ||||
| Result | Log in | Error | Error | Error |
Decision tables are a black-boxBlack-box testingTesting based only on inputs and outputs, without looking at the code.Open in glossary → technique: you build them from the requirementsRequirementA statement of what the software must do or how well it must do it.Open in glossary →, not from the code. They sit alongside equivalence partitioning and boundary value analysis, which handle single inputs, while decision tables handle combinations.
The quick version
When to use one
Use a decision table when the outcome depends on two or more conditions together: discounts, fees, eligibility, permissions, login and security rules. If you catch yourself reading “if… and… unless…”, draw a table.
How to build one
- List the conditions. Things that can be true or false and change the outcome (member? order ≥ €30? rainy day?).
- List the actions. What the system does (charge a fee, show an error, log in).
- Write every combination. With n yes/no conditions you get 2ⁿ columns: 2 conditions → 4, 3 → 8, 4 → 16.
- Fill in the action for each column from the requirement. If you can’t, you’ve found a gap.
- Collapse. When two columns have the same action and differ in only one condition, that condition doesn’t matter there: merge them and write ”–” (don’t care).
- Turn each column into a test case.
Why testers love them
Decision tables make missing rules (“what if it’s rainy and the member’s order is over €30?”) and contradictory rules (two rules that both apply but say different things) jump out. Finding those before coding is one of the cheapest bug fixes there is.
The limit
Every extra condition doubles the table. With 10 conditions you’d have 1,024 columns. Then teams use risk to pick the important ones, or pairwise testing, which covers every pair of values with far fewer tests.
When to use a decision table
Decision tables shine when several conditions combine to decide one outcome. Typical places in mobile and web apps:
- Prices and fees: delivery fees, discounts, shipping, surcharges, loyalty rewards.
- Eligibility: loan or credit card approval, free trials, age-restricted content.
- Access and security: login with 2FATwo-factor authenticationLogging in with two proofs, usually a password plus a one-time code.Open in glossary →, roles and permissions, account lockout.
- Business workflows: whether an order can be canceled, refunded or changed.
They’re not the best tool when there’s only one input (use equivalence partitioning and boundaries), or when the outcome depends on the order of events, like an order moving from “placed” to “delivered” (use state transition testing).
Building one step by step: login with 2FA
The requirement for a banking app:
“Users log in with email and password. If the email or password is wrong, show Incorrect email or password. If both are correct and the user has two-factor authentication (2FA) turned on, ask for the 6-digit code. If 2FA is off, log the user straight in.”
Step 1: list the conditions
Read the requirement and underline everything that can be true or false and changes what happens:
- Is the email registered?
- Is the password correct?
- Is 2FA turned on for the account?
Things like “the phone is in dark mode” don’t change the outcome, so they’re not conditions here.
Step 2: list the actions
What can the system do? Show the error, ask for the 2FA code, or log the user in.
Step 3: count and write every combination
Each yes/no condition doubles the number of combinations, so 3 conditions give 2 × 2 × 2 = 2³ = 8 rules. A simple trick to write them all without missing any: in the first row, write half Y then half N (YYYY NNNN); in the second, alternate in pairs (YY NN YY NN); in the last, alternate one by one (Y N Y N…).
Step 4: fill in the actions
Go column by column and ask “according to the requirement, what happens here?”
| Conditions | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
|---|---|---|---|---|---|---|---|---|
| Email registered? | Y | Y | Y | Y | N | N | N | N |
| Password correct? | Y | Y | N | N | Y | Y | N | N |
| 2FA on? | Y | N | Y | N | Y | N | Y | N |
| Actions | ||||||||
| Show error | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Ask for 2FA code | ✓ | |||||||
| Log in | ✓ | |||||||
Step 5: collapse the table
Six columns end in “Show error”. Do we need six tests? Look at R3 and R4: same action, and they differ only in “2FA on?”. So 2FA doesn’t matter when the password is wrong: merge them into one column with ”–” (don’t care) for 2FA. Repeat until no two columns can be merged:
| Conditions | R1 | R2 | R3 | R4 |
|---|---|---|---|---|
| Email registered? | Y | Y | Y | N |
| Password correct? | Y | Y | N | – |
| 2FA on? | Y | N | – | – |
| Actions | ||||
| Show error | ✓ | ✓ | ||
| Ask for 2FA code | ✓ | |||
| Log in | ✓ | |||
The merge rule is simple: same actions, and exactly one condition different. Never merge columns with different actions; their difference is the whole point. The order of merges can matter too: an early merge sometimes blocks a better one. If you end up with more columns than you expected, try merging in a different order.
Turning columns into test cases
Each column is one test case: the conditions become preconditionsPreconditionSomething that must already be true before you can start a test.Open in glossary → and test dataTest dataThe input values, accounts and records you use while testing.Open in glossary →, and the actions become the expected resultExpected resultWhat should happen when you perform a test step, according to the requirements.Open in glossary →. For ”–”, pick any value, ideally the riskier one. See how to write test cases for the full format.
TC-DT-01Correct credentials with 2FA on: the app asks for the codepositivehigh
Preconditions
- Account anna@test.com exists with 2FA turned on
Steps
- Open the app
- Enter anna@test.com and the correct password
- Tap Log in
Expected result
The 6-digit code screen appears; the user is not logged in yet.
TC-DT-02Correct credentials with 2FA off: the user is logged straight inpositivehigh
Preconditions
- Account ben@test.com exists with 2FA turned off
Steps
- Open the app
- Enter ben@test.com and the correct password
- Tap Log in
Expected result
The Home screen opens; no code is requested.
TC-DT-03Wrong password shows the generic error (2FA on)negativehigh
Preconditions
- Account anna@test.com exists with 2FA turned on
Steps
- Enter anna@test.com and a wrong password
- Tap Log in
Expected result
"Incorrect email or password" is shown; no code is requested; the user stays on the login screen.
TC-DT-04Unregistered email shows the same generic errornegativemedium
Steps
- Enter nobody@test.com and any password
- Tap Log in
Expected result
The exact same "Incorrect email or password" message is shown (it must not reveal that the email does not exist).
Tip: click a test case to expand it.
Notice TC-DT-03 picks “2FA on” for the don’t-care condition on purpose: it’s the riskier choice, because it catches the “asked for the code before checking the password” bug. TC-DT-04 links nicely to account enumerationAccount enumerationFinding out which emails are registered from differences in error messages or responses.Open in glossary →, covered in login and sign-up.
Second example: a delivery fee
Back to the rainy Saturday. The requirement:
“Delivery normally costs €3.99. Members get free delivery on orders of €30 or more, and pay €1.99 on smaller orders. On rainy days, a €1.00 surcharge is added to every order.”
| Conditions | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
|---|---|---|---|---|---|---|---|---|
| Member? | Y | Y | Y | Y | N | N | N | N |
| Order ≥ €30? | Y | Y | N | N | Y | Y | N | N |
| Rainy day? | Y | N | Y | N | Y | N | Y | N |
| Actions | ||||||||
| Delivery fee | €1.00 | €0.00 | €2.99 | €1.99 | €4.99 | €3.99 | €4.99 | €3.99 |
Two things stand out:
- R1 needs a decision. Read literally (“every order”), a member’s free delivery costs €1 on rainy days. Maybe that’s intended, maybe not. A tester’s job is to ask before release, not guess.
- Only two pairs collapse. For non-members, the order size doesn’t matter (R5 = R7, R6 = R8), so 8 columns become 6. Rain matters in every rule, so it can never be ”–”. Not every table shrinks much, and that’s fine.
Third example: credit card approval
“Applicants under 18 are always rejected. Adults with a credit score of 650 or higher are approved instantly. Adults with a lower score go to manual review if they have a regular income, and are rejected if they don’t.”
After collapsing:
| Conditions | R1 | R2 | R3 | R4 |
|---|---|---|---|---|
| 18 or older? | Y | Y | Y | N |
| Score ≥ 650? | Y | N | N | – |
| Regular income? | – | Y | N | – |
| Actions | ||||
| Decision | Approve | Review | Reject | Reject |
Real rules are often less tidy. If the bank later adds “scores from 600 to 649 get a lower credit limit”, the score is no longer yes/no: it has three ranges. You can still use a decision table, with a row whose values are ranges (< 600, 600–649, ≥ 650) instead of Y/N. This is sometimes called an extended-entry table. The number of columns is then the product of the number of values per condition (here 2 × 3 × 2 = 12).
Spotting missing and contradictory rules
This is the hidden superpower of decision tables. Because you’re forced to fill in every column, two kinds of requirement bugs become impossible to miss:
| Problem | What you notice | Example |
|---|---|---|
| Gap (missing rule) | A column where no rule says what happens | “Free cancellation before the restaurant accepts” and “€2 fee after accepting” — but what about after the courier picks up? |
| Conflict (contradiction) | A column where two rules apply and disagree | “Not enough money → Insufficient funds” and “Face ID fails → ask for PIN”: what if both are true? |
| Impossible combination | A column that can’t happen in reality | Courier picked up, but restaurant never accepted |
When you find a gap or conflict, don’t decide yourself. Raise it with the product owner or business analyst, ideally while the user storyUser storyA short description of a feature from the user's point of view.Open in glossary → is still being refined. It’s a five-minute conversation now, versus an angry rainy Saturday later.
Limits: when the table explodes
The number of columns doubles with every yes/no condition:
| Conditions | Combinations |
|---|---|
| 3 | 8 |
| 5 | 32 |
| 10 | 1,024 |
| 20 | over 1 million |
This is called combinatorial explosion. Ways to handle it:
- Collapse with don’t-cares, as above.
- Split the rule into smaller, independent tables (fees in one, eligibility in another).
- Prioritize by risk: test the combinations that involve money, security or the most users first.
- Pairwise testing: instead of every combination, choose a set of tests where every pair of condition values appears at least once. Many bugs are triggered by an interaction between just two conditions, so this catches a lot with very few tests: for 10 yes/no conditions, a handful of tests instead of 1,024. Free tools (such as PICT from Microsoft) generate these sets. It’s a useful idea to know for later; for most manual testing, a well-collapsed decision table is enough.
Common mistakes
- Mixing up conditions and actions. “Show an error” is something the system does, not a condition.
- Missing a combination. Use the YYYY NNNN / YY NN / Y N pattern so you never skip one.
- Merging columns with different actions. Only merge when the actions are identical.
- Guessing to fill a gap. An empty column is a question for the team, not for your imagination.
- Testing only one value for ”–”. Fine for low-risk rules; for payments and security, add the risky value too.
Try it: build a decision table
Pick a requirement, find the conditions, fill in every rule, collapse the table, and watch the test cases appear.
🧮 Decision-table builder
Turn a business rule into a table, and the table into test cases, in four steps.
RequirementUsers log in with email and password. If the email or password is wrong, show “Incorrect email or password”. If both are correct and the user has two-factor authentication (2FA) turned on, ask for the 6-digit code. If 2FA is off, log the user straight in.
Step 1. Which of these are conditions (inputs that can be true or false and change the outcome)? Leave out actions (what the system does) and things that don’t matter.
0 conditions selected → 20 = 1 combination (columns).
Try it: find the gap
Decision tables don’t just produce tests. They expose holes in requirements. In each round, decide for every column whether the requirement covers it.
🔍 Find the gap
Each requirement is laid out as a full decision table. For every column, decide: is it covered by a rule, impossible, a gap (no rule says what happens) or a conflict (two rules disagree)?
Food delivery: canceling an order
- R1. Customers can cancel for free while the restaurant hasn’t accepted the order.
- R2. If the restaurant has accepted the order but the courier hasn’t picked it up yet, canceling costs a €2.00 fee.
Column 1
- Restaurant accepted?
- Y
- Courier picked up?
- Y
Column 2
- Restaurant accepted?
- Y
- Courier picked up?
- N
Column 3
- Restaurant accepted?
- N
- Courier picked up?
- Y
Column 4
- Restaurant accepted?
- N
- Courier picked up?
- N
Practice: free checked bag
Build a decision table for this airline app rule, collapse it, and write down the test cases. Then compare with the answer.
“Business class passengers always get a free checked bag. Economy passengers get one free if they are Gold members or if the flight is longer than 6 hours. Otherwise, a checked bag costs €35.”
Show the answer
Three conditions (Business class? Gold member? Flight over 6 hours?) give 2³ = 8 combinations. All four Business columns are free, so they collapse to one. For Economy, Gold members are free whatever the flight length.
| Conditions | R1 | R2 | R3 | R4 |
|---|---|---|---|---|
| Business class? | Y | N | N | N |
| Gold member? | – | Y | N | N |
| Flight > 6h? | – | – | Y | N |
| Actions | ||||
| Checked bag | Free | Free | Free | €35 |
That’s 4 test cases. Good extra ideas: test a flight of exactly 6 hours (“longer than 6 hours” means 6:00 should cost €35, a boundary worth confirming), and ask the team whether a Gold member in Business gets a second free bag. The rule doesn’t say, so it’s a question, not an assumption.
Decision table checklist
0 of 8 checked
Check your understanding
Quick quiz
0/6 answered · 0 correct
1.A rule depends on four yes/no conditions. How many columns does the full decision table have?
2.Which of these is a condition, not an action, in a delivery fee rule?
3.Two columns have the same action and differ only in 'Rainy day?'. What can you do?
4.While building a table, you find a column that no rule in the requirement covers. What should you do?
5.Two rules both apply to the same column but prescribe different actions. What is that called?
6.A rule has 12 yes/no conditions. Why might the team use pairwise testing instead of a full decision table?
Key takeaways
- Use a decision table when several conditions combine to decide an outcome: fees, discounts, eligibility, login and permission rules.
- Build it in order: conditions → actions → all 2ⁿ combinations → fill in actions → collapse → test cases.
- Merge two columns only when they have the same actions and differ in exactly one condition; that condition becomes ”–” (don’t care).
- Every remaining column is one test case. For risky ”–” values, test the riskier option too.
- An empty column is a gap, two disagreeing rules are a conflict: both are requirement bugs worth raising early.
- Tables explode with many conditions; collapse, split, prioritize by risk, or use pairwise testing.
Finished this page?Nice work, this page is complete!
Mark it as done to track your progress. It's saved in this browser, no account needed.
Its review cards are now in Bites ⚡