Python SyntaxError: invalid syntax
Invalid syntax
Verified against Python docs: Errors and Exceptions, Python language reference: Lexical Analysis, PEP 617 (PEG parser) · Updated May 2026
> quick_fix
Python's parser rejected your file before any code ran. The error caret points at where it gave up - the actual mistake is usually one or two characters before. Most common: missing colon after if/for/def, unmatched bracket, or use of a feature added in a newer Python version than you're running.
# Common: missing colon
def greet(name)
return f'Hi {name}'
# SyntaxError: expected ':'
# Fix
def greet(name):
return f'Hi {name}'What causes this error
SyntaxError fires during the parse phase, before bytecode compilation. Python's PEG parser builds an AST from your tokens; if the tokens don't match any valid grammar production, it raises SyntaxError with a caret pointing at the first ambiguous token. Common causes: missing colons or commas, mismatched brackets, mixed tabs and spaces (Python 3 forbids), or syntax from a newer Python version (match/case, walrus, structural pattern matching).
How to fix it
- 01
step 1
Read the caret position
Python prints the offending line with a caret pointing at where parsing stalled. The actual bug is often a few characters before - the parser reads forward until it can't continue.
File "app.py", line 5 def greet(name) ^ SyntaxError: expected ':' - 02
step 2
Check for missing colons
Every if, elif, else, for, while, def, class, try, except, finally, and with line ends in a colon. Forgetting one is the #1 cause of SyntaxError.
if x > 0 # missing : print('+') # Fix if x > 0: print('+') - 03
step 3
Check for unmatched brackets
Open paren, bracket, or brace without a close (or vice versa). The caret often points at the line where Python finally gives up, not where the mismatch began.
result = sum([1, 2, 3, 4 # SyntaxError: '(' was never closed (line above) - 04
step 4
Check Python version vs syntax used
match/case requires Python 3.10+. Type-statement (type Alias = int) requires 3.12+. f-strings require 3.6+. Walrus := requires 3.8+. If you see 'invalid syntax' on a line that uses a recent feature, you're on an older Python.
python --version # check your version # Match was added in 3.10 match x: case 1: ... # SyntaxError on Python 3.9 or older - 05
step 5
Check for mixed tabs and spaces
Python 3 forbids mixing tabs and spaces in the same block. Most editors handle this, but copy-pasted code can mix. Use 'python -tt script.py' to flag inconsistencies.
python -tt script.py # TabError: inconsistent use of tabs and spaces in indentation - 06
step 6
Use a linter to catch these in your editor
ruff or pyright in your editor catches SyntaxError before you run anything. Configure them on save and most syntax errors never reach the runtime.
pip install ruff ruff check . # Or as a pre-commit hook
Why SyntaxError happens at the runtime level
Python's parser since 3.9 is a PEG (Parsing Expression Grammar) parser implemented in Parser/parser.c. It reads tokens from the tokenizer and tries to match them against grammar productions defined in Grammar/python.gram. When no production matches, the parser emits SyntaxError with the position of the failing token. The PEG parser produces clearer messages than the older LL(1) parser because it can backtrack and identify which production was 'almost' matching. This is why Python 3.10+ messages say 'expected :' or 'unexpected indent' instead of just 'invalid syntax', though both error classes are still possible.
Common debug mistakes for SyntaxError
- Forgetting the colon at the end of a control-flow line (if, for, def, class, etc.).
- Pasting code from a tutorial that uses syntax newer than your Python version (match/case, walrus, structural patterns).
- Mismatched brackets, often inside multi-line dicts or function calls where the imbalance is visually hidden.
- Mixing tabs and spaces in the same block; modern editors flag it but legacy code often fails.
- Using async/await in a function not marked async, or 'yield' in a non-generator function.
When SyntaxError signals a deeper problem
Frequent SyntaxError in CI signals a missing pre-commit checking discipline. The architectural fix is to enforce ruff (or pyright) as a pre-commit hook, plus a CI step that fails on any syntax error before tests run. Most SyntaxErrors are catchable in milliseconds by a linter, so reaching CI test execution with a syntax error wastes minutes. A team that ships SyntaxErrors regularly is also probably running mismatched Python versions across dev machines; pin python via .python-version or pyproject.toml's requires-python field, and use uv or rye to install consistent versions everywhere.
Frequently asked questions
What's the difference between SyntaxError and IndentationError?
IndentationError is a subclass of SyntaxError. It fires specifically for incorrect indentation: mixing levels, unexpected indent without a colon-line above, or unindenting too far. Plain SyntaxError covers other parse failures: missing colons, unmatched brackets, invalid tokens. Both happen before any code runs and both have line/caret pointers in the message. The fix patterns differ - indentation errors usually need a tab-vs-spaces audit; syntax errors need a token-by-token read of the line above the caret.
Why does Python sometimes report SyntaxError on the line AFTER the bug?
The PEG parser reads forward token by token. Some bugs (like an unclosed bracket) leave the parser in a state where any further token is valid as a continuation; it only signals failure when it hits something that can't possibly fit. So the caret may land on a perfectly valid line - the real issue is several lines earlier. Strategy: check the line before the caret first, especially for unclosed brackets, missing commas in function-call argument lists, and missing colons on control-flow statements.
Why does my f-string crash with SyntaxError on Python 3.5?
f-strings (f'{var}') were added in Python 3.6. On 3.5 and earlier, the f prefix isn't recognized and the parser treats it as a function call f('{var}'), which fails. The fix is to upgrade Python (3.6 is end-of-life; you should be on 3.10+ at minimum). If you must support legacy versions, use .format(): '{}'.format(var). For PEP 701 (relaxed f-string nesting in 3.12), the requirement is even higher.
Can I catch SyntaxError with try/except?
Only if the syntax error is in code you compile dynamically (eval, exec, compile, importlib.import_module). Static SyntaxError - in your script file - fires before any code runs, so there's no try block in scope to catch it. For dynamic code, wrap with try/except SyntaxError. For static code, use a linter to catch errors before deployment, and treat any SyntaxError reaching production as a CI failure.