Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. Indentation Error in Python: the three messages, decoded
Tools

Indentation Error in Python: the three messages, decoded

Python has three indentation messages and a fourth error that is not an IndentationError at all. Each one points at a different mistake.

By Max Arthur
Co-Founder & Content Marketer·August 22, 2026·7 Min read
Four Python snippets side by side, each with its indentation mistake highlighted and its exact error message beneath

In Python, indentation is syntax. Where other languages use braces to say which lines belong to a function or a loop, Python uses the leading whitespace, so a line indented wrongly is not a style problem, it is a parse error.

Python does not report one generic error for this. It reports three different IndentationError messages plus a separate exception called TabError, and each names a distinct mistake. Reading which one you got is most of the fix.

Every message below came from running the code in Python 3.13 through Pyodide 0.28.3.

The three messages at a glance

Message What it means
unexpected indent A line is indented and nothing opened a block
expected an indented block A block was opened and nothing was indented after it
unindent does not match any outer indentation level A line went back out to a level that never existed

There is a fourth message that belongs to this family and is not an IndentationError at all: TabError: inconsistent use of tabs and spaces in indentation, which means tabs and spaces were mixed inside one block. It gets its own section below, because the distinction matters more than it first appears.

unexpected indent

x = 1
    y = 2
print(y)
IndentationError: unexpected indent

The second line is pushed in, but the line above it does not open a block. x = 1 is a complete statement, so there is nothing for an indented line to belong to.

Python only expects indentation after a line ending in a colon: def, class, if, elif, else, for, while, try, except, finally and with. If the line above has no colon, the indented line below it is an error.

The fix is to remove the leading spaces. When this appears in code you did not indent yourself, it is usually a paste that brought its original indentation with it.

expected an indented block

The mirror image: you opened a block and did not indent anything into it.

def greet():
print('hi')
IndentationError: expected an indented block after function definition on line 1

Modern Python is specific here, and the specificity is worth using. The message names both the construct (function definition) and where it started (line 1). Older versions said only expected an indented block, which left you hunting. If you have ever seen the shorter version, you were on an older interpreter.

You will meet the same message with different constructs, such as after 'if' statement on line 3 or after 'for' statement on line 7. The fix is always to indent the body:

def greet():
    print('hi')

There is one case where you genuinely have nothing to put in the block, such as a function you have not written yet. An empty body is still a syntax error, so use pass:

def greet():
    pass

unindent does not match any outer indentation level

The hardest of the three to see, because the line looks indented correctly at a glance.

def f():
    if True:
        a = 1
      b = 2
IndentationError: unindent does not match any outer indentation level

b = 2 sits at six spaces. The levels that exist in this code are zero, four and eight. Six is not one of them, so when Python steps back out from the if block it cannot tell which level the line belongs to.

Python does not round to the nearest level, and that is deliberate. Guessing would mean guessing your intent, and the two candidates here mean different things: at four spaces b = 2 runs every time f() is called, at eight it runs only when the if is true.

The fix is to pick a level that already exists. This is the error that four-space consistency prevents outright, because every level is then a multiple of four and there is nothing in between to land on.

TabError is a different exception

This is the one that surprises people, and it is why "IndentationError" searches sometimes turn up nothing useful:

def f():
    a = 1
	b = 2
    return a
TabError: inconsistent use of tabs and spaces in indentation

Not an IndentationError. TabError is its own exception, and the distinction matters when you are catching or searching for it. In that snippet, a and return are indented with four spaces and b with a single tab character.

On screen they can line up perfectly. In the file they are different bytes, and Python refuses to guess how wide a tab is meant to be, because different editors answer that question differently. A file that looks aligned in your editor can be misaligned in your colleague's.

This is the strongest argument for spaces over tabs in Python, and the reason PEP 8 says to use four spaces per level and never mix the two in one file.

Four things that are allowed, and one that is not

Some of the rules people assume exist do not. All of these were run to check:

Any consistent width works. Two spaces per level runs fine. So does eight. Python requires consistency within a block, not a specific number. Four spaces is a convention from PEP 8, not a language rule, and it is worth following because everyone else does.

Blank lines inside a block are fine. They carry no indentation information, so you can space out a long function freely.

A wrongly indented comment is not an error.

x = 1
    # just a comment
print(x)

That prints 1. Comments are stripped before indentation is analysed, so a stray-indented comment cannot break your code. This is worth knowing because it rules out a suspect: if a comment is the only oddly indented line, it is not your problem.

Which error you get from mixed tabs depends on the order. Spaces followed by a tab produced TabError. A tab followed by spaces produced IndentationError: unindent does not match any outer indentation level instead. Two names for one underlying mistake, so treat both as "check the whitespace characters".

An unclosed bracket is not an indentation problem, although it can look like one:

items = [1, 2
for i in items:
    print(i)
SyntaxError: '[' was never closed

Python 3.13 names the exact bracket and where it opened. Older versions reported a confusing error on the following line, which is where the belief that "indentation errors point at the wrong line" comes from. On a current version, trust the message.

Make the whitespace visible

You cannot fix what you cannot see, and the fastest way to solve any of these is to stop guessing about characters:

  • VS Code: turn on editor.renderWhitespace, which draws dots for spaces and arrows for tabs. The command palette also has "Convert Indentation to Spaces", which fixes a mixed file in one action.
  • Any editor: set the tab key to insert spaces. In VS Code that is editor.insertSpaces with editor.tabSize at 4.
  • Whole codebase: run a formatter. Black and Ruff both normalise indentation, and neither will leave a mixed file behind.

If you are working in a file someone else started, checking the indentation style before you type is worth the two seconds. Matching what is already there is easier than converting it.

Why Python does this at all

It is a fair question when you are three errors deep. The trade is that indentation is the only thing describing the structure, so what you see is what runs.

In a brace language, code can be indented to look like one thing and execute as another, because the braces decide and the whitespace is decoration. Python removes that gap by making the whitespace load-bearing. The cost is these errors while you are learning; the benefit is that a Python file's shape is never a lie about its behaviour.

The habits that make it a non-issue are small: four spaces per level, spaces rather than tabs, an editor set to insert spaces, and a formatter for anything you did not write. After that, the errors on this page mostly stop appearing.

Want to get the shape of Python into your fingers? Start with the Python track, or read how to loop with an index for the construct that gives beginners the most indentation practice.

Keep reading

More from the blog

One commit from a feature branch being copied onto main as a new commit with a different hash
September 5, 2026·8 min readTools

git cherry-pick: move one commit anywhere

Cherry-pick copies a commit onto your current branch. It creates a new commit with a new hash, so the original stays where it was.

Read more
A successful 200 response arriving at the browser and being stopped before it reaches the page's JavaScript
September 3, 2026·7 min readTools

CORS error: what the browser is actually blocking

A CORS error is the browser refusing a response the server already returned with a 200. Here is that proved with two real origins, and the fixes.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs MimoDevpuff vs Sololearn
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs freeCodeCamp
Read All Comparisons