MGU Computer Science Series — Programming with Python
Part III: Functions and Program Design
Scope · Local Variables · Global Variables · State · Side Effects
Chapter 13 of 30
13
MGU Computer Science Series  ·  Programming with Python  ·  Library Textbook Edition

Chapter 13:
Scope, Local Variables,
and Program State

Where variables live, how long they last, and why functions that communicate through parameters and return values are easier to trust than functions that reach into hidden shared state

■ Part III of VII ■

■ 10 Sections  •  Chapter 13 of 30 ■

Builds on Chapters 11 and 12 — function definitions, calls, parameters, and return values

Chapter Introduction and Learning Objectives

When you write a function, the variables inside it do not automatically connect to variables outside it. Python keeps them separate on purpose. This separation is called scope, and understanding it will save you hours of confusion and debugging time.

This chapter explains how scope works, why it exists, what happens to variables when a function finishes running, what global variables are and why you should use them carefully, and how to design functions that communicate clearly through parameters and return values rather than through hidden shared variables.

Opening Question

Two different functions in the same program both use a variable named total. Does changing total inside one function change the total inside the other? Does it change a total that exists in the main program? The answer depends entirely on scope — and getting the answer wrong is one of the most common sources of confusing bugs for new programmers. By the end of this chapter, you will be able to answer this question correctly every time, and you will know how to design functions that avoid the confusion in the first place.

10Sections
6Objectives
10Key Terms
30Total Chapters

Learning Objectives

By the end of this chapter, you should be able to:

  1. 1Explain what scope is and why Python uses it.
  2. 2Identify whether a variable is local or global in a given program.
  3. 3Trace the lifetime of a local variable through a function call.
  4. 4Describe what a side effect is and recognize when a function produces one.
  5. 5Design functions that communicate cleanly through parameters and return values.
  6. 6Find and fix common scope-related bugs, including NameError and UnboundLocalError.

Sections at a Glance

Sections 13.1 – 13.3
The Foundation: Why Scope Exists, Local Variables, and Lifetime
Private Working Spaces · Creation and Destruction

Why Scope Matters

Imagine you are working at a large company. Every department has its own filing cabinet. A folder labeled "report" in the accounting department is a completely different folder from one labeled "report" in the marketing department. Neither department accidentally reads or changes the other's files. The same name in two places refers to two different things.

Python uses this same idea for variables inside functions. When you write a variable inside a function, Python treats it as belonging to that function. A variable named total inside one function is not the same variable as total inside a different function, and it is not the same as a total that might exist in the main body of your program.

This system is called scope. Scope answers the question: which part of the program can see and use this variable?

Why Scope Is Not Just a Technicality

New programmers sometimes treat scope as a strange rule to memorize. But scope exists for a very practical reason: it prevents functions from accidentally interfering with each other.

Think about what would happen without scope. If every variable in every function were shared, then calling one function might accidentally overwrite a variable that another function was relying on. Tracking down that kind of bug — where two completely unrelated functions share a name by accident — would be a nightmare. Programs would become fragile and unpredictable.

Scope gives every function a private working space. You can use any variable name you want inside your function without worrying about whether you are accidentally changing something in another function or in the main program.

A First Example

Read this program carefully:

def greet_user(): message = "Hello, welcome to the program." print(message) greet_user() print(message) # What happens here?

When you run this program, Python prints the greeting. But then the second print(message) causes an error:

NameError: name 'message' is not defined

The variable message was created inside greet_user. It belongs to that function. Once the function finishes, message is gone. The main program cannot see it at all.

This is scope working exactly as intended.

Study Note

An error like this is not a sign that something is broken. It is Python correctly enforcing a boundary. Learning to read this kind of error and recognize it instantly as a scope issue — rather than a mystery — is one of the most useful skills you will build in this chapter.

Local Variables

A local variable is a variable that is created inside a function. It belongs to that function and only exists while that function is running.

Here is how to recognize a local variable: if you see an assignment statement inside a function definition — that is, a line with = that creates a new name — that variable is local to that function.

def calculate_discount(price, rate): discount_amount = price * rate # local variable final_price = price - discount_amount # local variable return final_price

In this function, discount_amount and final_price are both local variables. They are created when calculate_discount is called and they cease to exist when the function returns.

The parameters price and rate are also local. Parameters behave exactly like local variables — they are created when the function call happens and they disappear when the function ends. You will read more about this in Section 13.4.

Local Variables Are Invisible Outside Their Function

You cannot access a local variable from outside the function that owns it. Attempting to do so raises a NameError.

def make_greeting(name): greeting = "Good morning, " + name + "!" return greeting result = make_greeting("Priya") print(result) # Works fine: prints "Good morning, Priya!" print(greeting) # NameError: name 'greeting' is not defined

The variable greeting is local to make_greeting. After the function returns, it does not exist anywhere you can reach.

Two Functions Can Use the Same Variable Name

Because local variables belong to their own functions, two different functions can use the same variable name without any conflict. They are completely separate variables.

def calculate_area(length, width): result = length * width return result def calculate_perimeter(length, width): result = 2 * (length + width) return result area = calculate_area(5, 3) perimeter = calculate_perimeter(5, 3) print(area) # 15 print(perimeter) # 16

Both functions use a variable named result. These are two completely different variables. Neither function knows or cares that the other one also uses the name result. Each result lives in its own private space.

This is one of the great advantages of functions and scope: you can name your variables clearly and naturally without constantly worrying about name collisions.

Variable Lifetime

A variable's lifetime is the period during which it exists in memory. For local variables, the lifetime is short and well-defined:

  • A local variable is created when the function is called and execution reaches the line that assigns it.
  • A local variable is destroyed when the function returns.

Each time you call a function, its local variables are created fresh. They do not carry values from a previous call.

Each Call Is Independent

def count_up(): counter = 0 counter = counter + 1 print(counter) count_up() # prints 1 count_up() # prints 1 count_up() # prints 1

Every time count_up is called, a fresh counter variable is created, set to 0, incremented to 1, and printed. The counter does not accumulate across calls because the variable is destroyed when the function returns.

If you want a value to persist between function calls, you need a different approach — such as passing values through parameters and return values, or (carefully) using a global variable. You will learn about global variables in Section 13.5.

Tracing the Lifetime of a Local Variable

It helps to trace lifetime explicitly. Consider this program:

def square(n): result = n * n return result x = square(4) y = square(7) print(x, y)

Here is what happens step by step:

StepEventVariables That Exist
1Program starts(none yet)
2square(4) is calledn = 4 created
3result = 4 * 4 executesn = 4, result = 16
4return result executes; function endsn and result are destroyed
5x = 16 in the main programx = 16
6square(7) is calledn = 7 created (new copy)
7result = 7 * 7 executesn = 7, result = 49
8return result executes; function endsn and result are destroyed
9y = 49 in the main programx = 16, y = 49
10print(x, y) executesprints 16 49

Notice that n and result are created twice — once for each call — and destroyed twice. They never accumulate. The main program's variables x and y persist because they live outside any function.

Sections 13.4 – 13.6
Parameters, Globals, and State
Local Copies · The global Keyword · What "State" Means

Parameters as Local Variables

This section makes a point that is easy to overlook: parameters behave exactly like local variables.

When you call a function, Python creates local variables for each parameter and assigns them the values you passed as arguments. From that moment on, the parameter is just a local variable with an initial value.

def double(number): number = number * 2 return number x = 5 result = double(x) print(x) # still 5 print(result) # 10

When double(x) is called:

Step-by-Step
What happens when double(x) runs
  • Step 1Python creates a local variable called number and sets it to 5 (the value of x).
  • Step 2Inside the function, number = number * 2 changes the local number to 10.
  • Step 3The function returns 10.
  • Step 4The local variable number is destroyed.
  • Step 5The variable x in the main program was never touched. It is still 5.
Critical Point

Assigning a new value to a parameter inside a function does not change the original variable that was passed in. The parameter is a local copy.

Verify Your Understanding

Read this program and predict the output before continuing.

def adjust(value, amount): value = value + amount print("Inside function:", value) score = 80 adjust(score, 10) print("Outside function:", score)
Inside function: 90 Outside function: 80

The function prints 90 because it added 10 to its local copy of value. But score in the main program never changed — it is still 80. The assignment inside the function affected only the local variable value, not the variable score that was passed in.

Global Variables at an Introductory Level

A global variable is a variable that is created in the main body of a program — outside of any function. It exists for the entire lifetime of the program.

program_name = "Grade Tracker" # global variable def show_title(): print("Welcome to", program_name) show_title()

Here program_name is created outside any function, so it is global. The function show_title can read it.

Functions Can Read Global Variables

By default, a function can read a global variable. Reading means looking up its value without changing it.

tax_rate = 0.08 def calculate_total(price): tax = price * tax_rate return price + tax print(calculate_total(50)) # 54.0 print(calculate_total(100)) # 108.0

The function calculate_total reads tax_rate from the global space. This works without any special instruction.

Modifying a Global Variable Requires the global Keyword

If a function tries to assign a new value to a global variable without special permission, Python creates a new local variable with that name instead. This is a very common source of confusion.

count = 0 def increment(): count = count + 1 # This causes an error increment()

Running this produces:

UnboundLocalError: local variable 'count' referenced before assignment

Python saw the assignment count = count + 1 inside the function and decided count must be a local variable. But then it tried to read count on the right side of = before any local count had been given a value. That is a contradiction, and Python raises an error.

To allow a function to modify a global variable, you use the global keyword:

count = 0 def increment(): global count count = count + 1 increment() increment() increment() print(count) # 3

The line global count tells Python: "When I use the name count in this function, I mean the global variable count, not a new local one."

Be Careful with Global Variables

The global keyword works, but it should be used with caution. Here is why.

When a function modifies a global variable, it creates a hidden connection between the function and the rest of the program. This hidden connection is called a side effect — you will read about side effects in Section 13.7.

Hidden connections make programs harder to understand and debug. If something goes wrong with count, you have to check every function that uses global count to find the problem. As programs grow, this becomes very difficult.

The better design is almost always to pass values in through parameters and receive values back through return values. Use global variables for configuration values or constants that never change — not for values that functions modify.

# Better design: no global needed def increment(count): return count + 1 count = 0 count = increment(count) count = increment(count) count = increment(count) print(count) # 3

This version is clearer. The function does not reach into global space. You can read count = increment(count) and immediately understand that count is being updated.

Common Mistake

Writing global count inside every function as a habit, just to avoid an error. The presence of many global declarations scattered across a program is usually a sign that the program's design should be reconsidered — not a problem to be patched with more global statements.

Program State

Program state is the collection of all variable values at a specific moment during execution. Every variable that exists, everywhere in the program, contributes to the program's state.

Understanding state is important because bugs are almost always a matter of state. When your program produces the wrong output, it is because some variable somewhere has the wrong value. Debugging means finding which variable has the wrong value and figuring out how it got there.

State Changes Over Time

State is not fixed. It changes every time an assignment statement executes.

balance = 500 def deposit(amount): global balance balance = balance + amount def withdraw(amount): global balance balance = balance - amount deposit(200) withdraw(75) print(balance) # 625

The state of balance changes three times: once when it is created, once after deposit, and once after withdraw. At any moment during execution, the state of the program includes the current value of balance plus anything else that exists.

Local State and Global State

Local variables contribute to the local state of the currently running function. Once the function returns, that local state disappears. Global variables contribute to the global state, which persists across all function calls.

When you have a bug, it helps to ask: "Which piece of state is wrong? Is it a local variable inside a function? Is it a global variable? Is it a value that was passed through a parameter?"

Debugging Tip

When tracking down a bug, sketch the program's state at each step on paper — a quick table of variable names and values is often faster than re-reading the code from the top. This habit, introduced fully in Chapter 10, becomes even more valuable once functions and scope are involved.

Sections 13.7 – 13.8
Designing Clean Functions
Side Effects · Parameters In, Return Values Out

Side Effects

A side effect is any change that a function makes to something outside its own local scope. If a function changes a global variable, prints output, writes to a file, or modifies a list that was passed in, those are side effects.

Side effects are not always bad. Printing output is a side effect, and printing output is often exactly what you want. The important thing is to know when your functions have side effects and to design them thoughtfully.

Functions with Side Effects

log = [] # global list def record(entry): log.append(entry) # side effect: modifies global list record("User logged in") record("File opened") record("File closed") print(log)
['User logged in', 'File opened', 'File closed']

The function record has a side effect: it modifies the global list log. This is a reasonable design for a logging system, but it is worth knowing that the function is doing more than just computing a value — it is changing shared state.

Side Effects Can Make Programs Hard to Trace

The danger of side effects is that they create invisible connections between different parts of your program. A function that only takes parameters and returns values is easy to test: call it with specific inputs and check the output. A function that also modifies global state is harder to test because you also need to check what it did to the global state.

Compare these two versions of the same idea:

# Version A — side effect via global variable total = 0 def add_score(points): global total total = total + points add_score(10) add_score(25) print(total) # 35
# Version B — no side effect, uses parameter and return value def add_score(total, points): return total + points total = 0 total = add_score(total, 10) total = add_score(total, 25) print(total) # 35

Both produce the same result. But Version B is easier to understand, test, and reuse. The function add_score in Version B has no side effects — you give it two values and it gives you one value back. You can test it independently, use it in different programs, and reason about it without thinking about global state.

When Side Effects Are Acceptable

Printing is a side effect, and it is common and intentional. Functions like greet_user() or display_menu() exist specifically to produce output. That is their job, and producing output is a perfectly reasonable side effect.

Side effects on global data structures like lists and dictionaries can also be reasonable in carefully controlled situations. You will learn more about this when you study lists and objects in later chapters.

Guideline

If your function is computing a value, prefer returning that value over storing it in a global variable. Save global state for things that genuinely need to persist across many function calls and cannot easily be passed through parameters.

Functions with Clear Inputs and Outputs

The clearest functions are those that follow a simple pattern:

  • They receive all the information they need through parameters.
  • They compute or transform that information.
  • They send results back through a return value.
  • They do not read or modify global variables unnecessarily.
  • Their behavior depends only on their inputs, not on hidden state elsewhere.

A function like this is sometimes called a pure function — though you do not need to memorize that term right now. The key idea is that the function's behavior is predictable and testable.

Example: Comparing Two Designs

Suppose you want to compute the letter grade for a numeric score.

# Design A — using a global variable score = 0 # global def get_grade(): if score >= 90: return "A" elif score >= 80: return "B" elif score >= 70: return "C" elif score >= 60: return "D" else: return "F" score = 85 print(get_grade()) # B

This works, but get_grade depends on a global variable. If you want to test it with a different score, you have to change the global variable first. If another part of your program also uses a global named score, you might have a conflict.

# Design B — using a parameter def get_grade(score): if score >= 90: return "A" elif score >= 80: return "B" elif score >= 70: return "C" elif score >= 60: return "D" else: return "F" print(get_grade(85)) # B print(get_grade(92)) # A print(get_grade(55)) # F

Design B is better. The function clearly states what it needs (a score), clearly states what it produces (a grade letter), and does not reach into global space. You can call it with any score at any time without worrying about global state.

Constants Are a Reasonable Exception

One acceptable use of global variables is for constants: values that are set once at the top of a program and never changed. Constants make sense as globals because they are not being modified — they are just shared reference values.

MAX_SCORE = 100 PASSING_SCORE = 60 PROGRAM_NAME = "Grade Tracker" def is_passing(score): return score >= PASSING_SCORE def show_header(): print("=== " + PROGRAM_NAME + " ===")

Here MAX_SCORE, PASSING_SCORE, and PROGRAM_NAME are global constants. The functions read them but do not modify them. This is a clean and readable pattern. By convention, Python programmers write constants in ALL_CAPS to signal that they should not be changed.

Design Principle

Receive information through parameters. Compute a result. Return that result. Reserve global variables for constants or for program-wide state that genuinely cannot be passed through parameters.

Sections 13.9 – 13.10
Putting It Into Practice
Tracing Multiple Scopes · Recognizing and Fixing Common Errors

Tracing Scope

The best way to build a solid understanding of scope is to trace programs carefully, paying attention to which variables exist where and when.

How to Trace Scope

When tracing a program that involves functions, maintain separate columns for the main program scope and for each function call scope. Create a function column when the function is called, and mark it as finished when the function returns.

Worked Tracing Example

Trace this program:

def compute(x): y = x * 2 z = y + 1 return z a = 5 b = compute(a) c = compute(10) print(a, b, c)

Main program scope:

VariableValue at Each Step
a5 (from line a = 5)
b11 (after compute(a) returns)
c21 (after compute(10) returns)

First call to compute(a) — i.e., compute(5):

VariableCreatedValueDestroyed
xwhen called5when function returns
yafter y = x * 210when function returns
zafter z = y + 111when function returns

Returns: 11

Second call to compute(10):

VariableCreatedValueDestroyed
xwhen called10when function returns
yafter y = x * 220when function returns
zafter z = y + 121when function returns

Returns: 21

Final output: 5 11 21

Notice that a stays 5 throughout. The function call compute(a) passes the value 5 — it does not give the function access to the variable a itself.

A More Complex Tracing Example

Trace this program:

total = 0 def add(amount): global total total = total + amount return total result1 = add(10) result2 = add(25) result3 = add(5) print(total, result1, result2, result3)

Scope trace:

StepEventtotal (global)result1result2result3
Starttotal = 00———
Call add(10)amount = 10, total = 0 + 10 = 1010———
Return 10result1 = 101010——
Call add(25)amount = 25, total = 10 + 25 = 353510——
Return 35result2 = 35351035—
Call add(5)amount = 5, total = 35 + 5 = 40401035—
Return 40result3 = 4040103540

Output: 40 10 35 40

Common Scope Errors

Scope errors are some of the most confusing errors for new programmers because the error message often points to a line that looks correct. Understanding what Python is actually telling you will help you fix these bugs quickly.

Error Type 1 — NameError: Using a Local Variable Outside Its Function

def setup(): username = "admin" setup() print(username) # NameError: name 'username' is not defined

What happened: username was created inside setup and belongs to that function. Once setup returns, username no longer exists.

Fix: Return the value and capture it in the main program.

def setup(): username = "admin" return username username = setup() print(username) # admin

Error Type 2 — UnboundLocalError: Assigning to a Global Without global

points = 0 def earn_points(n): points = points + n # UnboundLocalError earn_points(10)

What happened: Python saw the assignment points = ... inside earn_points and decided points must be a local variable. But then it tried to read points on the right side before any local points had been assigned a value.

Fix option A: Use the global keyword.

points = 0 def earn_points(n): global points points = points + n earn_points(10) print(points) # 10

Fix option B (usually better): Pass the value through parameters and return values.

def earn_points(points, n): return points + n points = 0 points = earn_points(points, 10) print(points) # 10

Error Type 3 — Expecting a Parameter Change to Affect the Caller

def reset(value): value = 0 score = 100 reset(score) print(score) # 100, not 0

What happened: value is a local variable (a copy of score). Assigning 0 to value changes the local copy. The variable score in the caller is untouched.

Fix: Return the new value and assign it in the caller.

def reset(value): return 0 score = 100 score = reset(score) print(score) # 0

Error Type 4 — Accidentally Relying on a Global Variable That Changes

multiplier = 2 def scale(value): return value * multiplier print(scale(5)) # 10 multiplier = 10 print(scale(5)) # 50, not 10!

What happened: scale reads multiplier from global scope. When multiplier changed, scale started producing different results — even though the function itself was never modified. This makes the function unpredictable.

Fix: Pass the multiplier as a parameter.

def scale(value, multiplier): return value * multiplier print(scale(5, 2)) # 10 print(scale(5, 10)) # 50

Now the function's behavior depends only on its inputs, not on a hidden global.

Error Type 5 — Confusing Same-Named Variables in Different Scopes

def process(): result = "inside function" print(result) result = "main program" process() print(result)
inside function main program

What happened: There are two completely separate variables both named result. The one inside process is local and exists only during the function call. The one in the main program is in the global scope and is unaffected by the function. This is not a bug — it is scope working correctly — but it can be confusing if you are not expecting it.

Recommendation

When possible, avoid using the same variable name in the main program and inside functions for different things. Clear naming reduces confusion even when scope technically keeps them separate.

Chapter Summary

Key Terms

scope
The region of a program where a variable can be seen and used. Variables in Python have either local scope (inside a function) or global scope (the main program body).
local variable
A variable created inside a function. It belongs to that function and cannot be accessed from outside it.
global variable
A variable created in the main body of a program, outside any function. It is accessible throughout the program.
variable lifetime
The period during which a variable exists in memory. Local variables exist only while their function is running. Global variables exist for the entire program run.
program state
The complete collection of all variable values at a given moment during program execution.
side effect
A change that a function makes to something outside its own local scope, such as modifying a global variable, printing output, or modifying a shared data structure.
global keyword
The Python keyword global, used inside a function to declare that a name refers to a global variable rather than a new local variable.
constant
A variable whose value is set once and not changed during the program. By convention, constants are written in ALL_CAPS.
UnboundLocalError
An error that occurs when Python treats a name as local (because it appears on the left side of an assignment) but the code tries to read the variable before it has been assigned a local value.
NameError
An error that occurs when code tries to use a name that does not exist in the current scope.

Code Reading

For each program, explain in your own words what each line does and identify the scope of every variable before predicting the output.

  1. def shout(message): loud = message.upper() + "!!!" return loud phrase = "we did it" result = shout(phrase) print(result) print(phrase)
  2. city = "Springfield" def describe_location(): return "Welcome to " + city print(describe_location())
  3. def shrink(n): n = n - 1 return n level = 5 new_level = shrink(level) print(level, new_level)

Concept Check

Answer these questions before beginning the full review questions. They check basic understanding, not deep analysis.

  1. In your own words, what is scope?
  2. What is the difference between a local variable and a global variable?
  3. What happens to a local variable when its function finishes running?
  4. Why does assigning a new value to a parameter inside a function not change the variable that was passed in?
  5. What does the global keyword do, and why must it be used carefully?
  6. Give one example of a side effect that is intentional and reasonable.

Common Misconceptions

Misconception 1
"If I change a parameter inside a function, the original variable changes too."
A parameter is a local variable that starts out holding a copy of the value you passed in. Reassigning the parameter with = changes only that local copy. The caller's original variable is completely unaffected. (Note: this is different from modifying a mutable object like a list through its methods, which you will explore in later chapters — but simple reassignment, as covered here, never affects the caller.)
Misconception 2
"Once I write 'global x' in one function, every function can now treat x as global."
The global declaration is local to the function it appears in. If you want a second function to also modify the same global variable, that function needs its own global statement. Each function's scope rules are independent.
Misconception 3
"Using global variables is always bad practice and should never be done."
Global variables are a completely reasonable choice for constants — values set once and never changed, like MAX_SCORE or PROGRAM_NAME. The caution in this chapter is specifically about global variables that functions modify, because that creates hidden dependencies that are hard to track. Reading a global constant is not a problem at all.
Misconception 4
"A NameError and an UnboundLocalError are basically the same thing."
A NameError happens when a name does not exist anywhere in the current scope — for example, trying to use a local variable from a function that already returned. An UnboundLocalError happens specifically when Python has already decided a name is local (because it is assigned somewhere in the function) but the code tries to read that name before any local assignment has actually happened. The fixes are related but the causes are different, and recognizing which one you are looking at speeds up debugging.
PracticeScope Tracing Practice

For each program, trace the program's execution. Create a table showing variable values in each scope — main program and each function call. State the final output.

  1. def multiply(a, b): product = a * b return product x = 6 y = 7 z = multiply(x, y) print(x, y, z)
  2. def adjust(n): n = n + 10 return n value = 5 result = adjust(value) print(value) print(result)
  3. def first_step(n): n = n * 2 return n def second_step(n): n = n - 3 return n start = 10 a = first_step(start) b = second_step(a) c = second_step(start) print(start, a, b, c)
  4. Trace this program carefully. Where does each variable live? What is the output?
    label = "Score" def format_score(score): output = label + ": " + str(score) return output message = format_score(88) print(message) print(label)
  5. This program uses a global variable that is modified by a function. Trace the value of balance at each step.
    balance = 1000 def apply_fee(amount): global balance balance = balance - amount apply_fee(25) apply_fee(10) apply_fee(50) print(balance)
PracticeLocal and Global Variable Questions

Answer these questions based on careful reading. Do not run the programs — reason through them first, then verify.

  1. In the following program, which variables are local and which are global? List each variable name and its scope.
    discount_rate = 0.15 def apply_discount(price): savings = price * discount_rate final = price - savings return final original = 80 result = apply_discount(original) print(result)
  2. What is the output of this program? Explain why count in the main program does not change.
    def try_to_change(count): count = 0 count = 99 try_to_change(count) print(count)
  3. This program has a bug. Identify it and explain what kind of error it produces and why.
    def calculate(): answer = 42 calculate() print(answer)
  4. Rewrite the following program so that get_discount does not use a global variable. Use a parameter instead.
    rate = 0.10 def get_discount(price): return price * rate print(get_discount(200))
  5. In this program, two functions both use a variable named temp. Will they interfere with each other? Explain why or why not.
    def convert_to_fahrenheit(celsius): temp = celsius * 9 / 5 + 32 return temp def convert_to_kelvin(celsius): temp = celsius + 273.15 return temp print(convert_to_fahrenheit(100)) print(convert_to_kelvin(100))
PracticeSide Effect Analysis

For each function, identify whether it has side effects. If it does, describe what the side effect is. Then suggest an improved version without the side effect, if improvement is appropriate.

  1. Function A
    output_lines = [] def save_line(text): output_lines.append(text)
  2. Function B
    def show_result(value): print("The result is:", value)
  3. Function C
    running_total = 0 def add_to_total(amount): global running_total running_total = running_total + amount
  4. Function D
    def calculate_tax(price, rate): tax = price * rate return tax

    For each function above, answer: Does it have a side effect? If yes, what is it? Is the side effect intentional and reasonable, or should the function be redesigned?

PracticeDebugging Practice

Each program below contains a scope-related bug. Identify the error, explain what caused it, and write a corrected version.

  1. Bug 1
    def greet(name): greeting = "Hello, " + name + "!" greet("Sam") print(greeting)
  2. Bug 2
    high_score = 0 def update_high_score(new_score): if new_score > high_score: high_score = new_score update_high_score(150) print(high_score)
  3. Bug 3
    def halve(number): number = number / 2 original = 80 halve(original) print(original) # Student expects 40
  4. Bug 4
    def make_label(text): label = "[" + text + "]" title = make_label("Important") print(title)
  5. Bug 5
    base_score = 50 def add_bonus(bonus): base_score = base_score + bonus # intending to modify global return base_score final = add_bonus(20) print(final)
PracticeShort Programming Tasks
  1. Write a function celsius_to_fahrenheit(celsius) that takes a Celsius temperature as a parameter and returns the Fahrenheit equivalent. Do not use any global variables. Call it with at least three different values and print the results.
  2. Write a function apply_raise(salary, percent) that returns a new salary increased by the given percent. The function should not modify any variable in the caller — it should only return the new value. Demonstrate that the original salary variable in the main program is unchanged after the call.
  3. Write two functions, area_of_circle(radius) and area_of_square(side), that each use a local variable named area. Show that using the same local variable name in both functions causes no conflict by calling both and printing their results.
  4. Write a program that defines a global constant STORE_NAME and a function print_receipt_header() that reads the constant and prints a formatted header. The function should not modify the constant.
  5. Take the following program, which uses a global variable that a function modifies, and rewrite it so the function instead receives the value as a parameter and returns the updated value:
    inventory = 100 def remove_items(n): global inventory inventory = inventory - n remove_items(15) remove_items(30) print(inventory)
ReviewReview Questions

Recall and Vocabulary

  1. What is scope? Why does Python use it?
  2. What is a local variable? Give one example.
  3. What happens to a local variable when its function returns?
  4. Can two different functions use the same variable name without interfering? Explain.
  5. What is the difference between reading a global variable and modifying a global variable from inside a function?
  6. What does the global keyword do? When is it needed?
  7. What is a side effect? Give one example of a side effect that is intentional and one that could cause problems.
  8. What is program state?
  9. Why is passing values through parameters generally better than using global variables for shared data?
  10. What is a constant, and how do Python programmers signal that a variable is meant to be a constant?

Predict the Output

  1. def double(n): n = n * 2 return n x = 5 y = double(x) print(x) print(y)
  2. label = "Total" def show(amount): print(label + ":", amount) show(99) show(150) print(label)
  3. def compute(a, b): result = a + b return result def describe(result): return "The answer is " + str(result) number = compute(3, 7) message = describe(number) print(message)
  4. x = 10 def change_x(): global x x = 99 change_x() print(x)
  5. def add(a, b): total = a + b return total def multiply(a, b): total = a * b return total print(add(3, 4)) print(multiply(3, 4))
Chapter 13 Checklist
Use this to confirm your understanding before moving to Chapter 14
Concepts
  • I can explain what scope is and why Python separates variables by function.
  • I can identify whether a variable is local or global in a given program.
  • I can explain when a local variable is created and when it is destroyed.
  • I can explain why reassigning a parameter does not change the caller's variable.
  • I can explain the difference between reading and modifying a global variable.
  • I know when and why the global keyword is required.
  • I can define program state and explain how it changes over time.
  • I can define side effect and give an example of an intentional one and a risky one.
Applications
  • I can trace a program with multiple function calls and track each variable's scope.
  • I can design a function that communicates only through parameters and a return value.
  • I can identify and fix NameError and UnboundLocalError bugs caused by scope.
  • I can decide when a global constant is a reasonable design choice.
Study Habits
  • I trace local and global variables in separate columns when reading unfamiliar code.
  • I predict the output of scope-related code before running it.
  • I can explain, in plain language, why a scope-related bug occurred rather than just fixing it by trial and error.
Preview — Chapter 14
Decomposition and Reusable Code

Chapter 13 explained where variables live and how functions keep their working data separate from the rest of the program. You now understand local variables, global variables, lifetime, program state, and side effects. You have seen why functions that communicate through parameters and return values are clearer than functions that reach into global state.

Chapter 14 takes the next step: how do you break a large program into well-designed functions? As programs grow beyond a dozen lines, the real challenge is not syntax — it is organization. Chapter 14 introduces decomposition, helper functions, single responsibility, top-down design, and the practice of refactoring repeated code into reusable pieces. You will move from writing functions one at a time to designing programs with structure.

MGU Library Connection
  • Programming with Python — Appendix G: Function Design Reference
  • Programming with Python — Appendix D: Common Python Error Messages
  • Programming with Python — Appendix E: Debugging Checklist
  • Companion resource: Function Design Guide
  • Companion resource: Print-versus-Return Guide
  • Companion resource: Scope Guide
  • Previous chapters: Chapter 11 (Defining and Calling Functions), Chapter 12 (Parameters, Arguments, and Return Values)
  • Next chapter: Chapter 14 (Decomposition and Reusable Code)