Chapter 144·Beginner·9 min read
Functions in Python
How to bundle logic into reusable, named blocks — defining functions with def, passing arguments, returning values, default and keyword arguments, and the idea of scope. The tool that turns growing scripts into organised, reusable programs.
July 27, 2026
By now you can store data, make decisions, loop, and organise values into collections. String enough of that together and you'll notice something: you keep writing the same clusters of lines — the same formatting, the same calculation, the same little routine — over and over. Copy-pasting them works, until you need to fix one and have to find all ten copies. There's a far better way, and it's one of the most important ideas in all of programming: the function. A function lets you bundle logic under a name and reuse it anywhere. Master this and your programs stop being long scripts and start being organised tools.
Defining and calling a function
You've already used functions — print(), len(), range() are all functions Python provides. Now you'll write your own. You define a function with the def keyword, a name, parentheses, and an indented body:
def greet():
print("Hello there!")
print("Welcome to Python.")Running this defines the function — but notice nothing is printed yet. Defining a function just teaches Python the recipe; it doesn't cook it. To actually run the body, you call the function by writing its name followed by ():
greet() # now it runs — prints both lines
greet() # call it again — runs againThat separation is the key mental model: definition (writing the recipe, once) and call (following it, as many times as you like) are two distinct events. Define once, call whenever.
Arguments: giving a function input
A function that always does exactly the same thing is limited. Parameters let a function accept input and act on it — so one function handles endless variations. You list parameters inside the parentheses:
def greet(name):
print(f"Hello, {name}!")
greet("Ada") # Hello, Ada!
greet("Sam") # Hello, Sam!Here name is a parameter — a placeholder in the definition — and "Ada" is the argument — the actual value you pass in when calling. Same logic, different data each time. A function can take several:
def describe(name, age):
print(f"{name} is {age} years old")
describe("Grace", 42) # Grace is 42 years oldThe arguments match up by position: the first value fills the first parameter, and so on. This positional matching is the default way arguments flow into a function.
return: getting a value back
So far our functions do something (print). Often you instead want a function to compute and hand back a result you can use. That's the return statement:
def add(a, b):
return a + b
total = add(5, 3) # total is now 8
print(total * 2) # 16 — we can use the returned valueThe difference between printing and returning is one every beginner must feel clearly. print shows a value on screen and then it's gone. return sends a value back to the calling code, where you can store it in a variable, feed it to another function, or do maths on it. A function that computes something almost always returns it rather than printing it — printing is for humans to read; returning is for the rest of your program to use.
When a function has no return, it hands back a special value called None — Python's way of saying "nothing." That's fine for functions that just act (like our greet), but if you meant to get a result and got None, a missing return is usually the culprit.
Default and keyword arguments
Python makes function calls flexible in two more ways worth knowing early. A default argument gives a parameter a fallback value, so callers can omit it:
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Ada") # Hello, Ada!
greet("Ada", "Welcome") # Welcome, Ada!Because greeting has a default of "Hello", you can leave it out — but override it when you want. And keyword arguments let you pass values by name instead of position, which makes calls clearer and order-independent:
describe(age=42, name="Grace") # names make order irrelevantPassing name= and age= explicitly means you don't have to remember the parameter order, and the call reads clearly at a glance. These two features are everywhere in real Python, especially in the libraries you'll soon use.
A word on scope
One more idea rounds out the picture: scope — where a variable "lives." Variables created inside a function are local: they exist only while the function runs and vanish when it returns. Code outside can't see them.
def compute():
result = 42 # local to compute()
return result
compute()
# print(result) # ERROR — result doesn't exist out hereThis is a feature, not a nuisance. Because each function's variables are sealed off in their own little world, you can name a variable total or i inside a function without worrying whether some other part of the program uses that name too. Functions don't step on each other. This isolation is a big part of why splitting a program into functions makes it easier to reason about — each one is a self-contained box with clear inputs (arguments) and a clear output (the return value).
Where this leaves us
Functions are the tool that keeps programs from collapsing under their own weight. You can now define reusable blocks with def, feed them arguments, get results back with return, make them flexible with default and keyword arguments, and rely on scope to keep each one self-contained. This is the point where you can genuinely structure a program instead of just writing it line by line.
There's one final piece to make you dangerous with Python: you don't have to write everything yourself. Python comes with a vast library of ready-made functions and tools — and the wider world has hundreds of thousands more, a pip install away. Learning to reach for existing code multiplies everything you've learned. The finale: Modules and the Standard Library.