Functions

💡 Big Idea

A function is a named, reusable block of code that does one job. Instead of writing the same logic in five different places, you write it once inside a function and call it whenever you need it. Functions make programs shorter, easier to read, easier to fix, and easier to explain — which is why every professional programmer uses them constantly. This article covers how to define and call functions, how to pass information in using parameters, how to send results back using return, and one of the most important concepts in programming: variable scope.

1   Why Do We Need Functions?

Suppose you are writing a quiz program. At the end of each round, you need to calculate a percentage, print a grade, and print a congratulations message. You have three rounds — so that logic appears three times in your program.

Without functions, you copy and paste those lines. Then you notice a bug — a typo in the congratulations message. You fix it in one place. You forget to fix the other two. Now your program has three versions of the same logic, and one of them is wrong.

🔍 Analogy: A Light Switch

Every room in a house needs electricity, but that does not mean every room has its own power station. The power station is built once and connected everywhere. A function is your program's power station: built once, used everywhere. Fix it in one place and every room benefits automatically.

Functions also give you something harder to measure but equally valuable: the ability to give a chunk of logic a name. A well-named function makes code read almost like English:

# Without functions — what does this code do? You have to read every line.
if score / total * 100 >= 60:
    print("Pass")

# With functions — the intent is obvious immediately.
if calculate_percentage(score, total) >= 60:
    print("Pass")

The function version does not just work — it explains itself.

2   Defining and Calling a Function

There are two distinct steps with every function: defining it (writing it once) and calling it (running it whenever you need it).

2.1   The Anatomy of a Function Definition

def greet():
    print("Hello, welcome to the program!")
    print("Let's get started.")
PartWhat it is
defThe keyword that tells Python you are defining a function.
greetThe function's name — you choose this. Same rules as variable names: snake_case, descriptive.
()Parentheses — required. This is where parameters go (see Section 3). Empty parentheses mean no parameters.
:A colon, just like if and while. Signals the start of the function body.
Indented bodyThe code that runs when the function is called. Must be indented exactly one level.

2.2   Calling a Function

Defining a function does not run it. It just registers the name and the code to run later. To actually execute the function, you call it by writing its name followed by parentheses:

def greet():
    print("Hello, welcome to the program!")
    print("Let's get started.")

# Nothing has printed yet — the function is defined but not called.

greet()    # Now it runs.
greet()    # Call it again — runs a second time.
greet()    # And again.

Output:

Hello, welcome to the program!
Let's get started.
Hello, welcome to the program!
Let's get started.
Hello, welcome to the program!
Let's get started.

The same function body runs every time greet() is called. This is the payoff of writing it once.

⚠️ Define before you call

Python reads your file from top to bottom. If you call a function before defining it, Python will not know what it is yet and will raise a NameError. Always write your function definitions near the top of your file, before the code that calls them.

✅ Check Your Understanding
  1. What is the difference between defining a function and calling a function?
  2. How many times does the body of a function run when you define it? How many times does it run when you call it three times?
  3. This code has an error. What is it?

    say_hello()
    
    def say_hello():
        print("Hello!")
  4. Write a function called print_divider that prints a line of 30 dashes (-). Then call it three times.

3   Parameters and Arguments

The greet() function above always does the same thing. That is useful, but limited. What if you want to greet a specific person by name? You need a way to pass information into a function. That is what parameters are for.

🔍 Analogy: A Toaster

A toaster is a function: it does one job (toasting). But you put something in — bread, a bagel, a waffle — and the result depends on what you gave it. The slot is the parameter (a place for input to go); the bread you actually put in is the argument (the specific input for this particular use). Same toaster, different results depending on what you feed it.

3.1   Parameters vs. Arguments — The Exact Distinction

These two words are often used interchangeably in casual conversation, but they mean different things and it is worth being precise:

TermWhere it appearsWhat it isExample
ParameterIn the def line, inside the parentheses.A placeholder variable name — a slot waiting to be filled.def greet(name):name is the parameter.
ArgumentIn the function call, inside the parentheses.The actual value passed in to fill the parameter slot.greet("Alex")"Alex" is the argument.

Think of it this way: the parameter is the label on the toaster slot; the argument is the actual bread you push in.

3.2   A Function With One Parameter

def greet(name):
    print("Hello,", name + "!")
    print("Welcome to the program.")

greet("Alex")     # "Alex" is the argument
greet("Priya")    # "Priya" is the argument
greet("Sam")      # "Sam" is the argument

Output:

Hello, Alex!
Welcome to the program.
Hello, Priya!
Welcome to the program.
Hello, Sam!
Welcome to the program.

Each call passes a different argument. The parameter name takes on that value inside the function body. Same function, three different results.

3.3   Multiple Parameters

A function can take more than one parameter. Separate them with commas — both in the definition and in the call. Order matters: the first argument goes to the first parameter, the second to the second, and so on.

def describe_student(name, score):
    print(name, "scored", score, "points.")
    if score >= 60:
        print(name, "has passed.")
    else:
        print(name, "has not passed.")

describe_student("Alex", 85)
describe_student("Sam", 42)

Output:

Alex scored 85 points.
Alex has passed.
Sam scored 42 points.
Sam has not passed.

3.4   Default Parameter Values

You can give a parameter a default value — a fallback that is used when the caller does not provide that argument. Default parameters are placed at the end of the parameter list.

def greet(name, greeting="Hello"):
    print(greeting + ",", name + "!")

greet("Alex")                  # uses default greeting
greet("Priya", "Good morning") # overrides the default

Output:

Hello, Alex!
Good morning, Priya!
⚠️ Wrong number of arguments

If you define a function with two parameters and call it with only one argument (and no default is set), Python raises a TypeError: "missing 1 required positional argument". If you pass too many arguments, you get a different TypeError. The number of arguments in the call must match the number of parameters in the definition — unless a default value covers the gap.

✅ Check Your Understanding
  1. In your own words, what is the difference between a parameter and an argument?
  2. How many arguments does this call pass, and what are they? calculate_area(5, 12)
  3. Predict the output:

    def multiply(a, b):
        print(a * b)
    
    multiply(3, 7)
    multiply(10, 2)
    multiply(4, 4)
  4. Write a function called print_grade that takes two parameters — a student's name and their score — and prints: "Alex: 85/100".
  5. What happens when you call a function with more arguments than it has parameters? Make a prediction, then try it.

4   The return Statement

So far, all our functions have printed results. Printing is fine for displaying things to a user — but it is not the same as making a value available to the rest of your program. The return statement does that: it sends a value back to whoever called the function, so the result can be stored in a variable, passed to another function, or used in a calculation.

This is one of the most important distinctions in programming, and one of the most commonly confused. Let's deal with it directly.

🟠 Common Confusion: return vs. print

print() puts text on the screen. The value disappears the moment it is displayed — your program cannot use it for anything else.

return sends a value back to the caller. The caller can store it, calculate with it, pass it to another function, or use it in a condition. Nothing is displayed unless you explicitly print() the returned value.

A function that only print()s its result is like a calculator that shows the answer on a screen but has no way to send the number to another machine. A function that returns its result is like a calculator that outputs the answer as a signal that other devices can receive and use.

4.1   A Function Without return

def add(a, b):
    print(a + b)       # displays the result — but does not return it

result = add(3, 4)     # add() runs and prints 7
print(result)          # prints: None

The function printed 7 to the screen — but when we tried to store the result in a variable, we got None. That is because a function with no return statement automatically returns None (Python's way of saying "nothing"). The value 7 was displayed and then lost.

4.2   The Same Function With return

def add(a, b):
    return a + b       # sends the result back to the caller

result = add(3, 4)     # the returned value (7) is stored in result
print(result)          # prints: 7

# Now we can use the result:
doubled = add(3, 4) * 2
print(doubled)         # prints: 14

The function no longer displays anything — it just does the calculation and hands the result back. The caller decides what to do with it.

4.3   return Ends the Function

The moment Python hits a return statement, the function stops — immediately. Any lines after return are never reached:

def check_pass(score):
    if score >= 60:
        return "Pass"       # function ends here if score >= 60
    return "Fail"           # only reached if score < 60

print(check_pass(85))       # Pass
print(check_pass(40))       # Fail

This is a clean, readable pattern: check the first condition and return early if it matches; otherwise fall through to the next return. You will see this in almost every professional codebase.

4.4   Using return Values in Practice

The real power of return is that it lets functions feed into each other:

def calculate_percentage(score, total):
    return round(score / total * 100, 1)

def get_grade(percentage):
    if percentage >= 90:
        return "A"
    elif percentage >= 80:
        return "B"
    elif percentage >= 70:
        return "C"
    elif percentage >= 60:
        return "D"
    else:
        return "F"

# Using both functions together:
score = 37
total = 50

pct = calculate_percentage(score, total)   # returns 74.0
grade = get_grade(pct)                     # returns "C"

print(f"Score: {score}/{total} ({pct}%) — Grade: {grade}")

Each function does one job. The output of one becomes the input of another. This is how real programs are built — not one enormous block of code, but small, composable functions that do one thing well.

🟠 Common Confusion: "My function works but I can't use the result"

If you call a function and store the result in a variable, but that variable ends up as None, the almost certain cause is that your function uses print() instead of return. Ask yourself: "Does this function need to display something, or does it need to send a value back?" If the answer is "send a value back," use return.

✅ Check Your Understanding
  1. What is the difference between print() and return inside a function? Give a one-sentence answer for each.
  2. What does this program print? Trace it carefully — there may be a surprise.

    def double(n):
        print(n * 2)
    
    result = double(5)
    print("Result is:", result)
  3. Fix the function so the program prints Result is: 10 correctly:

    def double(n):
        print(n * 2)
    
    result = double(5)
    print("Result is:", result)
  4. Write a function called area_of_rectangle that takes width and height as parameters and returns the area. Then write code that calls it and prints: "Area: 48" (for width=6, height=8).
  5. What value does Python return from a function that has no return statement?

5   Variable Scope

Scope is the set of rules that governs where a variable can be seen and used. This is one of the most important concepts in functions — and one of the most frequent sources of bugs and confusion for beginners. Read this section carefully.

🔍 Analogy: A Whiteboard in a Room

Imagine every function has its own private room with its own whiteboard. Variables created inside that function are written on that room's whiteboard. When the function finishes, the room is locked — the whiteboard is wiped clean. Code outside the function cannot see the whiteboard inside it, and code inside the function cannot see the whiteboard in another function's room.

The hallway outside all the rooms is the global space. Variables written there can be seen from anywhere. But a variable written only inside a room cannot be seen from the hallway.

5.1   Local Scope

A variable created inside a function is called a local variable. It exists only while the function is running, and it can only be used inside that function. Once the function returns, the local variable disappears.

def calculate_bonus(salary):
    bonus = salary * 0.1       # bonus is a LOCAL variable
    return bonus

calculate_bonus(50000)
print(bonus)                   # NameError: name 'bonus' is not defined

bonus was created inside calculate_bonus(). Outside the function, it does not exist. Python raises a NameError because from the outside world, bonus was never created.

5.2   Global Scope

A variable created outside all functions is called a global variable. It can be read from anywhere in the file — including inside functions.

TAX_RATE = 0.2              # global variable — readable everywhere

def calculate_tax(price):
    tax = price * TAX_RATE  # reading TAX_RATE from global scope — this is fine
    return tax

print(calculate_tax(100))   # 20.0
print(calculate_tax(250))   # 50.0

Reading a global variable from inside a function is perfectly normal and useful — especially for constants like TAX_RATE or MAX_SCORE that should not change. The convention is to name constants in ALL_CAPS to signal that they are global and fixed.

5.3   The Crucial Rule: Functions Cannot Modify Globals (by default)

Reading a global variable is fine. But trying to change a global variable from inside a function does not work the way you might expect. Python treats any variable you assign inside a function as a new local variable — even if a global with the same name already exists:

score = 0                    # global variable

def add_points(points):
    score = score + points   # Python creates a LOCAL score — does not touch the global!
    print("Inside function, score:", score)

add_points(10)
print("Outside function, score:", score)    # still 0 — unchanged!
🟠 Common Confusion: "I changed the variable inside the function but it didn't change outside!"

This is one of the most common beginner bugs. When you assign to a variable inside a function, Python creates a brand-new local variable with that name — it does not touch the global. The global and the local are completely separate, even though they have the same name.

The fix is almost never to use the global keyword (which is an advanced feature with downsides). The correct fix is to use return: have the function calculate the new value and return it, then update the variable outside:

score = 0

def add_points(current_score, points):
    return current_score + points     # return the new value

score = add_points(score, 10)         # update the global by reassigning it
print(score)                          # 10 — correct!

5.4   Local Variables in Different Functions Are Separate

Two different functions can both use a variable named result or total — they are completely independent. Each function has its own private scope:

def calculate_area(width, height):
    result = width * height     # this "result" belongs to calculate_area
    return result

def calculate_perimeter(width, height):
    result = 2 * (width + height)  # this "result" is a DIFFERENT variable
    return result

area = calculate_area(4, 6)           # 24
perimeter = calculate_perimeter(4, 6) # 20
print(area, perimeter)

Both functions use a variable called result. They do not interfere with each other at all — each function's result lives in its own private scope.

5.5   A Visual Summary of Scope

Variable typeWhere it is createdWhere it can be readWhere it can be modified
LocalInside a function.Only inside that function.Only inside that function. Disappears when the function returns.
GlobalOutside all functions, at the top level of the file.Anywhere in the file, including inside functions.Safely only outside functions. Inside a function, assigning creates a new local instead.
💜 The golden rule of scope

Functions should get their input through parameters and send their output back through return. They should not silently reach out and read or modify global variables (except named constants). A function that follows this rule is self-contained — you can understand it completely by reading only its own code, without hunting through the rest of the program.

✅ Check Your Understanding
  1. What is a local variable? What happens to it when the function returns?
  2. What is a global variable? Can a function read a global variable?
  3. This program has a scope bug. Identify it and explain what is actually happening:

    total = 100
    
    def apply_discount(amount):
        total = total - amount
        print("New total:", total)
    
    apply_discount(20)
    print("Final total:", total)
  4. Fix the program above so that total is correctly updated. Use return — not the global keyword.
  5. Two functions both have a local variable called count. Can they interfere with each other? Explain why or why not.

6   Putting It All Together

Here is a complete program that uses everything from this article: well-named functions, parameters, return values, and clean scope. Read it carefully — notice how each function does exactly one job, takes its input through parameters, and sends its result back through return.

PASSING_SCORE = 60           # global constant — readable everywhere

def calculate_percentage(score, total):
    """Returns score as a percentage of total, rounded to 1 decimal place."""
    return round(score / total * 100, 1)

def get_grade(percentage):
    """Returns a letter grade for a given percentage."""
    if percentage >= 90:
        return "A"
    elif percentage >= 80:
        return "B"
    elif percentage >= 70:
        return "C"
    elif percentage >= 60:
        return "D"
    else:
        return "F"

def is_passing(percentage):
    """Returns True if the percentage meets the passing threshold."""
    return percentage >= PASSING_SCORE

def print_report(name, score, total):
    """Prints a formatted report card line for one student."""
    pct   = calculate_percentage(score, total)
    grade = get_grade(pct)
    status = "PASS" if is_passing(pct) else "FAIL"
    print(f"  {name:<12} {score}/{total}  ({pct}%)  Grade: {grade}  [{status}]")

# ── Main program ──────────────────────────────────────────────────
students = [
    ("Alice",  43, 50),
    ("Bob",    28, 50),
    ("Carol",  47, 50),
    ("Dan",    31, 50),
]

print("=== End of Term Report ===")
for name, score, total in students:
    print_report(name, score, total)

What to notice:

  • Each function has a clear, single purpose — the name describes exactly what it does.
  • Every function gets its inputs through parameters and sends results back through return. None of them modify global variables.
  • PASSING_SCORE is a global constant — named in ALL_CAPS, read inside is_passing(), never modified.
  • print_report() calls three other functions and uses their return values — this is functions feeding into each other.
  • The main program loop is clean and short because all the logic lives in the functions.

7   Common Mistakes to Watch For

MistakeWhat it looks likeWhat actually happensFix
Calling before defininggreet() appears before def greet():NameError — Python does not know the name yet.Move all def statements above the code that calls them.
Using print instead of returnFunction prints a result; caller stores it in a variable.Variable is None. Displayed value cannot be reused.Replace print(result) inside the function with return result.
Forgetting to use the return valuecalculate_tax(price) called but result not stored.Function runs, calculates correctly, result disappears.Write tax = calculate_tax(price) to capture the result.
Trying to modify a global inside a functionscore = score + 10 inside a function expecting to update the global.Creates a new local score; global is unchanged. May also raise UnboundLocalError.Pass the value in as a parameter, update it with return, and reassign outside.
Wrong number of argumentsdef add(a, b): called as add(5)TypeError: missing 1 required positional argumentMatch the number of arguments to the number of parameters, or add a default value.
Expecting a local variable to exist outside the functionUsing a variable after the function returns, expecting it still to exist.NameError — local variables are destroyed when the function ends.Use return to send the value out; store it in a variable outside the function.

8   Final Check: The Big Picture

💡 Big Idea

A function is a named, reusable block of code. You define it once with def and call it whenever you need it. Parameters are the placeholders in the definition; arguments are the actual values you pass when calling. return sends a value back to the caller — it is not the same as print(). Local variables exist only inside their function and disappear when it returns; global variables exist outside all functions. Functions should take input through parameters and send output through return — this keeps them self-contained, readable, and easy to fix.

✅ Final Check Your Understanding
  1. Explain in plain English what a function is and why it is more useful than copying and pasting code.
  2. Trace this program completely. Write down exactly what is printed and in what order.

    def square(n):
        return n * n
    
    def cube(n):
        return n * n * n
    
    x = 3
    print(square(x))
    print(cube(x))
    print(square(x) + cube(x))
  3. This program is supposed to print The total is: 150 but it prints The total is: None. Find the bug and fix it.

    def add_all(a, b, c):
        total = a + b + c
        print(total)
    
    result = add_all(40, 60, 50)
    print("The total is:", result)
  4. Explain what will happen when this code runs. What error occurs and why?

    def calculate(x):
        answer = x * 10
        return answer
    
    calculate(5)
    print(answer)
  5. Write a complete program with two functions:

    • celsius_to_fahrenheit(c) — takes a Celsius temperature and returns the Fahrenheit equivalent. (Formula: F = C * 9/5 + 32)
    • describe_temperature(f) — takes a Fahrenheit value and returns a string: "Freezing" if below 32, "Cold" if below 60, "Warm" if below 85, and "Hot" otherwise.

    Then write a main section that asks the user for a Celsius temperature, converts it, and prints both the Fahrenheit value and the description.

9   Quick-Reference Summary

ConceptSyntax / ExampleKey rule
Define a functiondef my_function():Registers the name and body. Does not run the code yet.
Call a functionmy_function()Runs the body. Must be called after it is defined.
Parameterdef greet(name):Placeholder in the definition. A slot waiting for a value.
Argumentgreet("Alex")The actual value passed in at call time.
Default parameterdef greet(name, greeting="Hello"):Used when the caller does not provide that argument.
returnreturn resultSends a value back to the caller. Ends the function immediately.
No return(function ends without return)Function automatically returns None.
Local variableCreated inside a function.Exists only during that function call. Invisible outside.
Global variableCreated outside all functions.Readable everywhere. Assigning inside a function creates a new local instead.
Global constantMAX_SCORE = 100Convention: ALL_CAPS. Read inside functions, never modified.