Concepts / Using Blocks to Group Statements

Using Blocks to Group Statements

The dangling else problem occurs when nested if statements are interpreted differently by the compiler than the programmer intended.

  • Programming

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?

  • It prints the message for x not being positive

  • Nothing is printed

  • It prints the message for x and y being positive

Reveal answer
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

Definition

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.

java
Notes on the code
  • 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.

java
Notes on the code
  • 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.

java
Notes on the code
  • 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
Worked example

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?

  1. 1
    Before any statement

    x is 3, y is 7, and temp has been declared but not yet assigned.

  2. 2
    temp = x

    temp receives the current value of x, which is 3. The original value of x is now saved.

  3. 3
    x = 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.

  4. 4
    y = temp

    y receives the saved value from temp, which is 3. y is now 3.

Result

After the block, x is 7 and y is 3. The two values have been swapped.

x3x7y7y3tempnot yet assignedtemp3x = yy = temptemp = x
At the start and end of the swap block, what value does each variable hold, and which variables changed?

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

Definition

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.

subroutine bodyswap blocktempcode after swap blockcontainscontainsdeclares
temp is declared inside the swap block, so the swap block contains it. Code after the swap block sits outside that boundary and cannot reach temp.

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.

java
Output
When x is 0 or less, nothing is printed, regardless of y.
Notes on the code
  • 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
x > 0?y > 0?print x and ypositiveprint x not positivenothing printedyesnoyesno
Without braces, the else attaches to the y check. When x is not greater than zero, the path leads to nothing printed, because the inner check is never reached.

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.

java
Output
When x is 0 or less, the message x is not positive is printed.
Notes on the code
  • 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
FeatureWithout bracesWith braces
What the outer if controlsOnly the single inner if statementEverything inside the braces
What the else pairs withThe inner condition y greater than zeroThe outer condition x greater than zero
Result when x is 0 or lessNothing is printedThe x is not positive message is printed
Matches the usual intentNo, the else is read against the inner ifYes, 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 like

    Writing an unbraced inner if under an outer if, then placing an else at the outer indentation level.

    Why it fails

    The compiler does not use indentation as a grouping rule. The else is read against the nearest if that can take it.

    Do this instead

    Enclose 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 fails

    Nothing 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 instead

    Check 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 like

    Declaring temp inside the swap block and then reading temp in code after the closing brace.

    Why it fails

    A variable declared inside a block is local to that block and inaccessible from outside it.

    Do this instead

    Declare the variable in the outer scope if code outside the block needs it.

  • Common mistake

    Reordering the statements of a swap

    What it looks like

    Running x = y before temp = x.

    Why it fails

    The original value of x is overwritten before it is saved, so both variables end up holding the same value.

    Do this instead

    Save 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

MEDIUM

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
MEDIUM

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.