Using Blocks to Group Statements
The dangling else problem occurs when nested if statements are interpreted differently by the compiler than the programmer intended.
Two Versions of the Same Nested If
Nested if statements look simple, but they hide a trap. When one if statement sits inside another and an else follows, it is natural to assume the else belongs to the outer condition you had in mind. The compiler does not read indentation as a rule, though. The structure it builds from the code can differ from the structure you intended, and nothing in the program signals an error when this happens. This gap between intended meaning and actual meaning is called the dangling else problem.
Consider two versions of one nested structure. Both use the same two conditions and the same two print statements. The first version places the inner if directly under the outer if with no block. The second wraps the inner if in curly braces. The only difference between them is how the inner if is grouped.
What do you think happens?
In the version with no braces, what does the program print when x is less than or equal to zero?
Reveal answer
Nothing is printed.
Without a block, the else attaches to the inner if, which checks y greater than zero. When x is not greater than zero, the inner if is never reached, so the else attached to it is never reached either.
What a Block Is
A block is a sequence of statements enclosed between a pair of braces. A block can be empty, meaning it contains no statements at all.
The general pattern is an opening brace, any number of statements, and a closing brace. Each pair of braces marks one block. The braces, not the line breaks, mark where a group begins and ends. In a block with two print statements, both statements sit between the same pair of braces, so together they form a single unit that can be treated as one thing.
- The opening brace starts the block
- Both print statements are the block's statements
- The closing brace ends the block and makes the two statements one unit
A block with no statements is still legal. The source says an empty block can be useful at times without listing specific uses, so treat an empty pair of braces as a legal, meaningful form you will recognize when you see it. Its legality follows from the general rule that a block can occur wherever a statement can occur.
Where Blocks Can Appear in Java
Blocks usually appear inside other statements, where their purpose is to group several statements so they can be treated as one unit. A block can legally be used anywhere a single statement is allowed, which means a block is itself a kind of statement. Because of this, blocks can be nested inside one another.
There is one place where a block is required rather than merely allowed. The definition of a subroutine is a block, because it is a sequence of statements enclosed inside a pair of braces. The main subroutine of a program is the example the source uses. Whatever statements belong to the subroutine go inside its braces.
- The outer braces belong to the main subroutine, which is itself a block
- The inner braces form a block used as a statement inside the subroutine body
- Both uses are the same kind of construct
Tracing a Swap Through a Block
The swap block is a good place to practice tracing. It uses a helper variable called temp, and each line changes one variable, so the order of the lines matters. The following block swaps the values of x and y. The starting state, x equal to 3 and y equal to 7, is generated for this walkthrough and is not from the source.
- temp is declared inside the block, so it exists only while the block runs
- The first assignment saves the original value of x
- The second assignment copies y into x
- The third assignment copies the saved value into y
Tracing the swap with x = 3 and y = 7
Starting from x = 3 and y = 7, what does each variable hold after each statement in the swap block?
- 1Before any statement
x is 3, y is 7, and temp has been declared but not yet assigned.
- 2temp = x
temp receives the current value of x, which is 3. The original value of x is now saved.
- 3x = y
x receives the current value of y, which is 7. x is now 7, so the original value of x survives only in temp.
- 4y = temp
y receives the saved value from temp, which is 3. y is now 3.
After the block, x is 7 and y is 3. The two values have been swapped.
Now notice what happens when the order changes. Suppose the first two lines were reversed, so that x = y ran before temp = x. The original value of x would be lost before it was saved. The trace then looks like this: x becomes 7, temp is assigned the new x which is 7, and y is assigned temp which is 7. Both variables end up holding 7, and the swap has failed. This is the spot to look when a trace goes wrong: the step where the expected value first differs from the actual one.
Where a Block Variable Can Be Seen
A variable declared inside a block is local to that block. Its scope begins at its declaration, and it is inaccessible from outside the block.
The source states that declaring a variable inside a block is legal, and that it is good style to do so when the variable is used nowhere else but inside the block. The temp variable in the swap example is exactly this case. It exists only to help with the swap, so it is declared inside the swap block. Code outside that block cannot see temp, even though the enclosing subroutine body can see the swap block itself.
How the Dangling Else Happens
Return to the two versions from the opening. The first version has no block. Without a block, the outer if controls only the single statement immediately after it, which is the inner if. The else that follows is then read against that inner statement, so it pairs with the inner condition y greater than zero rather than the outer condition x greater than zero. In this reading, the outer if has no else at all.
When x is 0 or less, nothing is printed, regardless of y.- The outer condition tests x
- The inner condition tests y
- The else is read as belonging to the inner if, which is the nearest if that can take it
- The message for x not being positive is printed only when y is not greater than zero and x is positive
Forcing the Intended Structure with Braces
Adding a block changes the grouping. The braces enclose the inner if so that it becomes one unit inside the outer if. The else that follows the closing brace can no longer attach to the inner statement, so it pairs with the outer condition. The source gives this as the fix: enclosing the nested if in a block forces the reading the programmer usually intends.
When x is 0 or less, the message x is not positive is printed.- The braces make the inner if a single unit inside the outer if
- The else follows the closing brace, so it pairs with the outer condition x greater than zero
- When x is not greater than zero, control goes straight to the else output and the y check is never reached
| Feature | Without braces | With braces |
|---|---|---|
| What the outer if controls | Only the single inner if statement | Everything inside the braces |
| What the else pairs with | The inner condition y greater than zero | The outer condition x greater than zero |
| Result when x is 0 or less | Nothing is printed | The x is not positive message is printed |
| Matches the usual intent | No, the else is read against the inner if | Yes, the braces make the intent explicit |
The same nested structure read with and without a block
Mistakes Beginners Make with Nested Conditions
Common mistake Trusting indentation to decide which if an else belongs to
What it looks likeWriting an unbraced inner if under an outer if, then placing an else at the outer indentation level.
Why it failsThe compiler does not use indentation as a grouping rule. The else is read against the nearest if that can take it.
Do this insteadEnclose the inner if in braces when the else is meant to pair with the outer condition.
Common mistake Assuming the program will report an error
Why it failsNothing in the program signals a problem when the else attaches to the wrong if, so the behavior is simply different from what was planned.
Do this insteadCheck the grouping by hand when a branch never runs or runs at the wrong time.
Common mistake Expecting a variable declared in a block to be visible after the block ends
What it looks likeDeclaring temp inside the swap block and then reading temp in code after the closing brace.
Why it failsA variable declared inside a block is local to that block and inaccessible from outside it.
Do this insteadDeclare the variable in the outer scope if code outside the block needs it.
Common mistake Reordering the statements of a swap
What it looks likeRunning x = y before temp = x.
Why it failsThe original value of x is overwritten before it is saved, so both variables end up holding the same value.
Do this insteadSave the first value before overwriting it, then copy the second value, then copy the saved value.
Layout and Style in Java
Java is a free-format language. There are no syntax rules about how a program must be arranged on a page, so the same block can be written on one line or spread across many, and both forms are legal. Good style, however, lays out the program so its structure is as clear as possible. The general rule the source gives is to put one statement per line and to use indentation to show statements contained inside control structures.
Practice Tracing a Block
Starting with x = 3 and y = 7, trace the following block line by line and record the value of x, y, and temp after each statement. Then say whether the final result is a correct swap. Next, predict what would happen if the second and third lines were swapped in order.
Hints
- Write down a row for each statement and copy the values from the row above before changing one variable
- Check the step where x is overwritten, because that is where the original value can be lost
- After the third line, x should equal the original y and y should equal the original x
Write an unbraced nested if with one else where the else is meant to pair with the outer condition. Then add braces so that the else pairs with the outer condition. Explain in one sentence what changed.
Hints
- Use a condition on x for the outer if and a condition on y for the inner if
- The braces should enclose only the inner if, not the else
Key Takeaways from Blocks
At a glance
- A block is a sequence of statements enclosed between a pair of braces, and it can be empty. A block can be used anywhere a single statement can occur, and the body of a subroutine is a block that is required.
- The dangling else problem arises when nested if statements are read by the compiler differently than the programmer intended. Without a block, an else pairs with the nearest if that can take it, which is the inner if.
- Enclosing the inner if in braces forces the else to pair with the outer condition, which is usually the intended reading.
- A variable declared inside a block is local to that block, its scope begins at its declaration, and it is inaccessible from outside. Tracing a swap step by step reveals where a result first departs from expectation.