Functions are one of the first “real” programming concepts you hit in Python, and they’re also one of the most important.
Once you understand how to package logic into a function, you stop repeating yourself and start building code that’s actually reusable and testable.
1. Defining and Calling Functions
A function is a named block of code that runs only when you call it. You define one with the def keyword:
def greet():
print("Hello, welcome!")
Enter fullscreen mode Exit fullscreen mode
This creates a function called greet, but nothing happens yet, defining a function just stores the instructions. To actually run it, you call it by using its name followed by parentheses:
greet() # Output: Hello, welcome!
Enter fullscreen mode Exit fullscreen mode
You can call the same function as many times as you want:
greet()
greet()
greet()
Enter fullscreen mode Exit fullscreen mode
Each call executes the function’s body from the top.
2. Parameters and Arguments
Most useful functions need input to work with.
Parameters are the placeholders you define in the function signature; arguments are the actual values you pass in when calling the function.
def greet(name):
print(f"Hello, {name}!")
greet("Mary") # "name" is the parameter, "Mary" is the argument
greet("James")
Enter fullscreen mode Exit fullscreen mode
Multiple parameters
def describe_dataset(name, rows, columns):
print(f"{name}: {rows} rows, {columns} columns")
describe_dataset("Sales Data", 5000, 12)
Enter fullscreen mode Exit fullscreen mode
Default parameter values
You can give a parameter a default value, so it’s optional when calling the function:
def describe_dataset(name, rows, columns=1):
print(f"{name}: {rows} rows, {columns} columns")
describe_dataset("Sales Data", 5000) # uses default columns=1
describe_dataset("Sales Data", 5000, 12) # overrides the default
Enter fullscreen mode Exit fullscreen mode
Keyword arguments
Arguments can be passed by position or by name. Using names (keyword arguments) makes calls clearer, especially with several parameters:
describe_dataset(rows=5000, columns=12, name="Sales Data")
Enter fullscreen mode Exit fullscreen mode
Order doesn’t matter when you use keyword arguments — Python matches them by name.
*args and **kwargs
Sometimes you don’t know in advance how many arguments will be passed. *args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dictionary:
def sum_values(*args):
return sum(args)
print(sum_values(1, 2, 3)) # 6
print(sum_values(10, 20, 30, 40)) # 100
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Mary", role="Data Analyst", city="Nairobi")
Enter fullscreen mode Exit fullscreen mode
3. Return Values
A function that only prints something is limited, the result disappears once printed.
The return statement sends a value back to wherever the function was called, so you can store it, reuse it, or pass it to another function.
def add(a, b):
return a + b
result = add(4, 5)
print(result) # 9
Enter fullscreen mode Exit fullscreen mode
Without return, a function implicitly returns None:
def greet(name):
print(f"Hello, {name}!")
value = greet("Mary") # prints "Hello, Mary!"
print(value) # None
Enter fullscreen mode Exit fullscreen mode
Returning multiple values
Python lets you return more than one value at once, packed as a tuple:
def get_min_max(numbers):
return min(numbers), max(numbers)
low, high = get_min_max([4, 9, 1, 7, 3])
print(low, high) # 1 9
Enter fullscreen mode Exit fullscreen mode
A practical example combining parameters and return values
def calculate_average(numbers):
if len(numbers) == 0:
return 0
return sum(numbers) / len(numbers)
scores = [85, 90, 78, 92, 88]
average = calculate_average(scores)
print(f"Average score: {average}") # Average score: 86.6
Enter fullscreen mode Exit fullscreen mode
Here, the function takes data in (numbers), does the work, and hands a usable result back out via return, rather than just printing it and losing it.
4. return vs. print: What’s the Difference?
New Python learners often mix these up because both can show a value on the screen but they do fundamentally different things.
print()displays a value to the console. It’s for humans watching the program run. Once printed, that value is gone — it isn’t stored anywhere the program can use again.returnsends a value back out of the function to whatever code called it. It’s for the program itself. The returned value can be stored in a variable, passed to another function, or used in further calculations.
def add_print(a, b):
print(a + b)
def add_return(a, b):
return a + b
result1 = add_print(5, 3) # prints "8" to the console
result2 = add_return(5, 3) # returns 8, nothing printed
print(result1) # None -> add_print never returned a value
print(result2) # 8 -> add_return handed the value back
Enter fullscreen mode Exit fullscreen mode
add_print shows 8 on screen, but as far as the rest of the program is concerned, it produced nothing — result1 ends up None.
add_return, on the other hand, actually hands the value 8 back, so result2 holds a usable number you can do more with:
total = add_return(5, 3) + add_return(10, 2)
print(total) # 20
Enter fullscreen mode Exit fullscreen mode
Trying the same thing with add_print would fail, since add_print(5, 3) evaluates to None, and you can’t add None to a number.
Rule of thumb: use print() when you just want to see a value while debugging or communicating with a user; use return when the function’s result needs to be used elsewhere in your code.
In most real programs, functions should return values rather than print them, so the caller decides what to do with the result — whether that’s printing it, saving it, or feeding it into another function.
5. Scope
Scope determines where in your code a variable can be accessed. Understanding scope prevents a lot of confusing bugs, especially “why isn’t this variable available here?” moments.
Local scope
Variables created inside a function only exist inside that function. They are local to it and disappear once the function finishes running.
def calculate_total(price, quantity):
total = price * quantity # local variable
return total
print(calculate_total(100, 3)) # 300
print(total) # NameError: total is not defined
Enter fullscreen mode Exit fullscreen mode
total was created inside calculate_total, so it can’t be accessed outside of it.
Global scope
Variables defined outside any function are global and can be read from inside functions:
tax_rate = 0.16 # global variable
def calculate_total(price, quantity):
return price * quantity * (1 + tax_rate)
print(calculate_total(100, 3)) # 348.0
Enter fullscreen mode Exit fullscreen mode
Modifying a global variable inside a function
By default, assigning to a variable inside a function creates a new local variable, even if a global one has the same name. To actually modify the global variable, you need the global keyword:
counter = 0
def increment():
global counter
counter += 1
increment()
increment()
print(counter) # 2
Enter fullscreen mode Exit fullscreen mode
Without global counter, Python would raise an error or create a separate local variable instead of updating the outer one. In general, it’s good practice to avoid relying heavily on global state, passing values in as parameters and getting results back via return keeps functions predictable and easier to test.
Putting It All Together
Here’s a small example that uses everything above
Parameters with a default value, a return value, and local scope — to clean a simple list of numeric entries:
def clean_numbers(values, default=0):
"""Replace non-numeric entries with a default value and return the cleaned list."""
cleaned = [] # local variable
for value in values:
if isinstance(value, (int, float)):
cleaned.append(value)
else:
cleaned.append(default)
return cleaned
raw_data = [10, "N/A", 25, None, 30]
result = clean_numbers(raw_data, default=0)
print(result) # [10, 0, 25, 0, 30]
Enter fullscreen mode Exit fullscreen mode
This function takes a list and an optional default value as parameters, processes the data using a local variable (cleaned), and returns a new list, a pattern you’ll use constantly once you start working with real-world, messy data.
Key Takeaways
-
Define a function with
def; call it with parentheses to run it. -
Parameters are placeholders in the definition; arguments are the actual values passed at call time. Defaults, keyword arguments,
*args, and**kwargsall give you flexibility in how a function accepts input. -
returnsends a value back to the caller so it can be reused, without it, a function returnsNone. -
Scope controls where variables live: local variables exist only inside their function, while global variables are accessible everywhere (though modifying them from inside a function requires the
globalkeyword).
Functions are the basic unit of logic in Python, once you’re comfortable defining them, passing data in and out, and knowing where your variables live, you have the building blocks to organize almost any piece of code into something clear and reusable.