How to reduce bugs: prohibiting nested if statements
The first article in our “how we reduce bugs” series, based on practices at ORT. This piece explains the rule that forbids nesting if statements.

Overview
One of programmers’ perennial questions is: how do we reduce bugs?
This article introduces the rule “do not nest if statements” from the set of “ways we reduce bugs through coding style” that we use at our company.
Motivation
When the author posted on social media that “we prohibit nested if statements” at our company, it drew strong agreement and disagreement.
In our development work we have seen benefits such as fewer bugs and more efficient reviews. A short social post cannot capture all of that.
The motivation for this article was to cover what could not be said in a single social post.
Technical choices
We develop both backend and frontend in JavaScript, not TypeScript, so sample code is basically JavaScript.
Nested-if structure is common across many languages, so we hope these ideas help anyone thinking about how to reduce bugs.
Coding style
In our coding style, nested if statements are forbidden.
else if is syntactic sugar for a nested if inside else, so it is forbidden as well.
The following two fragments express the same logic:
if (alphaCondition) {
// ...
} else if (betaCondition) {
// ...
}
if (alphaCondition) {
// ...
} else {
if (betaCondition) {
// ...
}
}
Guideline for reducing bugs
The most effective way to reduce bugs is to reduce the chance that bugs can appear.
It is a bit like “to reduce household pests, remove the places where they can hide.”
If you remove structures where bugs tend to lurk, it stands to reason that the risk of bugs goes down.
Complex logic
Years ago the author saw this on social media:
Perhaps every bug comes from if statements
The idea is that bugs arise when we get branching conditions or timing wrong.
Even now, years later, the author remembers thinking, “that makes sense.”
Nested if statements
Beginners often write intricate logic by combining if statements cleverly.
Nested ifs become complex quickly—even at only two levels.
Thinking of complexity growing roughly exponentially with depth is about right.
How to reduce bugs
Putting together the guideline above and the fact that nesting ifs increases complexity, you get:
If you do not nest if statements, you reduce bugs
To reduce bugs, it is important not to let people write overly complex logic. One way to achieve that is to forbid nested if statements.
Lowering review cost
Forbidding nested if is fine in principle, but you can expect new or junior teammates to still submit nested ifs in pull requests. The cost of pointing that out in review is not negligible.
We address this with linting.
Because we use JavaScript, we use ESLint to forbid nested if statements.
How to configure ESLint
Expressing “no nested if” in ESLint is straightforward with a built-in rule:
// eslint.config.js
export default {
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'IfStatement IfStatement',
message: 'Never use nested-if including else-if',
},
],
},
}
Techniques for flattening nested ifs
To flatten nested ifs, use the following techniques. With them, any depth of nesting can be flattened.
- Early return (guard clauses)
- Move inner if conditions outward
- De Morgan’s laws
- Extract into methods or functions
Refactoring example
Using a feature that “shows the weekday label for a given calendar date”, we show how to flatten nested ifs.
Requirements
- Decide the string to show as the weekday for the given date.
- On a public holiday, show Hol; if that holiday falls on a Sunday, show Sun instead.
- On ordinary weekdays, show the weekday as Mon–Sat.
- On a substitute holiday, show Sub.
Sample code
This is an implementation that uses nested ifs as much as possible.
The deepest branch is eight levels deep.
function resolveDayString (date) {
const baseDay = resolveBaseDayString(date) // Mon ~ Sun
if (isHoliday(date)) { // (1)
if (isSunday(date)) {
return 'Sun'
} else {
return 'Hol'
}
} else {
if (isSunday(date)) { // (2)
return 'Sun'
} else {
const oneDayAgoDate = createYesterday(date)
if (isHoliday(oneDayAgoDate)) { // (3)
if (isSunday(oneDayAgoDate)) { // (4)
return 'Sub'
} else {
const twoDaysAgoDate = createYesterday(oneDayAgoDate)
if (isHoliday(twoDaysAgoDate)) { // (5)
if (isSunday(twoDaysAgoDate)) { // (6)
return 'Sub'
} else {
const threeDaysAgoDate = createYesterday(twoDaysAgoDate)
if (isHoliday(threeDaysAgoDate)) { // (7)
if (isSunday(threeDaysAgoDate)) { // (8)
return 'Sub'
} else {
return baseDay
}
} else {
return baseDay
}
}
} else {
return baseDay
}
}
} else {
return baseDay
}
}
}
}
function resolveBaseDayString (date) {
// Returns one of Mon ~ Sun
}
function createYesterday (date) {
// Builds and returns the date one day before date
}
function isHoliday (date) {
// Uses a holiday list; returns true if date is a holiday, false otherwise
}
function isSunday (date) {
// Returns true if date is Sunday, false otherwise
}
Branches (5)–(8) are the nesting needed to handle a case where a three-day weekend from May 3–5, 2026 starts on a Sunday and May 6 is a substitute holiday.
Refactoring by extracting behavior
Extract a function
What the ifs at (3)–(8) are doing is “decide whether the date is a substitute holiday.”
Those branches can be folded into a single function isSubstituteHoliday(). We refactor the body of isSubstituteHoliday() later.
function isSubstituteHoliday (date) {
// Returns true if date is a substitute holiday, false otherwise
}
Refactoring resolveDayString() with isSubstituteHoliday() yields the following. It is already much clearer.
function resolveDayString (date) {
const baseDay = resolveBaseDayString(date) // Mon ~ Sun
if (isHoliday(date)) { // (1)
if (isSunday(date)) {
return 'Sun'
} else {
return 'Hol'
}
} else {
if (isSunday(date)) { // (2)
return 'Sun'
} else {
if (isSubstituteHoliday(date)) { // (3)
return 'Sub'
} else {
return baseDay
}
}
}
}
Refactoring by moving inner ifs outward
Looking at the logic, whenever the date is a Sunday the result is always Sun.
Here is the technique of moving an inner if outward.
- Write an empty if–else that branches on the inner condition.
if (isSunday(date)) { // (A) } else { // (B) } - Copy the entire block of the if you want to refactor into (A) and (B) above.
if (isSunday(date)) { // <--- ✅️ if (isHoliday(date)) { if (isSunday(date)) { // <--- 🔥 return 'Sun' } else { return 'Hol' } } else { if (isSunday(date)) { // <--- 🔥 return 'Sun' } else { if (isSubstituteHoliday(date)) { return 'Sub' } else { return baseDay } } } } else { if (isHoliday(date)) { if (isSunday(date)) { // <--- 🔥 return 'Sun' } else { return 'Hol' } } else { if (isSunday(date)) { // <--- 🔥 return 'Sun' } else { if (isSubstituteHoliday(date)) { return 'Sub' } else { return baseDay } } } } - At each 🔥 mark,
isSunday(date)is fixed by the outer ✅️ if, so keep only the then or else branch that applies and delete the redundant if.if (isSunday(date)) { // <--- ✅️ if (isHoliday(date)) { // if (isSunday(date)) { // <--- true return 'Sun' // } else { // return 'Hol' // } } else { // if (isSunday(date)) { // <--- true return 'Sun' // } else { // if (isSubstituteHoliday(date)) { // return 'Sub' // } else { // return baseDay // } // } } } else { if (isHoliday(date)) { // if (isSunday(date)) { // <--- false // return 'Sun' // } else { return 'Hol' // } } else { // if (isSunday(date)) { // <--- false // return 'Sun' // } else { if (isSubstituteHoliday(date)) { return 'Sub' } else { return baseDay } // } } } - Clean up.
if (isSunday(date)) { if (isHoliday(date)) { return 'Sun' // <--- 👀 } else { return 'Sun' // <--- 👀 } } else { if (isHoliday(date)) { return 'Hol' } else { if (isSubstituteHoliday(date)) { return 'Sub' } else { return baseDay } } } - The two 👀 branches return the same value, so that if can be removed.
if (isSunday(date)) { return 'Sun' } else { if (isHoliday(date)) { return 'Hol' } else { if (isSubstituteHoliday(date)) { return 'Sub' } else { return baseDay } } }
Refactoring with early return
Use early return to strip away unnecessary nesting.
function resolveDayString (date) {
const baseDay = resolveBaseDayString(date) // Mon ~ Sun
if (isSunday(date)) {
return 'Sun'
}
if (isHoliday(date)) {
return 'Hol'
} else {
if (isSubstituteHoliday(date)) {
return 'Sub'
} else {
return baseDay
}
}
}
function resolveDayString (date) {
const baseDay = resolveBaseDayString(date) // Mon ~ Sun
if (isSunday(date)) {
return 'Sun'
}
if (isHoliday(date)) {
return 'Hol'
}
if (isSubstituteHoliday(date)) {
return 'Sub'
} else {
return baseDay
}
}
function resolveDayString (date) {
const baseDay = resolveBaseDayString(date) // Mon ~ Sun
if (isSunday(date)) {
return 'Sun'
}
if (isHoliday(date)) {
return 'Hol'
}
if (isSubstituteHoliday(date)) {
return 'Sub'
}
return baseDay
}
At this point resolveDayString() is flat. Assigning baseDay at the top is no longer needed, so we simplify:
function resolveDayString (date) {
if (isSunday(date)) {
return 'Sun'
}
if (isHoliday(date)) {
return 'Hol'
}
if (isSubstituteHoliday(date)) {
return 'Sub'
}
return resolveBaseDayString(date) // Mon ~ Sun
}
Refactoring isSubstituteHoliday()
Below is the intended implementation of isSubstituteHoliday().
function isSubstituteHoliday (date) {
const oneDayAgoDate = createYesterday(date)
if (isHoliday(oneDayAgoDate)) {
if (isSunday(oneDayAgoDate)) {
return true
} else {
const twoDaysAgoDate = createYesterday(oneDayAgoDate)
if (isHoliday(twoDaysAgoDate)) {
if (isSunday(twoDaysAgoDate)) {
return true
} else {
const threeDaysAgoDate = createYesterday(twoDaysAgoDate)
if (isHoliday(threeDaysAgoDate)) {
if (isSunday(threeDaysAgoDate)) {
return true
} else {
return false
}
} else {
return false
}
}
} else {
return false
}
}
} else {
return false
}
}
This code has a repetitive shape, so we rewrite it with recursion.
function isSubstituteHoliday (date) {
const oneDayAgoDate = createYesterday(date)
if (isHoliday(oneDayAgoDate)) {
if (isSunday(oneDayAgoDate)) {
return true
} else {
return isSubstituteHoliday(oneDayAgoDate)
}
} else {
return false
}
}
Rewrite again with early return. It is flat at this point.
function isSubstituteHoliday (date) {
const oneDayAgoDate = createYesterday(date)
if (!isHoliday(oneDayAgoDate)) {
return false
}
if (isSunday(oneDayAgoDate)) {
return true
}
return isSubstituteHoliday(oneDayAgoDate)
}
The scenario we had in mind was the May 3–5, 2026 three-day weekend whose first day is a Sunday.
Even if Japanese law changes in the future and that becomes a six-day break from May 3 through May 8, the recursive isSubstituteHoliday() can still handle it.
De Morgan’s laws in practice
The refactoring example above did not need De Morgan’s laws, so we illustrate them with a separate example.
De Morgan’s laws
In words, De Morgan’s laws are simple:
The negation of a whole logical expression equals the expression where each operand is negated and AND and OR are swapped.
Below: turning the negation of a whole expression that contains AND into OR.
| Step | Expression |
|---|---|
| Negation of a whole expression containing AND | !(!alpha && beta) |
| Apply De Morgan’s laws | !(!alpha) || !beta |
| Simplify | alpha || !beta |
The same idea applies when you express the negation of a whole expression that contains OR using AND.
| Step | Expression |
|---|---|
| Negation of a whole expression containing OR | !(!gamma || delta) |
| Apply De Morgan’s laws | !(!gamma) && !delta |
| Simplify | gamma && !delta |
Code example
Unlike the refactoring example above, we use a small abstracted example.
function sampleFunction (alpha, beta, gamma) {
if (!alpha && beta) {
if (gamma) {
return 100
} else {
return 200
}
} else {
return 300
}
}
Refactoring with early return
Early return means handling the branch side without further splits first. Here the else side has no inner branching, so it is a good candidate.
If you merely negate the first if, the condition becomes hard to read (see 👀). Reviewers pay a high cognitive cost; the result would fairly be called low quality.
function sampleFunction (alpha, beta, gamma) {
if (!(!alpha && beta)) { // <--- 👀
return 300
}
if (gamma) {
return 100
} else {
return 200
}
}
This is where De Morgan’s laws help.
Refactoring with De Morgan’s laws
The ✅️ condition is now simple.
function sampleFunction (alpha, beta, gamma) {
if (alpha || !beta) { // <--- ✅️
return 300
}
if (gamma) {
return 100
} else {
return 200
}
}
Nested if is already gone, but we also remove else with another early return.
function sampleFunction (alpha, beta, gamma) {
if (alpha || !beta) { // <--- ✅️
return 300
}
if (gamma) {
return 100
}
return 200
}
Conclusion
From 23 years of writing code, the author’s conclusion is: no matter how deeply if statements are nested, they can be flattened.
If you find a nested structure you believe cannot be flattened, please get in touch with the nearest StewEucen.