Phase 1 · FoundationsModule 7~36 min read

Functions

Package logic into reusable functions with flexible arguments, scope, and lambdas.

What you'll learn

As programs grow, cramming everything into one script becomes a tangled mess. Functions let you break code into small, named, reusable pieces — the single most important skill for writing programs that stay understandable.

By the end you'll be able to:

  • Define and call functions with def
  • Use default, positional, and keyword arguments
  • Accept any number of arguments with *args and **kwargs
  • Return single or multiple values
  • Understand variable scope and the LEGB rule
  • Write quick anonymous functions with lambda

Defining functions

A function is a named block of code that performs one task. You define it once with def and call it as often as you like. It can take parameters (inputs) and hand back a result with return:

Anatomy of a function
def greet(name):
    return f"Hi, {name}"
■

def — the keyword that starts a function.

■

Name — a verb in snake_case describing what it does.

■

Parameters — the inputs it needs, in parentheses.

■

return — hands a value back (optional).

functions.py
def greet(name):
    return f"Hello, {name}!"

def square(n):
    return n * n

print(greet("Sara"))
print(square(5))

Tip

A good function does one thing and its name says exactly what — calculate_tax, is_valid_email, send_receipt. If you need "and" to describe it, split it.

Arguments: default, positional & keyword

Parameters can have default values, making them optional. And when calling, you can pass arguments by position or by keyword (naming them) — keywords make calls self-documenting and order-independent:

arguments.py
def greet(name, greeting="Hello"):     # greeting has a default value
    return f"{greeting}, {name}!"

print(greet("Sara"))                        # Hello, Sara!
print(greet("Omar", "Welcome"))             # Welcome, Omar!
print(greet(name="Aisha", greeting="Hi"))   # keyword arguments

A classic gotcha

Never use a mutable default like def f(items=[]) — that list is created once and shared across all calls, causing surprising bugs. Use def f(items=None) and create a fresh list inside instead.

*args & **kwargs

Sometimes you don't know how many arguments there'll be. *args collects any extra positional arguments into a tuple, and **kwargs collects any extra keyword arguments into a dictionary:

flexible.py
def total(*numbers):          # *args: any number of positional args
    return sum(numbers)

def describe(**info):         # **kwargs: any number of keyword args
    for key, value in info.items():
        print(f"{key}: {value}")

print(total(1, 2, 3, 4))      # 10
describe(name="Sara", age=25)

Return values

return sends a value back to the caller (a function with no return gives back None). A lovely Python feature: you can return several values at once as a tuple, and unpack them into separate names:

returns.py
def min_max(numbers):
    return min(numbers), max(numbers)   # returns a tuple

low, high = min_max([4, 8, 1, 9, 3])    # unpack it into two names
print(low, high)

Scope & the LEGB rule

A variable created inside a function is local — it exists only there. When Python encounters a name, it searches scopes in a fixed order, remembered as LEGB:

How Python resolves a name
LLocalNames defined inside the current function
EEnclosingNames in any outer (enclosing) function
GGlobalNames defined at the top level of the module
BBuilt-inPython's built-ins like print, len, range

Functions can freely read outer variables, but to reassign a global one you must declare global (or nonlocal for an enclosing function's variable):

scope.py
count = 0            # a global variable

def increment():
    global count     # needed to reassign the global
    count += 1

increment()
increment()
print(count)         # 2

Note

In practice, prefer passing values in as arguments and returning results out, rather than reaching for global. It keeps functions self-contained and easy to test.

Lambda functions

A lambda is a tiny, anonymous, one-expression function. You'll rarely name one — their real power is being passed inline as an argument, most commonly as a key for sorting:

lambda.py
square = lambda x: x * x
print(square(5))              # 25

# lambdas shine as arguments — e.g. sorting by a key
words = ["banana", "kiwi", "apple"]
words.sort(key=lambda w: len(w))
print(words)                  # sorted by length

Tip

Keep lambdas to a single, simple expression. If the logic grows or you'd reuse it, write a proper def function with a name — it's clearer.

Recap & quick check

Key takeaways

  • Define functions with def; they take parameters and return a value (or None).
  • Parameters can have defaults; arguments can be passed by position or keyword.
  • *args gathers extra positional args (a tuple); **kwargs gathers extra keyword args (a dict).
  • Return multiple values as a tuple and unpack them; local variables live only inside the function.
  • Python resolves names by LEGB (Local, Enclosing, Global, Built-in); use global to reassign a global.

Quick check

1. Which keyword defines a function in Python?

2. What does a function return if it has no return statement?

3. What does *args collect?

4. How can a function return two values at once?

5. In what order does Python look up a name (LEGB)?

Superb — you've completed Phase 1! You can store data, make decisions, repeat work, and package code into functions. Next you'll dive into Phase 2 — Data Structures, starting with lists.