Coming From Another Language

No Braces: Indentation Is The Syntax

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 TabError or, 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:

  • log runs for every ticket
  • notify runs only for P1 tickets
  • assign runs 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():
    pass

pass does nothing. It exists purely so the block is not empty. You will use it while sketching.

The errors you will see

MessageCause
IndentationError: expected an indented blockYou wrote a colon and then did not indent the next line
IndentationError: unexpected indentYou indented a line that should not be
IndentationError: unindent does not match any outer indentation levelYour levels are inconsistent — usually mixed tabs and spaces
TabError: inconsistent use of tabs and spacesExactly 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 for loops 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 if inside a for inside a def, three levels deep, and read it by column.
  • Turn on whitespace rendering in your editor and look at a file you have already written.

Try It Yourself

Copy this into a new Colab notebook and run it. Nothing to install.

example.py
1# Indentation is the syntax. Two programs, one space of difference.
2
3tickets = ["P1", "P3", "P2"]
4
5print("--- print INSIDE the loop ---")
6for ticket in tickets:
7 print("processing", ticket)
8 print("done") # indented - runs every time
9
10print()
11print("--- print OUTSIDE the loop ---")
12for ticket in tickets:
13 print("processing", ticket)
14print("done") # not indented - runs once
15
16
17# Nesting. Read it by column, not by word.
18print()
19print("--- nesting ---")
20records = [
21 {"severity": "P1", "owner": None},
22 {"severity": "P1", "owner": "network"},
23 {"severity": "P3", "owner": "reporting"},
24]
25
26for r in records:
27 if r["severity"] == "P1":
28 if r["owner"] is None:
29 print("assign it first")
30 print("page the on-call")
31 print("logged:", r["severity"])
32
33
34# `pass` exists so a block is never empty - Python has no `{ }`
35def not_written_yet():
36 pass
37
38
39# The errors, on purpose:
40print()
41print("--- the errors ---")
42
43try:
44 exec("if True:\nprint('no indent')")
45except IndentationError as e:
46 print("IndentationError:", e)
47
48try:
49 exec("x = 1\n y = 2")
50except IndentationError as e:
51 print("IndentationError:", e)
52
53try:
54 exec("if True\n pass")
55except SyntaxError as e:
56 print("SyntaxError (missing colon):", e)