How does the left edge show which statements belong together?
Which headers require an indented body?
Why can wrong indentation change behavior without producing an error?
How can you repair invisible tabs, spaces, and mismatched levels?
1. A colon opens a block
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:
header: body line 1 body line 2outside again
You will see this shape many times:
if condition: do_this()for item in items: do_that(item)while still_working: keep_going()def function_name(): function_body()class ClassName: class_body()
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.
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]
The line after if ready: should be indented. Python cannot tell what belongs to the if statement, so it raises an IndentationError.
Fixed code:
ready =Trueif ready:print("Start")
Common issue: a block that is accidentally empty
Broken code:
ready =Trueif ready:print("Start")
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:
ready =Trueif ready:passprint("Start later")
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.
NoteA preview of later Python
This lesson uses if, loops, functions, and classes because they all share the same visible block rule. You only need to read and repair their indentation shape here. Units 4, 5, and 11 teach their behavior in depth.
2. Keep if, elif, and else aligned
The if, elif, and else headers must line up with each other. Their bodies are indented one level deeper.
Map sibling branches
temperature =18if temperature <10:print("Wear a heavy coat")elif temperature <20:print("Wear a light jacket")else:print("Short sleeves are fine")print("Weather check complete")
The three headers are siblings:
if temperature < 10:elif temperature < 20:else:
Each body is indented under its own header:
print(...)
Common issue: else indented under if body
Broken code:
temperature =18if temperature <10:print("Wear a heavy coat")else:print("Not that cold")
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:
temperature =18if temperature <10:print("Wear a heavy coat")else:print("Not that cold")
Common issue: code that should be conditional but is outside
This code runs, but the indentation changes the meaning:
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.
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.
Follow one loop body
names = ["Ada", "Grace", "Katherine"]for name in names:print("Hello", name)print("This line is inside the loop")print("All greetings sent")
Expected output:
Hello AdaThis line is inside the loopHello GraceThis line is inside the loopHello KatherineThis line is inside the loopAll greetings sent
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:
scores = [10, 20, 30]for score in scores: total =0 total = total + scoreprint(total)
It prints 30, not 60, because total = 0 runs at the start of every loop trip. The accumulator resets again and again.
Fixed code:
scores = [10, 20, 30]total =0for score in scores: total = total + scoreprint(total)
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:
Move the final print(total) line inside the loop. Predict how many lines will print and what each line will show.
Show the explanation
scores = [10, 20, 30]total =0for score in scores: total = total + scoreprint(total)
Expected output:
103060
Inside the loop, print(total) runs after every update, so it shows the running total. Outside the loop, it would show only the final total.
4. Show which lines repeat in a while loop
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.
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.
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. Put a function’s body beneath def
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.
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.
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.
The if and else headers are inside the function. The return lines are inside the matching conditional bodies.
6. Put methods inside their class
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.
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:
class Counter:def__init__(self):self.value =0def increment(self):self.value =self.value +1
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:
class Counter:def__init__(self):self.value =0def increment(self):self.value =self.value +1counter = Counter()counter.increment()
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:
AttributeError: 'Counter' object has no attribute 'increment'
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:
class Counter:def__init__(self):self.value =0def increment(self):self.value =self.value +1counter = Counter()counter.increment()print(counter.value)
Expected output:
1
NoteDebugging routine
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:
class Counter:def__init__(self):self.value =0def increment(self):self.value =self.value +1counter = Counter()counter.increment()
At first glance, this can look close to correct because increment is visually near the class. But count the indentation levels:
class Counter: # level 0 def __init__(self): # level 1, method inside class self.value = 0 # level 2, inside __init__ def increment(self): # level 2, also inside __init__
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:
counter.increment()
will likely raise:
AttributeError: 'Counter' object has no attribute 'increment'
The fix is to move def increment(self): one indentation level to the left, so it lines up with the other method definition:
class Counter:def__init__(self):self.value =0def increment(self):self.value =self.value +1counter = Counter()counter.increment()print(counter.value)
Expected output:
1
TipHow to spot this quickly
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:
class Counter:def__init__(self):self.value =0
The assignment should be inside the method body. It needs one more indentation level than the method header.
Fixed code:
class Counter:def__init__(self):self.value =0
Common issue: object code accidentally inside the class
This code is valid in some cases but usually not what beginners intend:
class Counter:def__init__(self):self.value =0 counter = Counter()
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:
class Counter:def__init__(self):self.value =0counter = Counter()
WarningCommon mistake
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. Fix tabs, spaces, and invisible mismatches
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:
ifTrue:print("four spaces")print("tab")
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:
ifTrue:print("line one")print("line two")
The second print line is indented two spaces, not four. Python sees it as a mismatched indentation level.
Fixed code:
ifTrue:print("line one")print("line two")
The exact number of spaces matters less than consistency, but four spaces is the community standard and the style used throughout this course.
8. Read nested blocks along the left edge
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.
Trace three nested levels
def summarize_scores(scores): total =0for score in scores:if score >=70:print("passing score:", score)else:print("needs review:", score) total = total + scorereturn totalprint(summarize_scores([80, 55, 90]))
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 bodyprint(...) # level 0, outside function
This 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 if or else body?
Move return into the loop and predict the effect
NoteChallenge
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:
def summarize_scores(scores): total =0for score in scores: total = total + scorereturn totalprint(summarize_scores([80, 55, 90]))
Expected output:
80
Fixed version:
def summarize_scores(scores): total =0for score in scores: total = total + scorereturn totalprint(summarize_scores([80, 55, 90]))
Expected output:
225
9. Diagnose indentation before changing program logic
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
Mark every incorrect level before fixing it
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 =0for minutes inself.minutes: total = total + minutesreturn totallog = StudyLog()log.add(20)log.add(-5)print(log.total())
Before fixing, identify the problem lines:
print("minutes must be positive") should be inside the else body.
total = total + minutes should be inside the for loop body.
return total should 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 =0for minutes inself.minutes: total = total + minutesreturn totallog = StudyLog()log.add(20)log.add(-5)print(log.total())
Expected output:
minutes must be positive20
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:
ifTrue:print("one space")print("still works sometimes, but do not do this")
Prefer this style:
ifTrue:print("four spaces")print("clear block")
You will occasionally see compact one-line statements like this:
if ready: print("Start")
Python allows it, but beginners should avoid it. Separate lines make the block structure visible:
if ready:print("Start")
11. Map and repair a complete block structure
NoteChallenge
Create a small Notebook class with three methods:
__init__ creates an empty list named notes.
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 inself.notes:print("-", note)notebook = Notebook()notebook.add("Practice indentation")notebook.add("")notebook.add("Run small examples")notebook.show()
Expected output:
empty note ignored- Practice indentation- Run small examples
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.