LambdaLabTM
Computer Science · Class 12 · Functions
FunctionsFlow⏱️ 13 min read

Flow of Execution

Flow of execution is simply the order in which lines run. Without functions it is top to bottom, and you can read it off the page. A call breaks that: it jumps into the function, runs it, and jumps back to exactly where it left. Learning to trace that jump is the whole of this lesson.

1A call jumps, and comes back

flow_trace.py
def double(n):
    print('   inside double, n =', n)
    return n * 2

print('1. program starts')
answer = double(5)
print('2. back in the main program, answer =', answer)
print('3. program ends')
Output
1. program starts
   inside double, n = 5
2. back in the main program, answer = 10
3. program ends
The order Python actually took
  1. 1def double(n):
    Stores the function. Runs none of its body.
  2. 2print('1. program starts')
    Prints the first line.
  3. 3answer = double(5)
    Reaches the call — and jumps into the function, taking 5 with it.
  4. 4 print(' inside double…')
    Inside the function now.
  5. 5 return n * 2
    Works out 10, ends the function, and jumps back to the call.
  6. 6answer = 10
    The call has become 10, so the assignment finishes.
  7. 7print('2. back in…')
    Carries on from where it left off.
  8. 8print('3. program ends')
    And on to the end.
Key Takeaway
The call is a detour, not a departure. Python remembers where it was. When the function finishes — because it hit a return, or simply ran out of lines — it resumes at that exact spot, with the returned value in hand.

2def does not run the body

Worth seeing on its own, because it decides the answer to almost every “predict the output” question about functions:

def_is_not_a_call.py
print('A')

def f():
    print('B')

print('C')
f()
print('D')
Output
A
C
B
D

A, C, B, D. The B is printed third, not second — because reaching the def only stored the function, and the body waited until the call on the second-to-last line.

3A function that calls another function

flow_nested.py
def inner():
    print('      inner runs')

def outer():
    print('   outer starts')
    inner()
    print('   outer ends')

print('main starts')
outer()
print('main ends')
Output
main starts
   outer starts
      inner runs
   outer ends
main ends

The indentation of the output is a picture of the flow. Each call goes one level deeper, and each finish comes back one level — and the two main lines sit outside everything because that is where the program started and ended.

Note
Python keeps a stack of where to come back to. When inner() finishes it returns to the middle of outer(), and when outer() finishes it returns to the middle of the main program. Each pending call is remembered until the one inside it is done — the same last-in-first-out idea as the stack you will meet later in this course.

4Definitions can be in any order — calls cannot

Since def only stores, one function may mention another that is defined further down. By the time anything is called, every definition in the file has been read:

order_ok.py
def a():
    b()              # b does not exist yet — and that is fine

def b():
    print('b ran')

a()                  # by now, both exist
Output
b ran

Move the a() call to the top of the file, though, and it fails — because at that moment the def lines have not run yet. The rule is about when the call happens, not when the mention is written.

5How to trace one by hand

The method that gets these right in an exam
  1. 1. Skip every def block on the way down. They store, they do not run.
  2. 2. Start at the first line that is not inside a function, and follow it down.
  3. 3. At a call, put your finger on that line and jump to the function. Write the arguments beside its parameters.
  4. 4. At return — or the end of the body — go back to your finger, with the value.
  5. 5. Write down each print as you pass it. That list, in order, is the answer.
trace_me.py

Work that one out on paper before you run it. The answer is start, then  in thrice, then   in twice, then 12, then end — and the 12 appears after both function messages because the outer print cannot print until the call inside it has produced a value.

6Recap

def stores, it does not run

A, C, B, D — the body waits until a call reaches it, however early the def appears.

A call is a detour

Python jumps in, runs the body, and resumes at exactly the line it left, with the returned value.

Calls can nest

Each one goes a level deeper and comes back a level. Python remembers every pending call.

Trace with your finger on the call

Skip the defs, follow the main program, jump at each call, come back at each return, and write down the prints.

✍️ Now write these yourself
  1. 1

    Predict the output of the A / C / B / D program before running it.

    Hint · Skip the def on the way down, then come back for the call.

  2. 2

    Write three functions where the first calls the second and the second calls the third, with a print in each.

    Hint · Indent each message a little further and the output draws the nesting for you.

  3. 3

    Put a print after a return and check whether it ever appears.

    Hint · It cannot. return ends the function before that line is reached.

  4. 4

    Trace thrice(4) on paper, then run it and compare.

    Hint · The trick is that print(thrice(4)) has to wait for the call before it can print anything at all.

Quick Check

What does the A / C / B / D program print, in order?

Quick Check

After a function finishes, where does the program carry on from?

Quick Check

Why can a() mention b() when b is defined below it?