This article is written with developers in mind, but it applies equally to other team members, such as testers, business analysts, or product owners.
Truth tables are frequently seen in academic mathematics and logic, and they represent the complete set of permutations for input variables along with the corresponding results. 📝
When applied to real-world problems in software development though, they can be an invaluable tool for tackling difficult requirements and test scenarios. 🏆
What are truth tables?
This is a truth table as seen in academic circles:
^
represents the logical AND, often seen as &&
in C-based languages.
How to read this table
The header row is the header and labels the columns.
Each row underneath the header is a single scenario.
On the left-hand side, P
and Q
are input variables.
On the right-hand side, P ^ Q
is the result, or the output.
ie. “Given that the value of P
is TRUE
and the value of Q
is TRUE
, then the result of P AND Q
is TRUE
.” (Scenario 1)
The important thing is that the table represents the complete set of permutations of P
and Q
.
When to use a truth table
When and why would you want to apply one of these to your job?
You start to feel overwhelmed with requirements as they currently are
Putting things into this level of detail can add clarity both for you and everybody around you. It can also help reveal or uncover when there are hidden assumptions about how the system should work. 🕵️♀️
It might feel daunting or even counter-intuitive to undertake the task of creating one of these tables for your requirements; but creating a truth table organizes the mental jumble into something tangible that can be worked on piece by piece. 🧶
You want to clarify scenarios so that everybody’s in agreement
When working with a table like this, the spoken agreement is that if the system works exactly as the scenarios in the table specify, then everybody is happy.
When hashing out how a system it supposed to work, it’s not unusual for a group of people to get caught up on a couple things. Conversations go in circles. People get lost and confused. Time is wasted. Everybody loses. 😭
Using a truth table can highlight what those things are while also providing a sense of cohesion around everything the team already agrees on. 😎
There might be hidden edge cases
You have that nagging feeling that you’re missing something or that things feel overly simplistic. 😰
Functionality needs to be solid
Sometimes pieces of software need to work with little-to-no margin for error. Truth tables are great for helping ensure that what you’re building is solid and meets expectations. 💯
So, now …
How to construct a table: a complete example
If you’re already comfortable with the idea of creating these tables, it might be worth reading this section anyway. I make a couple mistakes and, in doing so, I demonstrate how building a table can drive and clarify requirements making for a more accurate system. Otherwise, just skip on down to making a case in the real world. 🙂
Business rules
I recently got to work with a library system. A scenario that came up for modifying holds on items has this set of business rules:
- Certain items must be approved before being checked out.
- Only certain people should be allowed to do the approval.
- The UI should only show whether it needs an approval if the item is in-hand (ie, it’s not checked out or at a different branch).
- If the item is currently “In Use” (from its “Status”), then we cannot edit the approval status. (Think about “In Use” for materials which need to stay in the library.)
1. Define your variables
Kind of like doing algebraic word problems in school, we should figure out our variables, both the inputs and the outputs. I’ve conveniently ordered them so that the inputs come first.
In this case:
- (item)
requires_approval
(INPUT) can_user_approve
(INPUT)item_status
(INPUT)should_ui_show
(OUTPUT)
Spoiler: These variables aren’t enough. We’ll see that come out later, though.
2. What are ALL the possible values for each of these variables?
requires_approval
(INPUT) :TRUE
,FALSE
can_user_approve
(INPUT) :TRUE
,FALSE
item_status
(INPUT) :NOT_AVAILABLE
,AVAILABLE
,IN_USE
should_ui_show
(OUTPUT) :TRUE
,FALSE
Since this is a blog, I’ve kept things simple and stuck to boolean values and a limited number of statuses. You may find yourself needing to be more creative, such as using number ranges. I’ve also ordered the statuses to most closely follow a hold’s lifecycle. It’s small, but it helps reduce mental load.
3. Create your table of inputs One. Column. At. A. Time.
For each column, create a row for each input permutation. Then duplicate the entire table so far for each of the permutations for your next column. Rinse, repeat, as below:
a. First column: requires_approval
b. Second column: can_user_approve
Copy and paste.
Add the second column’s possible inputs to get the complete matrix of possibilities.
c. Third column: item_status
. Which is also the last of our Inputs.
I copy-pasted the table three times, once for each item_status
option, then filled in the statuses.
Notice how every row is unique and we’re not missing any permutations. That’s critical - don’t skip permutations! If you want to do math, the total number of rows should be equal to the product of the number of options for each input. Aka. Total_Rows = (A_NumberOfOptions) * (B_NumberOfOptions) * (C_NumberOfOptions) (etc.)
In this case: Total_Rows = 2 * 2 * 3 = 12. Checks out! ✅
4. Fill in your output column(s)
This is where you figure out the behavior based on the business rules. I frequently like to add a “comments” or “remarks” column, but it’s not required. Additionally, I like to have some kind of visual cue between inputs and outputs. Because this is markdown, I’m going to add a small empty column. In a spreadsheet program, I’d make the vertical cell border have a heavy outline. Maybe I’d still add a small empty column with a different background color.
Uh oh.
Do you see what happened? By filling out this out, we uncovered a couple scenarios that weren’t clear:
- the item needs approval but the user doesn’t have permissions. What should happen?
should_ui_show
isn’t detailed enough. - the item is in hand, but doesn’t need approval… then what?
A clearer set of business rules
In this case, after checking back in with the business, they said that if there’s no approval required, they don’t want it to show at all.
Additionally, if an approval is required AND the item is available, they always want that information displayed.
For completion, here is the updated set of rules:
- Certain items must be approved before being checked out. (no change, it’s really a premise)
- Only certain people should be allowed to do the approval. The field should only be editable if somebody is allowed to edit the approval. Otherwise, the UI should show whether or not it’s been approved.
- The UI should only show whether it needs an approval the approval field if the item is in-hand (ie, it’s not checked out or at a different branch) AND the item requires approval.
- If the item is currently “In Use” (from its “Status”), then we cannot edit the approval status. (Think about “In Use” for materials which need to stay in the library.)
The updated table
I’ve fast-forwarded building the updated table based on the new rules.
Most notably, I changed the output variables to reflect the fact that visibility and editability are distinct.
-
denotes that the value is irrelevant
I also bolded things that I think somebody reading this should pay attention to as noteworthy or exceptional scenarios.
And it’s done! 🙌
For formatting, I generally recommend against, say, coloring every “TRUE” a certain color, or coloring by status, for example. Such coloring in bulk doesn’t necessarily aid comprehension. I do think, though, that judicious use of color can be invaluable for calling out edge-cases. However, because these tables are read row-by-row, it’s often more valuable to color key rows or fields. For example, using color to highlight unknown or tricky scenarios can draw attention to where it’s most needed.
5. (Optional) Trim your table
Looking at the table, there are some obvious things which could be distilled:
- When an approval isn’t required, the field should never show.
- If the user can not approve, the field should never be editable.
And that’s it! Now we can go build our code and tests with confidence. 🙂
I actually hesitate to make the second distillation. I feel very confident that if we were to turn the first table into test cases, we wouldn’t be able to accidentally introduce any bugs. But I’m slightly less confident in the second scenario. Logically it works out, so I’ll leave it as an example; but I don’t think it necessarily makes the table easier to read (and hence more valuable) in the same way that the first trim did.
Handling many scenarios
You may have noticed that as we add variables and permutations, these tables can become large quickly. While it may be daunting as scope grows, it’s worth going through the process.
Applying tables to a real-world scenario, and why
When demonstrating how to construct a table, I chose an example that was relatively basic; and maybe you impatiently skimmed waiting for me to realize that of course visibility and editability needed to be distinct.
Below is a second scenario describing how, in the face of very complex rules, a truth table like this served as the basis for both establishing business rules and the subsequent coding.
In particular, I really want to demonstrate how these tables can drive and reveal business rules and serve as the baseline for development. And it’s a bonus that the scenarios translate directly into test cases.
Establishing rules
So … let’s talk about two-way data synchronization. Messy, right? The thought of it makes me cringe and look for other options, but we don’t always have that luxury. And sometimes we have synchronize between systems with dramatically different data models.
In the particular scenario I’m thinking of, it was a side-by-side migration from a mainframe into a commercial-off-the-shelf (COTS) system for judicial person records. In the mainframe, each of an individual’s Aliases were Person records linked together, and in the COTS system, each Alias was just a string in a list of names associated to the person. In both systems, Cases were associated with a Person. However, in the mainframe, Person-Aliases owned Cases and in the COTS system, Cases didn’t even reference an Alias. Addresses were similarly distributed and un-aligned.
In summation, we needed two-way synchronization between data models that were drastically different. Business did their best to create some rules, but the rules focused on field mapping and less on scenarios. The direction was basically “do C(R)UDy things that happen in one system in the other system.”
It wasn’t specific enough to create code that wasn’t riddled with bugs. What about when somebody was renamed? What about when an Person-Alias in the mainframe had a change of address? (There was NO link between aliases and addresses in the COTS.)
I don’t want to bore either of us by recreating the past, but here are some scenarios to get an idea of scope:
Mainframe person actions: Created, other modification1, name updated, record “deleted”, alias link created, alias link removed
COTS person actions: Created, other modification1, primary name updated, alias added, alias deleted2
Target System: Person exists, person doesn’t exist, address is current/not-current, name exists/doesn’t, etc.
1 other modifications might include changes in their drivers license number, physical characteristics, or address. Addresses were a whole different mess.
2 The COTS system didn’t have the concept of renaming an alias. It only allowed Add and Delete.
Fast-forward a little to having a fairly large table with all the input scenarios laid out.
Using the table of scenarios, we were able to work directly with business to fill in the expected outcome for each scenario.
It wasn’t fast. In an initial meeting, we were able to fill in maybe three-quarters of the table.
In a series of follow-ups, after they had time to think and confer with other stakeholders and the COTS vendor, we almost finished it. However, there was one scenario that dragged so long that us developers just started drawing pictures of roller-coasters going off a cliff (that scenario) and pointed to it whenever somebody asked about status. (I think it had to do with deleting an alias. 🙄 )
The manyfold benefits of this tool
I really, really want to point out some beneficial side-effects that came from creating a giant table and sharing it:
- 🪨 We were confident that if all of the scenarios were covered, our system would be solid.
- 🦟 There were no developer assumptions – either something the developer actively decided, or an outcome that was implied in conditional logic but never actively considered.
- 👍 We had business buy-in on exactly what we were building and how it would behave.
- 🧪 We had a list of test scenarios that, if they passed, would guarantee that the logic was correct.
- 🧱 We were able to clearly point out where we were blocked on decisions.
- ⛵️ By forcing the business (and ourselves) to go through this process, we were able to notice similarities in conditions and outcomes which surfaced business rules.
- For example, some of the rules we came up with were “If an Alias is added in the COTS and doesn’t exist, make a new Person record in the mainframe and link it to the primary Person record.”, “If an Alias is renamed in the mainframe, just drop and re-create all the Aliases in the COTS.”, “Overwrite an Address in the mainframe with whatever the COTS system says is current”, etc.
- 🏡 By getting to clearer business rules, we were able to code the system in an efficient and maintainable fashion. (The initial coding (ahem, a prior dev) for this was a very long list of if-else statements that was impossible to grok, much less maintain or debug. By having clarity, it ended up being a small handful of classes with almost completely set-based logic. And I ended up with a lot of extra time on my hands for doing other important things, like assembling jigsaw puzzles in the lunchroom 🧩, reading the stack exchange hot network questions 🔥, and building a dashboard to help with synchronization debugging 🐞.)
Other thoughts and guidelines
To conclude, here are some final tips for getting value:
- 📈 Always start with the intention of creating the complete set of scenarios in the table. As you fill it out, you’ll likely arrive at some simple rules that let you distill it down. Nonetheless: Always default to verbosity over brevity.
- 🧠 Using a spreadsheet program is far easier to work with than a word document or markdown file.
- 🎨 Use color coding where it adds clarity. Also, it’s better to reserve color for indicating exceptions or grouping scenarios rather than, for example, coloring true = green, false = red.
- 👩💼 Keep things meaningful for business. (TRUE/FALSE or YES/NO instead of 1/0)
- 👀 Where possible, choose words that scan easily. (YES/NO instead of Y/N – the different number of characters means our brains only need to notice how much space is used.)
- ❓ A “Comments/Remarks” column can be very useful to help document why a particular conclusion was reached or why there’s still uncertainty.
- ✍️ Don’t hesitate to spell out some guiding business rules above the table, especially as they’re uncovered.
- 📢 Make it public.
- 🦻 Ask for input.
- 🥳 Celebrate when the hard work pays off.
Join the conversation about this post on our N.E.A.T community
Not a N.E.A.T. community member yet? More info.