PYTHON / CONTROL FLOW
pass and writing empty blocks deliberately
Use pass to keep a required block deliberately empty, and tell it apart from continue, return None, and ... in stubs and handlers.
What you will learn
- Write a syntactically valid empty block with pass instead of leaving it blank
- Tell pass apart from continue, break and return None inside loops and functions
- Mark an intentionally empty branch or handler with a comment beside pass
- Choose between pass, ... and a docstring-only body when stubbing code
Understanding pass and writing empty blocks deliberately
Python marks the end of a block with indentation, not with a closing brace or an end keyword. That means a header line ending in a colon has nothing to pair with except the indented statements below it, so the grammar insists on at least one statement there. If you write `if n < 0:` and then dedent, the parser raises IndentationError before your program ever runs. `pass` is the statement you put there when you genuinely want the block to do nothing: it compiles to a no-op and has no runtime effect at all.
Because `pass` does nothing, it also does not redirect control. This is where beginners trip: inside a loop, `continue` jumps to the next iteration and `break` leaves the loop, but `pass` lets execution fall straight through to whatever follows the block. Similarly, a function whose entire body is `pass` returns None, but that is because execution reaches the end of the function, not because `pass` produces a value. Reading `pass` as "skip the rest" is wrong; read it as "there is no code here".
The honest uses are narrow. An `except` clause where you truly want to swallow a specific error, a placeholder class such as `class ConfigError(Exception): pass`, a branch you are leaving inert while the other branches carry the logic, and a function stub you have not written yet. In stubs and protocol definitions many codebases write `...` instead, which is legal because `...` is an expression and an expression alone is a valid statement. If a body already contains a docstring, that docstring is the statement, so adding `pass` after it is redundant. In every case, a short comment next to `pass` is what separates a deliberate empty block from a forgotten one.
def classify(n):
if n < 0:
pass # negatives are out of scope for now, on purpose
elif n % 2 == 0:
return "even"
else:
return "odd"
for n in (-3, 4, 7):
print(n, repr(classify(n)))pass is a statement that does nothing, and it exists only because Python's indentation-based blocks must contain at least one statement.
Worked examples
pass is not continue
Shows that pass leaves the rest of the loop body running while continue skips it.
kept = []
for ch in "a1b2":
if ch.isdigit():
pass
kept.append(ch)
print("with pass:", kept)
kept = []
for ch in "a1b2":
if ch.isdigit():
continue
kept.append(ch)
print("with continue:", kept)Example explained
Line 1The first loop hits pass for '1' and '2', then falls through to kept.append(ch), so digits are still collected.
Line 2The second loop hits continue for the same characters, which abandons the iteration before append runs.
Line 3The two lists differ, which proves pass has no effect on control flow at all.
Line 4If you meant to filter and typed pass, the code runs without error and silently keeps everything.
Why the parser needs it
Compiles a block with and without a body to show that the empty block is a syntax-level error, not a runtime one.
try:
compile("if True:\n", "<demo>", "exec")
except IndentationError as e:
print("empty block ->", type(e).__name__)
try:
compile("if True:\n pass\n", "<demo>", "exec")
except IndentationError:
print("unreachable")
else:
print("with pass -> compiles")Example explained
Line 1compile() turns source text into a code object, so it fails at parse time exactly like an imported module would.
Line 2The first source has a colon header and no indented statement, so CPython raises IndentationError, a subclass of SyntaxError.
Line 3Adding pass supplies the required statement, and the same source compiles cleanly.
Line 4The else branch of try runs only when no exception was raised, confirming the second compile succeeded.
Important notes
`pass` is a keyword statement, not a function; `pass()` is a SyntaxError, and it takes no arguments or value.
A body that already has a docstring, a `...`, or any other statement does not need `pass`; adding it there is noise, not safety.
Common mistakes
Writing `pass` inside a loop's if block when `continue` was meant: the filtered items are still processed, and the bug is silent because nothing raises.
Using `except Exception: pass` (or bare `except: pass`) as a habit: typos, missing keys, and logic errors vanish with no traceback, and the program keeps running on wrong data.
Assuming `pass` ends the enclosing block or returns a value: code after it in the same block still runs, and a pass-only function returns None only because execution reaches the end.
Try it yourself
Change, predict, then run
Write a loop over [4, -1, 9, -6, 2] that appends only non-negative numbers to a list, first using an if branch containing `pass` and then replacing that `pass` with `continue`. Print both results and explain in a comment why they differ.
Open the Python workspaceCheck your understanding
Inside a for loop, an if block contains only `pass`. What happens to the statements written after that if block in the same iteration?
- They still run, because pass does nothing and execution falls through to them
- They are skipped for that iteration, the same as if continue had been used
- They are skipped and the loop ends, because pass behaves like break
- They run only if the if condition was False, since pass cancels the True branch
Show answer
pass is a no-op: it satisfies the requirement that a block contain a statement and has no runtime effect, so the remainder of the loop body executes normally. Option 2 is tempting because pass and continue look interchangeable in an otherwise empty branch, but continue actively aborts the current iteration while pass changes nothing about control flow.