Fix: Python IndentationError – Unexpected Indent or Expected an Indented Block
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.
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.
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: pip SSL: CERTIFICATE_VERIFY_FAILED certificate verify failed
How to fix the pip SSL CERTIFICATE_VERIFY_FAILED error when installing Python packages, covering corporate proxies, trusted-host flags, certifi, pip.conf, macOS, Windows, and Docker environments.
Fix: Python TypeError: 'NoneType' object is not subscriptable
Learn why Python raises TypeError NoneType object is not subscriptable and how to fix it with practical solutions for functions, dictionaries, lists, APIs, and regex.
Fix: Python UnicodeDecodeError – 'utf-8' codec can't decode byte
How to fix Python UnicodeDecodeError 'utf-8' codec can't decode byte in position invalid start byte, covering chardet detection, encoding fallback, BOM handling, pandas CSV encoding, and PYTHONIOENCODING.
Fix: Python ValueError: invalid literal for int() with base 10
How to fix Python ValueError invalid literal for int() with base 10 caused by passing float strings, whitespace, empty strings, or non-numeric characters to int().