Fix: Python SyntaxError: invalid syntax
Part of: Python Errors
Quick Answer
How to fix Python SyntaxError invalid syntax caused by missing colons, parentheses, wrong operators, Python 2 vs 3 syntax, f-strings, and walrus operator issues.
The Error
You run a Python script and get:
File "app.py", line 5
if x == 10
^
SyntaxError: invalid syntaxOr variations:
SyntaxError: invalid syntax (line 12)SyntaxError: invalid syntax. Perhaps you forgot a comma?SyntaxError: f-string: expecting '}'Python’s parser could not understand your code. The ^ caret points to approximately where the parser gave up, but the actual mistake is often on the line before or a few characters earlier.
Why This Happens
Python’s parser reads source code as a stream of tokens and tries to assemble them into a valid syntax tree. When the next token does not fit any grammar rule it could currently apply, the parser stops and reports SyntaxError. The location it prints is the token that broke the rule, not necessarily the source of the bug. A missing closing parenthesis on line 5 keeps the parser in “still inside an expression” mode for every subsequent line, so it does not give up until something definitively cannot continue an expression — often a keyword like def or if many lines later.
Python 3.10 and newer have a much better parser (PEG-based, replacing the old LL(1) parser), and the error messages are noticeably more specific: “Perhaps you forgot a comma?”, “expected ’:’”, “did you forget parentheses around the comprehension target?”. On older Pythons the same code prints a bare SyntaxError: invalid syntax. If your team runs a mix of versions, expect the same source file to produce very different diagnostics depending on where it is executed.
There is also a category of errors that look like syntax errors but are really version errors. match statements parse on 3.10 and crash on 3.9. F-strings parse on 3.6 and crash on 3.5. Type-parameter syntax (def f[T](x: T) -> T) parses on 3.12 and crashes everywhere else. The parser correctly reports SyntaxError because, in the version doing the parsing, the syntax really is invalid. Knowing which features arrived in which version is half the battle.
Common causes:
- Missing colon after
if,for,while,def,class,else,elif,try,except,with. - Unmatched parentheses, brackets, or quotes.
- Using
=instead of==in conditions. - Python 2 syntax in Python 3.
print "hello"instead ofprint("hello"). - Missing commas in lists, dicts, or function arguments.
- Using reserved keywords as variable names.
- Invalid f-string expressions.
Platform and Environment Differences
Python is one of the worst languages for “works on my machine” because the OS package manager dictates which interpreter you actually have, and the same code can produce SyntaxError on one OS and run cleanly on another.
Ubuntu 22.04 LTS ships Python 3.10 as python3. Ubuntu 24.04 LTS ships 3.12. Debian 12 ships 3.11. RHEL 9 / Rocky 9 / AlmaLinux 9 ship 3.9 as the default system Python and gate newer versions behind dnf install python3.11 or python3.12. Amazon Linux 2023 defaults to 3.9. Alpine Linux version varies per release: 3.18 ships 3.11, 3.19 ships 3.11, 3.20 ships 3.12.
macOS stopped shipping a usable system Python after macOS 12.3 (the old /usr/bin/python was 2.7 and is gone). What you get now depends on whether you installed Xcode CLT (which provides 3.9.x), Homebrew (brew install python gives the latest stable), pyenv, or the python.org installer. Apple Silicon Macs may have separate Intel and ARM Pythons under Rosetta; which -a python3 shows them all.
Windows is more chaotic. The Microsoft Store Python (linked from the python command if nothing else is installed) currently ships 3.12. The python.org MSI installer can install any version side-by-side and registers them with the py launcher (py -3.10 script.py). Anaconda installs its own Python alongside everything else. The same script run via VS Code’s “Run Python File” may pick a completely different interpreter than python at the terminal.
Feature-introduction history matters when porting code:
- f-strings: 3.6
- f-string
=debug expressions (f"{value=}"): 3.8 - Walrus operator
:=: 3.8 - Positional-only parameters (
def f(x, /)): 3.8 match/casestatements: 3.10- Parenthesized context managers (
with (a, b):): 3.10 - Exception groups and
except*: 3.11 - Type parameter syntax (
def f[T](x: T)): 3.12 - F-string nesting and quote reuse (
f"a {"b"}"): 3.12
A match block written in VS Code on Python 3.12 dies with SyntaxError the moment a teammate tries it on the system Python in RHEL 9. The fix is rarely the code — it is to install a newer interpreter (pyenv install 3.12, or the distro’s python3.12 package) and pin it in pyproject.toml.
Docker base images mirror the underlying distro. python:3.12-slim is Debian-based and reliable. python:3.12-alpine builds on Alpine and inherits Alpine’s musl-libc quirks, which can break wheels but not syntax. Pinning a specific minor version (python:3.12-slim, not python:3-slim) is the cheapest way to make sure your local interpreter matches the one in production.
Fix 1: Add the Missing Colon
The most common cause. Control structures require a colon at the end:
Broken:
if x > 10
print("big")
for i in range(5)
print(i)
def greet(name)
return f"Hello, {name}"
class User
passFixed:
if x > 10:
print("big")
for i in range(5):
print(i)
def greet(name):
return f"Hello, {name}"
class User:
passEvery if, elif, else, for, while, def, class, try, except, finally, and with statement must end with :.
Fix 2: Fix Unmatched Parentheses and Brackets
An unclosed (, [, or { causes a SyntaxError, often on a later line:
Broken:
data = {
"name": "Alice",
"age": 30
# Missing closing }
print("done") # SyntaxError appears HERE, not on the dictFixed:
data = {
"name": "Alice",
"age": 30
}
print("done")Tip: Count your brackets. Most editors highlight matching brackets. If the error points to a line that looks correct, check previous lines for unclosed brackets.
Common case — multi-line function call:
result = some_function(
arg1,
arg2,
arg3
# Missing )
next_line = "hello" # Error appears herePro Tip: When the SyntaxError points to a line that looks perfectly valid, the bug is almost always on a previous line — usually an unclosed parenthesis, bracket, or string. Start from the error line and scan backwards.
Fix 3: Fix Python 2 vs Python 3 Syntax
If you are running Python 3 with Python 2 code:
print statement (Python 2) → print function (Python 3):
# Python 2 (SyntaxError in Python 3):
print "Hello"
# Python 3:
print("Hello")Integer division:
# Python 2: 5 / 2 = 2 (integer division)
# Python 3: 5 / 2 = 2.5 (float division)
# Python 3: 5 // 2 = 2 (integer division)except syntax:
# Python 2 (SyntaxError in Python 3):
except ValueError, e:
# Python 3:
except ValueError as e:raise syntax:
# Python 2:
raise ValueError, "message"
# Python 3:
raise ValueError("message")Check your Python version: python --version. If Python itself is not found, see Fix: python command not found.
Fix 4: Fix Assignment vs Comparison
Using = (assignment) where == (comparison) is expected:
Broken:
if x = 10: # SyntaxError — cannot assign in if condition
print("ten")Fixed:
if x == 10:
print("ten")Walrus operator (Python 3.8+):
If you intentionally want to assign AND compare:
if (n := len(items)) > 10:
print(f"Too many items: {n}")The walrus operator := is valid in Python 3.8+. In older versions, it causes a SyntaxError.
Fix 5: Fix F-String Syntax Errors
F-strings have special parsing rules:
Broken — backslash inside f-string expression (before Python 3.12):
name = f"{'\\n'.join(items)}" # SyntaxError in Python < 3.12Fixed — use a variable:
newline = "\n"
name = f"{newline.join(items)}"Broken — unmatched braces:
msg = f"Value is {x" # Missing closing }
msg = f"Use {{x}}" # Wrong — {{ is an escaped braceFixed:
msg = f"Value is {x}"
msg = f"Use {{{x}}}" # Literal { + value + literal }Broken — nested quotes:
msg = f"Hello {"world"}" # SyntaxError in Python < 3.12Fixed:
msg = f"Hello {'world'}" # Use different quotes inside
# Or in Python 3.12+:
msg = f"Hello {"world"}" # Now validFix 6: Fix Reserved Keyword Usage
Using Python keywords as variable or function names:
class = "Math" # SyntaxError — 'class' is reserved
return = 42 # SyntaxError — 'return' is reserved
import = "data.csv" # SyntaxError — 'import' is reservedPython reserved keywords:
False, None, True, and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, nonlocal, not, or, pass, raise, return, try, while, with, yield
Fix — use a different name:
class_name = "Math"
return_value = 42
import_path = "data.csv"Common Mistake: Using
type,list,dict,str,int,id,input,formatas variable names. These are not keywords (no SyntaxError), but they shadow built-in functions, causing subtle bugs later. For example,list = [1,2,3]means you can no longer calllist().
Fix 7: Fix Missing Commas
Missing commas in collections, function calls, or multi-line expressions:
Broken — missing comma in list:
items = [
"apple"
"banana" # SyntaxError or silent string concatenation!
"cherry"
]Python concatenates adjacent string literals: "apple" "banana" becomes "applebanana". This is not a SyntaxError but is almost certainly a bug.
Fixed:
items = [
"apple",
"banana",
"cherry",
]Broken — missing comma in function call:
result = my_function(
arg1
arg2 # SyntaxError
)Fixed:
result = my_function(
arg1,
arg2,
)Trailing commas are valid in Python and recommended for multi-line structures — they make diffs cleaner.
Fix 8: Fix Multi-Line String Issues
Broken — unclosed string:
message = "Hello
World" # SyntaxError — string not closedFixed — use triple quotes for multi-line:
message = """Hello
World"""Or use explicit newline:
message = "Hello\nWorld"Or parenthesized string concatenation:
message = (
"Hello "
"World"
)For indentation-related syntax issues, see Fix: Python IndentationError.
Still Not Working?
If you cannot find the syntax error:
Use Python’s -c flag to test snippets:
python3 -c "if True: print('ok')"Use a linter. pylint, flake8, or ruff catch syntax errors with better messages:
pip install ruff
ruff check app.pyCheck for invisible characters. Copy-pasting from web pages can introduce zero-width spaces or non-breaking spaces:
import re
with open("app.py", "rb") as f:
content = f.read()
non_ascii = [(i, b) for i, b in enumerate(content) if b > 127]
print(non_ascii)Check for mixed tabs and spaces. Python 3 does not allow mixing tabs and spaces for indentation. Configure your editor to use spaces only:
python3 -tt app.py # Warns about tab/space mixingCheck the encoding declaration. Non-UTF-8 files might need an encoding header:
# -*- coding: utf-8 -*-Check the actual interpreter version, not the one you think you have. python --version and python3 --version may differ from the interpreter your IDE actually launches. Inside the script, print import sys; print(sys.version, sys.executable) and confirm both match the version where the feature you are using was added. A match statement in code that runs perfectly in your editor will throw SyntaxError the moment a cron job invokes /usr/bin/python3 on a RHEL 9 box where that is 3.9.
Check the wrong virtualenv. A common variant is that the editor activated .venv (Python 3.12) but a shell tab still has the old .venv (Python 3.9) active. Run which python (or where python on Windows) and compare against sys.executable from inside the script.
Check that the file was saved. If you edited a file in one window and ran it in another, the running process may have loaded a cached .pyc from before your fix. Delete __pycache__/ and re-run. If the error is about imports rather than syntax, see Fix: Python ModuleNotFoundError. For ValueError during type conversions (not syntax errors), see Fix: Python ValueError: invalid literal for int().
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: joblib Not Working — Parallel Backends, Memory Cache, and Pickling Errors
How to fix joblib errors — Parallel n_jobs slower than expected, Memory cache miss, backend loky vs threading vs multiprocessing, pickling lambda not supported, dump load file size, and pytest interference.
Fix: Marshmallow Not Working — Schema Errors, Load vs Dump, and Field Validation
How to fix Marshmallow errors — Schema not validated on dump, ValidationError messages format, unknown field handling, missing vs default, post_load object construction, and Marshmallow 3 to 4 migration.
Fix: Pipenv Not Working — Lock File Generation, Shell Activation, and Dependency Resolution
How to fix Pipenv errors — pipenv lock takes forever, Pipfile.lock not generated, shell activation broken, no virtualenv created, dependency conflict, and migration to uv or Poetry.
Fix: Copier Not Working — Template Updates, Question Conditions, and Migrations
How to fix Copier errors — copier.yml not found, conditional questions not appearing, update breaks generated project, migrations between versions, Jinja vs YAML escaping, and answers file conflict.