Python Error Guide: 'IndentationError: unexpected indent' — Fix Whitespace and Blocks
Fix 'IndentationError: unexpected indent' in Python: resolve mixed tabs and spaces, stray leading whitespace, misaligned blocks, and paste artifacts cleanly.
- #python
- #automation
- #troubleshooting
- #errors
Stuck on this Bash & Python Automation error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
Python uses indentation to define code blocks, so whitespace is syntax. IndentationError: unexpected indent is raised at parse time — before any code runs — when a line is indented more than the parser expects with no block-opening statement (like if, for, def, or :) to justify it:
File "deploy.py", line 12
retries = 3
^
IndentationError: unexpected indent
The caret points at the first offending character. Because this is a SyntaxError subclass, the entire module fails to import — nothing executes, not even the lines above the error.
Symptoms
- The script fails immediately on run or import with
IndentationError, before any output. - The flagged line looks correctly aligned in your editor but Python still rejects it (a tabs-vs-spaces mismatch).
- Code pasted from a terminal, chat, email, or a web page fails with unexpected indent.
- A related message appears —
unexpected indent,expected an indented block,unindent does not match any outer indentation level, orTabError: inconsistent use of tabs and spaces. - The error moves or disappears when you retype the line by hand.
Common Root Causes
- Mixed tabs and spaces — some lines use tab characters and others use spaces; they can look identical on screen but the parser treats them as different indentation (raises
TabErrorin Python 3). - Stray leading whitespace — an accidental space or tab at the start of a top-level line that shouldn’t be indented at all.
- Paste artifacts — copying from a REPL (with
>>>/...prompts), a diff (with+/-prefixes), or a rich-text source that injects non-breaking spaces or smart indentation. - Misaligned block after an edit — moving a line in or out of a block without matching the surrounding indentation level.
- Inconsistent indent width — mixing 2-space and 4-space indentation within the same block.
- Non-breaking or zero-width characters — a U+00A0 (non-breaking space) or U+FEFF (BOM) masquerading as normal whitespace.
Diagnostic Workflow
First, make the parser tell you exactly where it fails and see the full traceback:
python -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" deploy.py
Traceback (most recent call last):
...
File "deploy.py", line 12
retries = 3
^
IndentationError: unexpected indent
Reveal the actual whitespace bytes on the offending lines — tabs show as \t, non-breaking spaces as 302 240:
sed -n '10,14p' deploy.py | cat -A # tabs show as ^I, line ends as $
sed -n '12p' deploy.py | od -c # exact bytes, incl. \t and \240 (NBSP)
Ask Python to flag tab/space inconsistency directly:
python -tt deploy.py # -tt makes inconsistent tab/space usage a hard error
Search the whole file for tab-indented lines if you standardize on spaces:
grep -nP '^\t' deploy.py # lines that start with a tab
grep -nP '^ *\t| \t|\t ' deploy.py # mixed space-then-tab or tab-then-space runs
Example Root Cause Analysis
An automation script failed in CI but “looked fine” in the developer’s editor:
File "sync.py", line 21
for item in queue:
IndentationError: unexpected indent
Line 21 appeared to line up with its neighbors. cat -A exposed the truth:
$ sed -n '19,22p' sync.py | cat -A
logger.info("starting")$
^Ifor item in queue:$
^I process(item)$
done()$
Line 20 (logger.info) was indented with four spaces, but line 21 (for item) began with a tab (^I). The developer’s editor rendered a tab as four columns, so the two lines looked aligned, but Python saw a space-indented line followed by a tab-indented line at a different level — an unexpected indent. The fix was to convert the whole file to spaces and enforce it:
# Convert tabs to 4 spaces across the file
expand -t 4 sync.py > sync.py.tmp && mv sync.py.tmp sync.py
# Verify no tabs remain
grep -nP '\t' sync.py && echo "tabs still present" || echo "clean"
# Confirm it parses
python -c "import ast; ast.parse(open('sync.py').read())" && echo "parses OK"
The durable fix was configuring the editor and a formatter (black) so indentation can never drift again — PEP 8’s four-spaces, no tabs.
Prevention Best Practices
- Use spaces, never tabs — follow PEP 8’s four-spaces-per-level and configure your editor to insert spaces when Tab is pressed.
- Enable “show whitespace” in your editor so tabs and trailing whitespace are visible while editing.
- Run a formatter —
black(orruff format) rewrites indentation consistently and makes this class of error nearly impossible. - Lint in CI —
ruff/flake8flags indentation and whitespace issues before the code merges. - Run Python with
-ttin tests/CI so any tab/space inconsistency fails loudly. - Add an
.editorconfigto the repo (indent_style = space,indent_size = 4) so every contributor’s editor agrees. - Strip paste prompts — when copying from a REPL or diff, remove
>>>,..., and+/-prefixes before pasting into a file.
Quick Command Reference
python -c "import ast,sys; ast.parse(open(sys.argv[1]).read())" file.py # parse-only check
sed -n 'Np' file.py | cat -A # show tabs (^I) and line ends ($)
sed -n 'Np' file.py | od -c # exact bytes incl. \t and NBSP (\240)
python -tt file.py # make tab/space inconsistency fatal
grep -nP '\t' file.py # find tab characters
expand -t 4 file.py > out && mv out file.py # convert tabs to spaces
black file.py # auto-fix indentation
Conclusion
IndentationError: unexpected indent is a parse-time failure telling you a line is indented without a block that permits it — most often because tabs and spaces are mixed, or stray whitespace slipped in from a paste. Because the module won’t even import, the fix has to happen before the code runs: reveal the real whitespace with cat -A or od -c, standardize on four spaces, and let a formatter and linter enforce it. Configure your editor and an .editorconfig once, and this whole category of error stops recurring.
Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.