No Braces: Indentation Is The Syntax
Every other language you have used marks a block with { and }. Python does not have them.
This is the first thing that trips people coming from Java, C#, C++ or JavaScript, and it is the most common error in anyone's first week. It takes ten minutes to understand and then never bothers you again.
The rule
A block begins with a colon at the end of a line, and contains every line indented underneath it.
if severity == "P1":
page_on_call()
notify = True
print("done")The two indented lines belong to the if. The print does not — it is back at the outer level, so it runs either way.
In Java that would be:
if (severity.equals("P1")) {
pageOnCall();
notify = true;
}
System.out.println("done");Same structure, same meaning. Python simply removed the braces and made the whitespace carry the information.
Why the difference matters
In Java, indentation is a courtesy to the reader. The compiler ignores it. You can write your whole program on one line and it still works.
In Python, indentation is the syntax. Change it and you change what the program does.
Consider these two:
for ticket in tickets:
process(ticket)
print("done")for ticket in tickets:
process(ticket)
print("done")The first prints done once per ticket. The second prints it once, at the end. Nothing else changed. No error, no warning — just a different program.
That is the whole reason this page exists. Brace languages cannot express a bug this way.
The rules in practice
- Four spaces per level. This is the convention and every editor follows it.
- Be consistent. Whatever you use, use it everywhere in the file.
- Never mix tabs and spaces. They look identical and Python treats them differently. This produces
TabErroror, worse, silently wrong nesting. - Let your editor do it. VS Code, PyCharm and Colab all indent for you after a colon and convert tabs to spaces.
The last point is the practical answer. Almost nobody manages indentation by hand.
Where colons appear
Every construct that opens a block ends its line with a colon.
if x > 5:
...
elif x > 0:
...
else:
...
for item in items:
...
while retries < 3:
...
def add(a, b):
...
class Agent:
...
try:
...
except ValueError:
...
with open("file.txt") as f:
...A missing colon is one of the most common SyntaxError causes, and the message rarely says "you forgot a colon". If a SyntaxError points at a line that looks fine, check the line above for a missing colon or an unclosed bracket.
Nesting
Each level is four more spaces.
for ticket in tickets:
if ticket["severity"] == "P1":
if ticket["owner"] is None:
assign(ticket)
notify(ticket)
log(ticket)Read it by column, not by word:
logruns for every ticketnotifyruns only for P1 ticketsassignruns only for P1 tickets with no owner
The structure is visible at a glance, which is the upside of the whole design. Deeply nested Python is hard to write badly and stay unnoticed — if it looks wrong, it is wrong.
If you find yourself four levels deep, that is usually a sign to pull the inner part out into a function.
Empty blocks
Java lets you write { }. Python has no way to write nothing, so it has a word for it:
def not_written_yet():
passpass does nothing. It exists purely so the block is not empty. You will use it while sketching.
The errors you will see
| Message | Cause |
|---|---|
IndentationError: expected an indented block | You wrote a colon and then did not indent the next line |
IndentationError: unexpected indent | You indented a line that should not be |
IndentationError: unindent does not match any outer indentation level | Your levels are inconsistent — usually mixed tabs and spaces |
TabError: inconsistent use of tabs and spaces | Exactly what it says |
SyntaxError: expected ':' | Missing colon |
All five are cheap to fix once you recognise them. None indicates anything deeper is wrong.
One habit that solves it permanently
Turn on "render whitespace" in your editor, at least for a fortnight. VS Code: Settings, search for renderWhitespace, set it to all. You will see dots for spaces and arrows for tabs, and every indentation problem becomes visible rather than invisible.
Also switch on "insert spaces instead of tabs", which is the default in most Python setups but worth confirming.
Practise this
- Write the two
forloops from the top of this page and confirm they behave differently. - Cause each of the first three errors in the table on purpose.
- Write an
ifinside aforinside adef, three levels deep, and read it by column. - Turn on whitespace rendering in your editor and look at a file you have already written.