Skip to content
VLSI Mentor

Python Part I · Module 9 · Conditional Statements

Indentation Is the Block

Chapter 9.1 used indentation without examining it: the indented lines belonged to the decision and the unindented ones ran regardless. That boundary deserves its own chapter, because in Python indentation is not a convention that keeps code tidy — it is the syntax that defines structure. Most languages an RTL engineer has met mark a block with begin and end, or with braces, and then indent to match. Python has only the indentation. There is no second marker to check it against, which means the structure cannot disagree with the layout, and also that a four-space change is a change to the program. This chapter shows exactly how to read that boundary, and what Python says when you get it wrong.

Foundation14 min readPythonPart IFundamentalsConditionalsIndentation

Part I · Module 9 · Chapter 9.2 · Indentation Is the Block

1. The Boundary Question

Every decision raises the same question: where does the branch stop?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
errors = 3

if errors > 0:
    print("errors found:", errors)
    print("opening the log")

print("done")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
errors found: 3
opening the log
done

Three statements, two of which belong to the decision. The first two lines are indented by four spaces, so they are inside the branch. The last is not, so it is after it.

Run the same program with no errors:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
errors = 0

if errors > 0:
    print("errors found:", errors)
    print("opening the log")

print("done")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
done

Both indented lines disappeared together. They are not two things that happened to be conditional — they are one block, and the block is what the if selects.

2. Inside and After

The two categories are worth naming, because you will label every line of every conditional you ever read as one or the other:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
errors = 0

if errors == 0:
    print("PASS")      # inside the decision
print("done")          # always runs
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS
done
  • A line indented under if ...: is inside the block. It runs only when the branch is selected.
  • A line at the same indentation as the if itself is after the statement. It runs regardless.

There is no third category and no ambiguity. The column a line starts in is the complete answer.

3. The Colon Announces a Block

The : at the end of if errors > 0: is not decoration. It says a block follows, and Python then requires one on the next line, indented further than the if.

That pairing — a line ending in :, then an indented block — is the shape of every compound statement in the language. You have already seen it twice in this module with if and else, and you will see the identical shape again for loops in Module 10, for functions in Module 11, and for match in Chapter 9.8. Learning to read it once is enough.

4. One Line, Four Spaces, A Different Program

Here is why this chapter exists. Take the working program from Section 1 and move a single line four spaces to the left. Change nothing else.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
errors = 0

if errors > 0:
    print("errors found:", errors)
print("opening the log")
print("done")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
opening the log
done

The program is valid. It raises nothing. It runs cleanly. And it is wrong: there were no errors, and it opened the log anyway.

print("opening the log") is no longer part of the decision. It is now an unconditional line that happens to sit directly under one. Nothing in the syntax is amiss, so nothing complains — the only evidence is that the output is not what the program was supposed to say.

5. The Block Is Missing

The remaining failures are the ones Python does catch. Each has a distinct message that names the actual problem, and reading them precisely saves the guessing.

Promise a block and do not provide one:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
errors = 1

if errors > 0:
print("investigate")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  File "bad.py", line 4
    print("investigate")
    ^^^^^
IndentationError: expected an indented block after 'if' statement on line 3

The message is unusually specific. It names what it expected (an indented block), what produced the expectation (an if statement) and where that was (line 3). The caret points at the line that should have been indented and was not. The fix is in the message: indent line 4.

6. The Block Cannot Be Empty

A related case: you write the if, then leave the body for later.

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
errors = 1

if errors > 0:

print("after")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  File "bad.py", line 5
    print("after")
    ^^^^^
IndentationError: expected an indented block after 'if' statement on line 3

The same complaint. A blank line is not a block — Python skips it looking for indented code and finds print("after") at the outer level. An if must contain at least one statement.

7. Indented With Nothing to Belong To

The opposite mistake — an indented line where no block was opened:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print("a")
    print("b")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  File "bad.py", line 2
    print("b")
IndentationError: unexpected indent

"Unexpected" is exact: the indentation itself is not malformed, but nothing on the line before ended in a colon, so there is no block for this line to be part of. Python saw a block begin with no statement to introduce it.

8. The Alignment Does Not Match

The third message appears when a line sits at an indentation level that does not correspond to any block currently open:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
errors = 1

if errors > 0:
    print("a")
  print("b")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  File "bad.py", line 5
    print("b")
              ^
IndentationError: unindent does not match any outer indentation level

Line 5 is indented two spaces. There are exactly two levels available — column 0 (the outer level) and column 4 (inside the if) — and two is neither. Python cannot tell which block you meant, so it refuses to guess.

The same message appears for a misaligned else, which is worth seeing because the cause looks different:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
errors = 1

if errors > 0:
    print("a")
  else:
    print("b")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  File "bad.py", line 5
    else:
         ^
IndentationError: unindent does not match any outer indentation level

else and elif must start in the same column as their if. They are parts of one statement, and the alignment is what joins them. An else indented to column 2 belongs to no if at all.

9. Tabs and Spaces

One more failure has its own exception type, because its cause is invisible on screen. The last line here is indented with a tab; the line above it uses four spaces:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
errors = 1

if errors > 0:
    print("spaces")
	print("tab")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  File "bad.py", line 5
    	print("tab")
    ^
TabError: inconsistent use of tabs and spaces in indentation

Both lines may look identically indented in your editor. Python does not compare their appearance, and a tab is not equivalent to any particular number of spaces, so it cannot decide whether the two lines are at the same level.

10. Reading the Five Messages

Collected, so the message tells you the fix without further diagnosis:

MessageWhat it meansFix
expected an indented block after 'if' statement on line Nline N opened a block; nothing was indented under itindent the body, or add one
unexpected indenta line is indented but nothing opened a blockremove the indentation
unindent does not match any outer indentation levelthe line sits at a level no open block usesalign it with one of the levels already in use
TabError: inconsistent use of tabs and spacesone line uses a tab, another spacesconvert the file to spaces
(no message at all)the program is valid and means something you did not intendcompare the column of every line against the if

The last row is the one that costs time, and it is the only one with no error to read.

11. Nesting Adds a Level, Not a Rule

Blocks can contain blocks. The rule does not change; it just applies again.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = "FAIL"
rerun = True

if status == "FAIL":
    print("failed")
    if rerun:
        print("rerunning")
    print("still inside the outer branch")
print("outside both")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
failed
rerunning
still inside the outer branch
outside both

Four lines at three levels. print("rerunning") is at column 8, inside the inner if. print("still inside the outer branch") is back at column 4 — out of the inner block, still in the outer one. print("outside both") is at column 0 and runs regardless.

Each line's column is the complete statement of what it belongs to. Chapter 9.4 takes up when this is worth doing and when it is not.

12. Predict, Run, Observe

Four programs, each followed by what it actually does. Cover the result and commit to an answer before reading it — two of the four raise, and the point is to tell which from the columns alone.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
errors = 0
if errors > 0:
    print("A: one")
    print("A: two")
print("A: three")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: three

Both indented lines are skipped together; the unindented one runs regardless.

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
# B
errors = 0
if errors > 0:
    print("B: one")
print("B: two")
    print("B: three")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndentationError: unexpected indent

print("B: three") is indented, but the line before it is print("B: two"), which does not end in a colon. No block was opened for it to join.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# C
ready = True
if ready:
    print("C: one")
print("C: two")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
C: one
C: two

Nothing surprising, which is the point — this is the shape every other program in this chapter deviates from.

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
# D
errors = 2
if errors > 0:
    print("D: one")
  print("D: two")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndentationError: unindent does not match any outer indentation level

Column 2 matches neither the outer level at column 0 nor the if body at column 4, so there is no open block it could belong to.

Notice that B and D both look roughly right at a glance and fail for completely different reasons. The message names which.

13. Exercises

Exercise 1 — label every line. For the program below, write inside or after beside each print, then run it with errors = 0 and with errors = 5 to check yourself.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if errors > 0:
    print("a")
    print("b")
print("c")

Exercise 2 — produce each error deliberately. Write four short programs that raise, in turn, expected an indented block, unexpected indent, unindent does not match any outer indentation level, and TabError. Record the exact message each one produces.

Exercise 3 — the silent bug. Take the correct program from Exercise 1 and move print("b") to column 0. Run it with errors = 0. Explain in two sentences why Python reports nothing and what the output proves.

Exercise 4 — find the misplaced line. This is meant to print the seed only when the test failed. It does not. Fix it by changing indentation alone.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = "PASS"
seed = 4210
if status == "FAIL":
    print("investigate")
print("seed was", seed)

Exercise 5 — align the else. Why does an else indented four spaces further than its if fail, and what is the rule in one sentence? Verify your answer by writing it.

Exercise 6 — read the nesting. For the program in Section 11, write down the column number of every line and say for each which blocks it is inside. Then predict the output for status = "PASS" and verify.

Exercise 7 — the colon. Remove the colon from if errors > 0: and run it. Record the message. Explain in one sentence why this is a different error from the ones in this chapter.

Exercise 8 — reconstruct from output. A program contains exactly one if, one else, and three print calls. With a true condition it prints one then three; with a false condition it prints two then three. Write it, and say what the column of each print must be.

14. Summary

  • Indentation is the syntax, not the formatting. The column a line starts in decides which block it belongs to.
  • A line indented under if ...: runs only when that branch is selected; a line at the same column as the if runs regardless.
  • The indented lines form one block. They are selected together and skipped together.
  • A line ending in : announces a block, and Python requires one on the next line.
  • Moving one line four spaces changes the program and may raise nothing at all — the silent version is the expensive one.
  • expected an indented block after 'if' statement on line N — the block is missing or empty.
  • unexpected indent — a line is indented with no block open.
  • unindent does not match any outer indentation level — the line is at a level no open block uses; this is also what a misaligned else produces.
  • TabError — tabs and spaces are mixed. Use four spaces everywhere.
  • else and elif align with their if, because they are parts of one statement.
  • Nesting applies the same rule again. Each level is four more spaces, and a line's column says exactly what it is inside.

The next chapter turns to what goes between if and the colon: the condition itself.

Where this fits

Part of the Python curriculum.