Ch. 9 · Python

IndentationError: expected an indented block in Python

Fix Python IndentationError: expected an indented block, unindent does not match any outer indentation level, and TabError from mixed tabs.

~7 min readbeginnerupdated Oct 4, 2026

Python refuses to start the program and prints one of these (Python 3.14.7, paths shortened; 3.12 and 3.13 print the same messages):

  File "check_age.py", line 3
    print("adult")
    ^^^^^
IndentationError: expected an indented block after 'if' statement on line 2

  File "total.py", line 5
    return result
                 ^
IndentationError: unindent does not match any outer indentation level

  File "area.py", line 3
    	return w * h
    ^
TabError: inconsistent use of tabs and spaces in indentation
Text

All three are compile-time errors: Python could not work out the block structure of the file, so not a single line ran. IndentationError is a subclass of SyntaxError, and TabError is a subclass of IndentationError. A fourth relative, IndentationError: unexpected indent, means a line is indented where no block was opened.

Quick fix checklist

  • Go to the line number in the message, then look at the line above it: the bug is usually where the two meet.
  • After any line ending in : (if, for, while, def, class, try, except, with, else), the next statement must be indented further. If the block is intentionally empty, write pass.
  • A comment does not count as a statement. Commenting out the only line of a block leaves the block empty.
  • For unindent does not match, make the line’s indentation exactly equal to the block it belongs to (4, 8, 12 spaces), not something in between.
  • For TabError, convert the file’s indentation to spaces in your editor (“Convert Indentation to Spaces” in VS Code) and make the editor insert 4 spaces per level.
  • Turn on visible whitespace in your editor so tabs and spaces look different.

Before you start

You need to know that Python uses indentation instead of braces to group statements. The “after ‘if’ statement on line N” detail was added in Python 3.10; Python 3.9 and older only print IndentationError: expected an indented block. The shell commands are for macOS and Linux; on Linux use cat -A where this article uses macOS’s cat -et.

Why it happens

Before parsing, Python’s tokenizer converts leading whitespace into INDENT and DEDENT tokens using a stack of indentation widths, starting with [0]:

  • A line indented more than the top of the stack pushes the new width and emits INDENT.
  • A line indented less pops widths until it finds one that matches exactly, emitting a DEDENT per pop. If no width on the stack matches, you get unindent does not match any outer indentation level.
  • A line indented more when no block was opened gives unexpected indent.

The parser then requires a compound statement’s header (anything ending in :) to be followed by NEWLINE INDENT and at least one statement. If the next real line is not indented, it reports expected an indented block after .... Blank lines and comment-only lines produce no tokens at all, which is why a block containing only a comment is still empty.

Tabs add ambiguity. The language reference says a tab advances to the next multiple of 8 columns. Python also measures each line as if a tab were 1 column wide, and if the two measurements disagree about whether a line is deeper, equal or shallower than the previous level, it raises TabError instead of guessing. A line of four spaces followed by a line with one tab is “deeper” at tab width 8 but “shallower” at tab width 1: inconsistent, so Python refuses.

Step-by-step walkthrough

Step 1: Reproduce without running the program

Indentation errors are reported at compile time, so you can check a file without executing it (useful when running it has side effects):

$ python3 -m py_compile notify.py
Sorry: IndentationError: expected an indented block after 'if' statement on line 7 (notify.py, line 9)
$ echo $?
1
Terminal

Step 2: Read the two line numbers

For expected an indented block, the message names two lines: the header that opened the block (line 7) and the line where Python expected the indented body (line 9). Everything between them is blank or comments. For unindent does not match, the caret sits at the end of the offending line; ignore its column and compare that line’s leading whitespace with the lines above it.

Step 3: Make the whitespace visible

Spaces and tabs look identical in most editors. Show them:

$ cat -et area.py
def area(w, h):$
    if w > 0:$
^Ireturn w * h$
    return 0$
$ grep -n $'\t' area.py
3:	return w * h
Terminal

^I is a tab and $ marks the end of the line. Line 3 is indented with a tab while the rest of the file uses spaces. The standard library’s tabnanny module reports the same thing (python3 -m tabnanny area.py prints area.py 3 '\treturn w * h'), but note that it exits with status 0 even when it finds a problem, so do not rely on its exit code in CI.

Step 4: Convert tabs with the width the author saw

This is the step people get wrong. The tab on line 3 looked correct to whoever wrote it, which means their editor displayed tabs as some width. Converting with a different width changes the structure:

$ expand -t 4 area.py > area4.py && cat -et area4.py
def area(w, h):$
    if w > 0:$
    return w * h$
    return 0$
$ python3 area4.py
  File "area4.py", line 3
    return w * h
    ^^^^^^
IndentationError: expected an indented block after 'if' statement on line 2

$ expand -t 8 area.py > area8.py && cat -et area8.py
def area(w, h):$
    if w > 0:$
        return w * h$
    return 0$
Terminal

With 8-wide tabs the return w * h lands inside the if, which is what the author meant. Your editor’s “Convert Indentation to Spaces” command uses its configured tab size, so set that first, convert, then read the result rather than trusting it.

Step 5: Stop it from coming back

Make every editor on the project insert spaces. An .editorconfig file at the repository root is honoured by most editors, natively or through a plugin:

[*.py]
indent_style = space
indent_size = 4
ini

A formatter such as Black or Ruff’s formatter keeps indentation uniform afterwards, but it only works on code that already parses, so it prevents these errors rather than repairing them. Add python -m py_compile (or python -m compileall -q src) to a pre-commit hook or CI step for a cheap syntax gate.

Worked scenario

While debugging, a developer comments out the only line inside an if:

import logging

log = logging.getLogger(__name__)


def notify(user, message):
    if user.wants_email:
        # send_email(user.email, message)
    log.info("notified %s", user.id)
python
  File "notify.py", line 9
    log.info("notified %s", user.id)
    ^^^
IndentationError: expected an indented block after 'if' statement on line 7
Text

Diagnosis. Line 7 opens a block. Line 8 is a comment, which the tokenizer ignores, so the next real statement is line 9 at the same depth as the if. The if has no body.

Fix. Give the block a statement that does nothing:

def notify(user, message):
    if user.wants_email:
        # send_email(user.email, message)
        pass
    log.info("notified %s", user.id)
python

py_compile now exits with status 0. pass is the conventional placeholder; ... (the Ellipsis literal) also works and is common in stubs and abstract methods. If the whole branch is temporarily pointless, comment out the if line as well, so nobody reads it later as a deliberate no-op.

Common mistake

The tempting fix is to tap the spacebar until the error goes away. That makes the file parse, but indentation is logic in Python, so you can silently move a statement into a different block:

def first_even(numbers):
    for n in numbers:
        if n % 2 == 0:
            return n
    return None

def first_even_broken(numbers):
    for n in numbers:
        if n % 2 == 0:
            return n
        return None

print(first_even([1, 3, 4]), first_even_broken([1, 3, 4]))
# 4 None
python

Both versions are valid Python. In the second, return None slid into the loop, so the function gives up after checking only the first number. When you fix an indentation error, decide which block each line belongs to, then indent it to that level, and rerun the tests.

Another mistake is fixing a TabError by retyping only the reported line. The tab is usually one of many pasted lines; convert the whole file.

Verify the behavior

Three checks, from cheapest to most meaningful:

$ python3 -m py_compile area.py && echo "compiles"
compiles
$ grep -c $'\t' area.py
0
$ python3 -c "import area; print(area.area(3, 4), area.area(-1, 4))"
12 0
Terminal

The first proves the structure is valid, the second that no tabs remain, and the third that the code still does what it meant to: a positive width returns the product, a non-positive one returns 0. If the file has tests, run them, because the most dangerous outcome of an indentation fix is code that compiles and behaves differently.

Interview exercise

“Python has no braces. How does the interpreter decide where a block ends, and why is mixing tabs and spaces an error instead of a style warning?”

Answer and reasoning

The tokenizer tracks a stack of indentation widths. Indenting further pushes a level and emits an INDENT token; dedenting pops levels and emits a DEDENT per level until the line’s width matches one on the stack exactly. The grammar then treats INDENT ... DEDENT like an opening and closing brace. That explains all the messages: a header with no INDENT after it is “expected an indented block”, a dedent to a width not on the stack is “unindent does not match”, and an INDENT with no header is “unexpected indent”.

Mixing tabs and spaces is an error because the block structure would depend on a display setting. A tab could mean 4 or 8 columns, and the same bytes would parse into different programs on different machines. Python 3 checks the file under two interpretations of a tab and raises TabError when they disagree, rather than silently choosing one. PEP 8 recommends 4 spaces per level for that reason.

Continue learning

Keep practising with the Python interview questions and the Python MCQs. Scope bugs that look similar at first glance are covered in UnboundLocalError: cannot access local variable and comprehension scope. Official references: the language reference on indentation, the IndentationError and TabError exceptions, the tabnanny module and PEP 8.

More in Python

esc