flowchart TD A[Left margin\nTop-level code] --> B[Header line ending with colon] B --> C[Indented body\ninside the block] C --> D[Same indentation\nmore lines in same block] C --> E[More indentation\nnested block] C --> F[Back to previous indentation\nblock is finished]
Indentation in Python
Questions
- Why does Python care about spaces at the beginning of a line?
- How do indentation rules apply to
if,for,while, functions, classes, and methods? - How can we read and fix
IndentationError,TabError, and mistakes where code runs outside the intended block?
Objectives
- Identify the header line and body of a Python block.
- Indent code consistently with four spaces.
- Predict which lines run inside and outside nested blocks.
- Fix common indentation mistakes in conditionals, loops, functions, classes, and methods.
Hands-on episode: Indentation in Python
Python uses indentation as part of the language. In many languages, curly braces or keywords mark where a block starts and ends. In Python, the spaces at the start of the line do that job. This makes code readable, but it also means that spacing is not decoration. Spacing changes what the program means.
A block is a group of lines that belongs together. The body of an if statement is a block. The body of a loop is a block. The body of a function is a block. A method inside a class has a block, and that method may contain more blocks inside it.
Use this lesson as a debugging lab. Some examples work. Some examples are intentionally broken. Run each cell in Colab, read the error or output, and then fix only the indentation before changing anything else.
Example 1.1
Predict the output. Which lines belong to the if statement, and which line runs after the if statement is finished?
Expected output:
Explain Example 1.1
if score >= 70:is a header line. The colon says, “a block is coming next.”- The next two
printlines are indented four spaces, so they are inside theifbody. - The blank line does not end the program. It only separates ideas for the reader.
print("Done checking score")is back at the left margin, so it is outside theifblock.- Because the score is
85, the condition is true, so the indented block runs. - The final
printruns no matter what, because it is not inside theifblock.
A good beginner habit is to point to the left edge of each line and ask: “Which block does this line belong to?” If two lines begin at the same column, they are usually siblings in the same block. If one line is farther to the right, it is inside a more specific block.
Challenge 1.1
Change score to 50 and run the cell again. Predict exactly which lines will print before you run.
Show one possible solution path
With score = 50, the condition score >= 70 is false. The two indented lines inside the if block do not run. The final line is outside the block, so it still runs.
Expected output:
Debugging checkpoint 1.1
If Python says IndentationError, first check the line after a colon. A colon usually means the next real line must be indented.
The most important rule is:
Header line with
:first. Indented body next. Return to the previous indentation level when the block is finished.
Apply it
In the Colab notebook, create a cell named “indentation scratchpad” in Markdown. For every example in this lesson, write one sentence before running:
- “I think this line is inside the block because…”
- “I think this line is outside the block because…”
- “I expect this error because…”
Key points
- Indentation is syntax in Python, not visual decoration.
- A colon usually introduces an indented block.
- Lines at the same indentation level belong to the same surrounding block.
- Four spaces per indentation level is the standard Python style.
- Most indentation bugs are fixed by asking which block each line should belong to.
1. The basic block rule
A Python block normally starts with a header line ending in a colon. The body of the block is indented under that header. When the indentation returns to a previous level, the block is over.
Here is the shape:
You will see this shape many times:
The words change, but the block shape stays the same. This is why indentation is a core concept, not a small formatting detail.
This diagram shows how indentation creates nested blocks.
Example 1
Read it as a map:
ready = Trueis top-level code.if ready:starts a conditional block.- The two indented lines are inside that block.
- The final line is back at the top level.
Common issue: missing indented body
Broken code:
The line after if ready: should be indented. Python cannot tell what belongs to the if statement, so it raises an IndentationError.
Fixed code:
Common issue: a block that is accidentally empty
Broken code:
A blank line is allowed inside code, but it does not count as the body of the if statement. Python still expects an indented statement after the colon.
If you need a placeholder while planning code, use pass:
pass means “do nothing here on purpose.” It is useful while sketching a program, but replace it with real code when the behavior is ready.
2. Indentation with if, elif, and else
The if, elif, and else headers must line up with each other. Their bodies are indented one level deeper.
Example 2
The three headers are siblings:
Each body is indented under its own header:
Common issue: else indented under if body
Broken code:
The else is too far to the right. Python reads it as if you are trying to put an else statement inside the if body, but else must match the if.
Fixed code:
Common issue: code that should be conditional but is outside
This code runs, but the indentation changes the meaning:
The dashboard line is not indented, so it runs even when logged_in is false. That is not a syntax error. It is a logic error.
Fixed code:
Not every indentation bug creates an error message. Sometimes the code runs, but a line belongs to the wrong block. When output surprises you, inspect the left edge of the lines before changing conditions or values.
Checkpoint quiz
3. Indentation with for loops
A for loop repeats its indented body once for each item. Lines inside the loop are indented. Lines after the loop return to the previous indentation level.
Example 3
Expected output:
The first two print lines repeat because they are inside the loop. The final line prints once because it is outside the loop.
Common issue: the accumulator is inside the loop
This code is broken logically:
It prints 30, not 60, because total = 0 runs at the start of every loop trip. The accumulator resets again and again.
Fixed code:
The line total = 0 belongs before the loop because it sets up the starting state once. The update belongs inside the loop because it should happen once per score.
Common issue: the loop body is too short
This code runs, but only the last value is summarized:
The final print is outside the loop. It prints once, after the loop, using the last value of tax.
If you want one line per price, indent the print:
If you want only the last tax after the loop, leave it unindented, but name the choice clearly:
Challenge 3.1
Move the final print(total) line inside the loop. Predict how many lines will print and what each line will show.
4. Indentation with while loops
A while loop repeats as long as its condition is true. Indentation decides what repeats. This is especially important because a misplaced update can create an infinite loop.
Example 4
Expected output:
The update line countdown = countdown - 1 is inside the loop, so the condition will eventually become false.
Common issue: update outside the loop
Broken code:
The update is outside the loop, so countdown stays 3 forever while the loop is running. In Colab, stop the cell if it keeps printing. Then move the update line into the loop body.
Fixed code:
Common issue: break and continue at the wrong level
break and continue only make sense inside a loop.
Broken code:
The break is at the top level, outside the for loop. Python raises a syntax error because there is no loop to break out of at that indentation level.
Fixed code:
The break is inside the if, and the if is inside the loop. That means break runs only when the loop reaches the stop item.
5. Indentation with functions
A function definition has a header line and an indented body. Code inside the body runs only when the function is called. Code outside the body runs normally as the program reaches it.
Example 5
The function body is:
The call is outside the function:
Common issue: function body is not indented
Broken code:
Python raises IndentationError because a function definition needs an indented body.
Fixed code:
Common issue: return outside the function
Broken code:
The return line is not indented, so it is outside the function. Python raises SyntaxError: 'return' outside function.
Fixed code:
Common issue: calling the function inside its own body by accident
This code is valid, but it hides the function call inside the function body:
The call greet("Ada") is indented inside greet, so calling greet would call itself again and again. That is recursion, and it is not what beginners usually intend here.
Move the call outside the function:
Challenge 5.1
Write a function named is_passing(score) that returns True when the score is at least 70 and False otherwise. Use indentation to put the return lines in the correct blocks.
6. Indentation with classes and methods
A class body is indented under the class header. Methods are functions defined inside a class, so method definitions are indented under the class, and method bodies are indented under the method definition.
Example 6
There are three indentation levels:
The lines after the class return to the left margin:
Those lines are outside the class. They create and use an object.
Common issue: method is not inside the class
This code runs, but increment is not a method of Counter:
The increment definition starts at the left margin, so it is a regular function, not a method inside Counter.
That difference becomes visible when you try to call it as a method:
Python creates a Counter object, but that object does not know about increment. The function named increment exists at the top level, beside the class, not inside the class. The likely error is:
This error can be confusing because you can see a function named increment in the cell. The indentation is the clue: Python does not ask “is there a function nearby with this name?” It asks “does this object have a method with this name?” For counter.increment() to work, the def increment(...) line must be indented inside the class Counter: block.
Fixed code:
Expected output:
When a method call raises AttributeError, check two things before changing the method body:
- Is the
def method_name(...)line indented inside the class? - Did you spell the method name the same way in the class and in the call?
- Is the method accidentally indented inside another method?
Common issue: method is inside another method by accident
There is a second version of the same mistake. Sometimes the method is not all the way outside the class. Instead, it is accidentally placed inside another method.
Broken code:
At first glance, this can look close to correct because increment is visually near the class. But count the indentation levels:
The line def increment(self): lines up with self.value = 0, not with def __init__(self):. That means Python treats increment as a local function created while __init__ is running. It is not a method stored on the Counter class, so this line:
will likely raise:
The fix is to move def increment(self): one indentation level to the left, so it lines up with the other method definition:
Expected output:
Inside a class, method definitions usually line up with each other. If def increment(...) is farther to the right than def __init__(...), then increment is probably inside __init__ by accident.
Common issue: method body is not indented enough
Broken code:
The assignment should be inside the method body. It needs one more indentation level than the method header.
Fixed code:
Common issue: object code accidentally inside the class
This code is valid in some cases but usually not what beginners intend:
The line counter = Counter() is indented inside the class body. It tries to create a Counter while Python is still building the Counter class. Move object-creation code outside the class:
Classes often have two nested levels before any real behavior happens: method definitions inside the class, then method bodies inside each method. Count the levels before editing.
7. Tabs, spaces, and inconsistent indentation
Python can technically read tabs, but the standard style is four spaces. Mixing tabs and spaces can produce errors that are hard to see because a tab may look like several spaces in your editor.
Common issue: inconsistent indentation
Broken code, with inconsistent whitespace:
Python may raise TabError: inconsistent use of tabs and spaces in indentation. The fix is not to count invisible characters by hand. Use your editor to convert tabs to spaces.
In VS Code:
- Open the command palette.
- Search for Convert Indentation to Spaces.
- Use four spaces per indent level.
- Save and run again.
In Colab, avoid pressing Tab at the beginning of a line unless you know how the notebook handles it. If indentation looks strange, delete the whitespace at the start of the line and press Space four times.
Common issue: indentation width changes inside the same block
Broken code:
The second print line is indented two spaces, not four. Python sees it as a mismatched indentation level.
Fixed code:
The exact number of spaces matters less than consistency, but four spaces is the community standard and the style used throughout this course.
8. Nested blocks
A nested block is a block inside another block. You have already seen this with loops inside functions, if statements inside loops, and methods inside classes. Each nested level moves four more spaces to the right.
Example 7
Indentation levels:
def summarize_scores(scores): # level 0
total = 0 # level 1, function body
for score in scores: # level 1, function body
if score >= 70: # level 2, loop body
print(...) # level 3, if body
else: # level 2, matches if
print(...) # level 3, else body
total = total + score # level 2, loop body
return total # level 1, function body
print(...) # level 0, outside functionThis example is worth tracing slowly. Ask three questions for each line:
- Is this line inside the function?
- Is this line inside the loop?
- Is this line inside the
iforelsebody?
Challenge 8.1
Move return total four spaces to the right so it is inside the loop. Predict what changes before running.
Show the explanation
If return total is inside the loop, the function returns during the first loop trip. That means it never processes the remaining scores. The code may run, but the logic is wrong.
Broken version:
Expected output:
Fixed version:
Expected output:
9. A debugging checklist for indentation problems
Use this checklist when code looks correct but Python complains about spacing or runs lines at the wrong time.
| Symptom | Likely cause | First thing to check |
|---|---|---|
IndentationError: expected an indented block |
A header line ending in : has no indented body |
Look at the first real line after the colon |
IndentationError: unexpected indent |
A line is indented even though no block is open | Look at the previous non-blank line |
IndentationError: unindent does not match any outer indentation level |
A line returned to a column Python does not recognize | Check spaces/tabs and block levels |
TabError |
Tabs and spaces are mixed | Convert indentation to spaces |
| Code runs too often | A line is inside a loop by accident | Check whether it should be left of the loop body |
| Code runs too rarely | A line is outside a loop or conditional by accident | Check whether it should be indented under the header |
return outside function |
return is not inside a function body |
Indent it under the correct def |
break outside loop |
break is not inside a loop body |
Indent it under the correct for or while |
| Method missing from class | def method(...) starts at the left margin |
Indent the method under class |
| Method missing from object | def method(...) is nested inside another method |
Line up method definitions with each other |
Example 8: diagnose before fixing
Broken code:
class StudyLog:
def __init__(self):
self.minutes = []
def add(self, minutes):
if minutes > 0:
self.minutes.append(minutes)
else:
print("minutes must be positive")
def total(self):
total = 0
for minutes in self.minutes:
total = total + minutes
return total
log = StudyLog()
log.add(20)
log.add(-5)
print(log.total())Before fixing, identify the problem lines:
print("minutes must be positive")should be inside theelsebody.total = total + minutesshould be inside theforloop body.return totalshould be inside the method but outside the loop.
Fixed code:
class StudyLog:
def __init__(self):
self.minutes = []
def add(self, minutes):
if minutes > 0:
self.minutes.append(minutes)
else:
print("minutes must be positive")
def total(self):
total = 0
for minutes in self.minutes:
total = total + minutes
return total
log = StudyLog()
log.add(20)
log.add(-5)
print(log.total())Expected output:
This example combines class, method, conditional, loop, and return indentation. It is a good final check because it uses several block levels at once.
10. Style rules that prevent indentation bugs
PEP 8, Python’s style guide, recommends four spaces per indentation level. This course follows that rule.
Good habits:
- Use four spaces for each block level.
- Configure your editor to insert spaces when you press Tab.
- Keep related lines at the same indentation level.
- Do not align code by hand with random numbers of spaces.
- Let formatters such as Ruff format handle mechanical spacing when possible.
- Fix indentation before debugging values, conditions, or algorithms.
Avoid this style:
Prefer this style:
You will occasionally see compact one-line statements like this:
Python allows it, but beginners should avoid it. Separate lines make the block structure visible:
Final challenge
Create a small Notebook class with three methods:
__init__creates an empty list namednotes.add(note)appends the note only if it is not an empty string.show()loops over notes and prints each one.
Use an if, a for loop, a class, and methods. Then intentionally break one indentation level, run the cell, read the error, and fix it.
Show one possible solution
class Notebook:
def __init__(self):
self.notes = []
def add(self, note):
if note != "":
self.notes.append(note)
else:
print("empty note ignored")
def show(self):
for note in self.notes:
print("-", note)
notebook = Notebook()
notebook.add("Practice indentation")
notebook.add("")
notebook.add("Run small examples")
notebook.show()Expected output:
The class body is indented under class Notebook. Each method is indented inside the class. Each method body is indented inside its def line. The if and for bodies are one level deeper inside their methods.
Check your understanding
Key points
- Python uses indentation to decide which lines belong to a block.
- A header line ending in
:usually requires an indented body. if,elif, andelseheaders that belong together should line up.- Loop setup often belongs before the loop; loop updates often belong inside the loop.
- Function bodies are indented under
def;returnmust be inside the function. - Methods are indented under
class; method bodies are indented under method definitions. - Method definitions inside the same class usually line up with each other.
- Use four spaces per indentation level and avoid mixing tabs and spaces.
- When debugging indentation, inspect block structure before changing program logic.
References
- Python Tutorial: control flow: https://docs.python.org/3/tutorial/controlflow.html
- Python Tutorial: defining functions: https://docs.python.org/3/tutorial/controlflow.html#defining-functions
- Python Tutorial: classes: https://docs.python.org/3/tutorial/classes.html
- PEP 8 indentation guidance: https://peps.python.org/pep-0008/#indentation