Fix: Python IndentationError – Unexpected Indent or Expected an Indented Block
Part of: Python Errors
Quick Answer
How to fix Python IndentationError including unexpected indent, expected an indented block, and unindent does not match, caused by mixed tabs and spaces or incorrect nesting.
The Error
You run a Python script and hit one of these tracebacks:
File "main.py", line 8
x = 10
^
IndentationError: unexpected indent File "main.py", line 5
return result
^
IndentationError: expected an indented block File "main.py", line 12
print(total)
^
IndentationError: unindent does not match any outer indentation level File "main.py", line 6
print("done")
^
TabError: inconsistent use of tabs and spaces in indentationAll four are variations of the same fundamental problem. Python relies on indentation to define code structure — where other languages use braces {} or keywords like end, Python uses whitespace. When that whitespace is wrong, Python cannot parse your code and refuses to run it.
Why This Happens
Python’s parser reads indentation to determine which statements belong to which block. Every line after a colon (:) must be indented, and all lines within the same block must share the same indentation level using the same whitespace character consistently.
The most common causes:
- Mixing tabs and spaces. This is the number one cause. Your file contains some lines indented with tab characters and others indented with spaces. They may look identical in your editor, but Python sees them as completely different characters. A line that appears to be at the correct indentation level may actually be wrong because it uses tabs where the rest of the file uses spaces.
- Copy-pasting code from the web. Code copied from Stack Overflow, blog posts, documentation sites, or chat applications often brings invisible whitespace problems. The pasted code may use different-width spaces, non-breaking spaces (
\xa0), or a mix of tabs and spaces that doesn’t match your file. - Empty function, class, or control flow body. You wrote a
def,class,if,else,elif,for,while,try,except, orwithstatement followed by a colon, but left the body empty. Python requires at least one indented statement after every colon. - Editor inserting the wrong character. Your editor is configured to insert tabs, but the rest of the file uses spaces (or vice versa). This happens frequently when multiple developers use different editors or settings on the same project.
- Incorrect nesting level. A line is indented too far or not far enough, placing it in the wrong logical block. This often happens with deeply nested
if/else/try/exceptstructures. - Accidental dedent after a multiline expression. After a long function call, list comprehension, or string that spans multiple lines, the next line may end up at the wrong indentation level.
Diagnostic Timeline
This is the order an experienced Python developer actually works through an IndentationError that “makes no sense,” including the obvious-but-wrong moves and the diagnostic checks that find the real problem.
Minute 0 — first reaction: “the indent looks fine, just fix it.”
You delete the leading whitespace, retype it, and rerun. Same error. Discard this hypothesis: if the visible indent looked right but Python rejects it, the bytes on disk are not what your editor is rendering. Stop touching the indent in the editor and start inspecting the raw file.
Minute 1 — second reaction: “mixed tabs and spaces.”
You run:
python -tt your_file.pyIf you get TabError: inconsistent use of tabs and spaces, this is correct — convert with expand or your editor’s “Convert Indentation to Spaces” command and you are done. If -tt runs without complaint but the original error persists, the file uses one consistent character. Move on.
Minute 3 — third reaction: “encoding or line endings.”
You check for a UTF-8 BOM at the top of the file (a \xef\xbb\xbf byte sequence). In Python 3 this is technically allowed, but some toolchains and shebangs misbehave when the BOM lands in front of #!/usr/bin/env python3. Inspect the first bytes:
head -c 3 your_file.py | xxdIf you see efbb bf, strip it:
sed -i '1s/^\xEF\xBB\xBF//' your_file.pyCRLF line endings on a Unix system can also confuse the parser when paired with an editor that reports column counts inconsistently. Run dos2unix and rerun.
Minute 5 — actual root cause.
The single most underdiagnosed cause is non-breaking spaces (\xa0) introduced by copy-paste from a PDF, a Slack message, a Confluence page, or an LLM chat UI. They render as a normal space in every editor but the Python parser rejects them as invalid indentation, and the error message points at a line that visually looks identical to its neighbors.
Run a hexdump of the offending line:
sed -n '15p' your_file.py | xxd | head -2Look for c2a0 bytes — that is a UTF-8 non-breaking space. Replace them globally:
python -c "
import sys
data = open('your_file.py', 'rb').read()
open('your_file.py', 'wb').write(data.replace(b'\xc2\xa0', b' '))
"A related class of invisible whitespace — zero-width space (\xe2\x80\x8b), em space (\xe2\x80\x83), or ideographic space (\xe3\x80\x80 from Japanese keyboards in IME mode) — produces the same symptom. The hexdump tells you which one. If the script is actually running but failing because Python itself is missing, see Fix: python command not found before chasing indentation further.
Fix 1: Convert All Indentation to Spaces
The Python community standard is 4 spaces per indent level, as defined in PEP 8. While tabs are technically allowed, you must never mix tabs and spaces in the same file. The simplest and most reliable fix is to convert everything to spaces.
VS Code
- Open the file with the indentation error.
- Look at the bottom-right status bar. It shows either
Spaces: 4orTab Size: 4. - Click that indicator and select Indent Using Spaces, then choose 4.
- Open the Command Palette (
Ctrl+Shift+Pon Windows/Linux,Cmd+Shift+Pon macOS). - Type Convert Indentation to Spaces and run the command.
This converts every tab character in the file to the equivalent number of spaces. To make this the permanent default for all Python files, add the following to your settings.json:
{
"[python]": {
"editor.insertSpaces": true,
"editor.tabSize": 4,
"editor.detectIndentation": false
}
}Setting editor.detectIndentation to false is important. Without it, VS Code automatically detects and matches a file’s existing indentation style. If the file already has broken indentation, VS Code will happily continue using the broken style. Disabling detection forces VS Code to always use your configured settings.
To visualize whitespace characters and spot the problem lines, enable View > Render Whitespace or set "editor.renderWhitespace": "all" in your settings. Spaces appear as faint dots and tabs appear as arrows, making mixed indentation immediately visible. If you are having broader issues with VS Code and your Python or linting setup, see Fix: VSCode ESLint not working for general editor troubleshooting approaches.
PyCharm
- Go to Settings (or Preferences on macOS) > Editor > Code Style > Python.
- Under the Tabs and Indents tab, uncheck Use tab character.
- Set Tab size, Indent, and Continuation indent all to
4. - To fix an existing file, go to Code > Reformat Code (
Ctrl+Alt+Lon Windows/Linux,Cmd+Alt+Lon macOS).
PyCharm’s reformatter fixes both indentation style and indentation levels in one pass. It also respects your code style settings, so if you have configured 4-space indentation, the reformatted file will be consistent.
Sublime Text
- Go to View > Indentation.
- Uncheck Indent Using Tabs.
- Set Tab Width to 4.
- Select all text (
Ctrl+A/Cmd+A), then go to View > Indentation > Convert Indentation to Spaces.
Vim / Neovim
Add these lines to your ~/.vimrc or init.vim:
set expandtab
set tabstop=4
set shiftwidth=4
set softtabstop=4To convert tabs to spaces in a file that is already open:
:set expandtab
:retabThe :retab command replaces all tab characters with the appropriate number of spaces based on your tabstop setting.
Common Mistake: Copying code from Stack Overflow or a blog post and pasting it directly into your editor. The pasted code often carries invisible non-breaking spaces (
\xa0) or mixed tab/space characters that look identical to regular whitespace but cause Python to reject the indentation.
Fix 2: Detect Mixed Tabs and Spaces with python -tt
Python 3 already raises a TabError when it finds mixed tabs and spaces within the same indented block. But some mixed-indentation problems slip through if tabs and spaces are used in separate blocks or in ways that happen to align visually.
Run your script with the -tt flag to make Python treat all tab/space inconsistencies as hard errors:
python -tt script.pyThis forces Python to flag every line where the indentation character type doesn’t match, even if the visual alignment looks correct. It is the fastest way to identify which lines are causing the problem.
You can also inspect the raw whitespace characters in your file from the command line:
Linux / macOS:
cat -A script.pyTabs show up as ^I. If you see some lines starting with ^I and others starting with regular spaces, that is your problem.
Windows (PowerShell):
Get-Content script.py | ForEach-Object { $_ -replace "`t", "→" }Python one-liner:
python -c "
with open('script.py') as f:
for i, line in enumerate(f, 1):
if '\t' in line:
print(f'Line {i}: contains tab')
"Fix 3: Add pass to Empty Blocks
Every compound statement in Python — def, class, if, elif, else, for, while, try, except, finally, with — requires at least one indented statement in its body. If you leave the body empty, Python raises IndentationError: expected an indented block.
Broken:
def placeholder():
# TODO: implement later
class MyModel:
# fields go here
if user.is_admin:
# admin logic
try:
risky_operation()
except ValueError:
# handle later
for item in items:
# process itemsEvery one of these raises IndentationError: expected an indented block because the comment after the colon is not considered a statement. Comments are ignored by the parser.
Fixed with pass:
def placeholder():
pass
class MyModel:
pass
if user.is_admin:
pass
try:
risky_operation()
except ValueError:
pass
for item in items:
passThe pass statement is a no-op. It does nothing, but it satisfies Python’s requirement that a block must contain at least one statement. You can also use the Ellipsis literal ... as a placeholder, which is common in type stubs and abstract method definitions:
def not_implemented_yet():
...
class AbstractHandler:
def handle(self, request):
...Both pass and ... are valid. The convention is to use pass when the block is intentionally empty and ... when the implementation is deferred or the method is abstract.
Fix 4: Fix Copy-Paste Indentation from the Web
Code copied from websites, PDFs, Slack messages, Discord, or email clients frequently introduces invisible whitespace problems. The pasted text may contain non-breaking spaces (\xa0 / ), mixed tab/space characters, or different indentation widths.
After pasting code into your file:
- Select all the pasted lines.
- Remove all leading whitespace by pressing
Shift+Tabrepeatedly until everything is at column 0. - Re-indent from scratch using your editor’s indent command (
Tab) to add the correct number of spaces.
Non-breaking spaces
Some websites, Jupyter Notebook exports, and messaging applications replace regular spaces with non-breaking spaces (Unicode \xa0). Python does not recognize these as valid indentation. They look identical to regular spaces in every editor, making them extremely hard to spot.
To find non-breaking spaces:
grep -P "\xc2\xa0" script.pyTo replace them with regular spaces:
sed -i 's/\xc2\xa0/ /g' script.pyOr in Python:
with open("script.py", "r") as f:
content = f.read()
content = content.replace("\xa0", " ")
with open("script.py", "w") as f:
f.write(content)If you regularly paste code from external sources, make a habit of always re-indenting the pasted block. Many editors have a “paste and indent” or “format on paste” feature that handles this automatically. In VS Code, set "editor.formatOnPaste": true in your settings.
Fix 5: Use autopep8, black, or ruff to Auto-Fix Indentation
Auto-formatters can fix most indentation issues in a single command. They parse your Python file, rebuild the indentation structure, and write the result back — all using consistent spaces.
black
pip install black
black script.pyblack is an opinionated formatter. It enforces a consistent style across your entire file, including indentation, line length, quote style, and trailing commas. It uses 4 spaces per indent level with no configuration needed.
autopep8
pip install autopep8
autopep8 --in-place script.pyautopep8 is less aggressive than black. It fixes PEP 8 violations — including indentation — while preserving more of your original formatting choices. If you want minimal changes, autopep8 is the better choice.
ruff
pip install ruff
ruff format script.pyruff is a fast Python linter and formatter written in Rust. Its formatting output is compatible with black’s style, but it runs significantly faster on large codebases.
Important: Formatters fix whitespace issues, but they cannot fix logical indentation errors. If your code is indented at the wrong level — for example, a return statement sitting outside its function, or an else block that doesn’t align with any if — you need to fix the logic manually. The formatter will normalize the whitespace, but it won’t move statements between blocks for you.
To format an entire project:
black .
# or
ruff format .To check for issues without modifying files:
black --check .
ruff format --check .Fix 6: Fix if/else/try/except Block Indentation
Deeply nested control flow is where indentation errors happen most often. Each level of nesting adds another indent level, and it becomes easy to misalign a line.
Common mistake — else not aligned with if:
def process(data):
if data is not None:
result = transform(data)
if result.is_valid():
save(result)
else:
log_error("invalid result")
else:
notify_admin() # IndentationError: unexpected indentThe notify_admin() line is indented too far. It should be at the same level as the else: body (one level in from else):
def process(data):
if data is not None:
result = transform(data)
if result.is_valid():
save(result)
else:
log_error("invalid result")
else:
notify_admin()Common mistake — except not aligned with try:
def fetch_data():
try:
response = requests.get(url)
data = response.json()
return data # IndentationError: unexpected indent
except requests.RequestException:
return NoneThe return data line has an extra level of indentation. It should be at the same level as the other statements in the try block:
def fetch_data():
try:
response = requests.get(url)
data = response.json()
return data
except requests.RequestException:
return NoneCommon mistake — unindent doesn’t match:
def calculate(x, y):
if x > 0:
if y > 0:
result = x + y
return result # IndentationError: unindent does not match any outer indentation levelThe return line is indented by 6 spaces, which doesn’t match any existing indentation level (0, 4, 8, or 12). Python cannot determine which block this line belongs to. Fix it by aligning to the correct level:
def calculate(x, y):
if x > 0:
if y > 0:
result = x + y
return resultWhen you have complex nesting, count the spaces carefully. Each level should be exactly 4 spaces deeper than the one above it. If you are working with complex code structures and also running into linting issues, check Fix: ESLint Parsing Error: Unexpected Token for similar structural parsing problems in JavaScript.
Pro Tip: Run
black script.pyorruff format script.pyto auto-fix most indentation problems in a single command. These formatters parse the Python AST and rebuild whitespace from scratch, converting all tabs to 4 spaces and normalizing indentation levels. It is the fastest way to fix a file with mixed indentation.
Fix 7: Set Up .editorconfig for Your Project
An .editorconfig file ensures that every developer and every editor on your project uses the same indentation settings. This prevents the “works on my machine” problem where one developer uses tabs and another uses spaces.
Create a .editorconfig file in your project root:
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
indent_style = space
indent_size = 4
[*.{js,ts,jsx,tsx,json,yaml,yml}]
indent_style = space
indent_size = 2
[Makefile]
indent_style = tabMost modern editors — VS Code, PyCharm, Sublime Text, Vim (with a plugin), and many others — read .editorconfig files automatically. When a developer opens a Python file in the project, their editor will use spaces with an indent size of 4, regardless of their personal global settings.
This is especially important in teams where developers use different editors. Without .editorconfig, one developer might use PyCharm with 4-space indentation while another uses Vim with tabs, and every commit introduces whitespace changes that create merge conflicts and indentation errors.
To verify that your .editorconfig is working, open a Python file in the project and check the status bar of your editor. It should show Spaces: 4 (not tabs).
Fix 8: Fix Line Endings and Encoding Issues
Mixed line endings can occasionally cause phantom indentation errors. If your file was edited on both Windows (\r\n) and Unix/macOS (\n), the \r character at the end of some lines can confuse Python’s parser.
Convert to Unix line endings:
sed -i 's/\r$//' script.pyOr use the dos2unix utility:
dos2unix script.pyIn VS Code, click CRLF or LF in the bottom-right status bar and switch to LF. To make this the default, add to your settings:
{
"files.eol": "\n"
}If your file contains unusual Unicode characters that look like spaces but are not (such as the em space \u2003, en space \u2002, or zero-width space \u200b), Python will reject them as indentation. These typically come from copying code out of PDFs or word processors. The fix is the same as for non-breaking spaces: find and replace them with regular ASCII spaces.
Still Not Working?
The error points to a line that looks correct
Python’s indentation error sometimes points to the line after the actual problem. If line 15 is flagged but looks fine, check line 14 and the lines above it. The parser encounters the error when it reaches the first line that doesn’t match the expected indentation level, which may be several lines after the line where tabs and spaces were actually mixed.
Git shows whitespace-only changes
If git diff shows lines as changed but you can’t see the difference, it is almost certainly a tab-vs-space change. View whitespace changes explicitly:
git diff --ws-error-highlight=allAdd a .gitattributes file to prevent whitespace issues from entering your repository:
*.py whitespace=space-before-tab,indent-with-non-tab,trailing-spacePre-commit hooks to prevent future errors
Add a pre-commit hook to automatically format Python files before every commit:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: mixed-line-ending
args: ['--fix=lf']Install and activate:
pip install pre-commit
pre-commit installNow every commit is auto-formatted and tab/space issues are caught before they enter your codebase.
The error occurs in generated or template code
If the indentation error is in code generated by a template engine (Jinja2, Mako, etc.), the problem is in the template, not in the output. Check the template file’s indentation, keeping in mind that template control structures ({% if %}, {% for %}) add complexity because the template’s own indentation and the generated code’s indentation are two separate things.
You’ve fixed the whitespace but still get IndentationError
If you have confirmed that the file uses consistent spaces and the indentation levels are correct, the problem may be elsewhere in your Python setup. Make sure Python itself is running correctly — see Fix: python: command not found if you are having trouble running Python at all. If your script imports modules that fail to load, the traceback may point to an indentation error in a dependency rather than your own code. Check Fix: ModuleNotFoundError: No module named and Fix: circular import errors for related import issues.
The error appears in a Jupyter notebook cell but not in a .py file
Jupyter cells track indentation per-cell. If you paste indented code into a fresh cell, the leading whitespace of the first line becomes the new baseline and the rest of the block is interpreted relative to it. Either strip all leading whitespace from the pasted block before pressing run, or wrap the code in a function so the indentation is anchored. Notebooks exported to .py via nbconvert sometimes carry magic comments (# In[5]:) that survive but leave stray indent — re-run black file.py to normalize. If the kernel itself will not start, see Fix: Jupyter Not Working.
LLM-generated code repeatedly breaks indentation
Code produced by ChatGPT, Claude, or Copilot inside a chat surface often arrives with U+00A0 non-breaking spaces in indentation, especially when the response wraps long lines or uses smart formatting. Always paste through a plain-text intermediate (e.g., pbpaste | tr ' ' ' ' | pbcopy on macOS, or paste into Notepad first on Windows) before dropping it into your editor. Configuring "editor.formatOnPaste": true in VS Code and running ruff format immediately also strips the problem.
Pre-commit hooks reject the file but the editor shows it as clean
If pre-commit rejects your commit with a tab/space diff that you cannot see in the editor, your editor has "editor.renderWhitespace": "none" and is silently hiding the mismatch. Enable whitespace rendering and run python -tt. If the file imports a module that fails to compile, you may also be hitting a venv mismatch — see Fix: ModuleNotFoundError No Module Named in Python venv. If a circular import in a sibling module is preventing the file from loading at all, see Fix: ImportError Cannot Import Name Most Likely Due to a Circular Import.
Related: Fix: python: command not found | Fix: ModuleNotFoundError: No module named | Fix: circular import errors in Python
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: aiosqlite Not Working — Single Writer, WAL Mode, Row Factory, and Connection Patterns
How to fix Python aiosqlite errors — database is locked, WAL mode for concurrent reads, foreign_keys PRAGMA, row factory for dict-like rows, connection per request vs pool, datetime detect_types, and FastAPI integration.
Fix: AWS Lambda SnapStart Not Working — Version vs Alias, Restore Hooks, and Uniqueness Bugs
How to fix Lambda SnapStart errors — feature requires published version, $LATEST not supported, restore hook for stale connections, UUID collisions after snapshot, time-based state staleness, and pricing surprises.
Fix: mise Not Working — Shell Activation, .tool-versions, Plugin Install, and Python venv
How to fix mise (formerly rtx) errors — activation hook not running, tool not found after install, .tool-versions vs .mise.toml, Python venv integration, idiomatic env loading, and trust prompts.
Fix: Modal Not Working — App vs Stub, Image Build, Volumes, GPU Selection, and Cold Starts
How to fix Modal Labs errors — modal.App vs modal.Stub deprecation, image dependencies missing, Volume vs NetworkFileSystem, GPU type mismatch, .remote vs .local invocation, web endpoint URL, and cold start tuning.