Functions 🧩¶
What You'll Learn
- ✅ How to define and call functions
- ✅ Parameters, arguments, and return values
- ✅ Default, keyword, and arbitrary arguments (
*args,**kwargs) - ✅ Scope — local, global, and nonlocal variables
- ✅ Lambda functions and when to use them
- ✅ Recursion with real examples
- ✅ Higher-order functions —
map(),filter(),sorted() - ✅ Decorators — the Pythonic superpower
- ✅ Common mistakes to avoid
📖 Introduction¶
A function is a reusable block of code that performs a specific task. Instead of writing the same code over and over, you define it once and call it whenever you need it.
New to Python?
Start with Basics → Complexity Analysis → Data Structures → Algorithms before jumping into practice problems.
Already know Python?
Jump straight to Data Structures or Algorithms depending on what you're revising.
Keep in mind
Every topic includes real code examples, memory diagrams, and practical use cases — read them carefully, don't just skim the code.
🧩 Defining & Calling Functions¶
1️⃣ Basic Function¶
Output:
Memory Visualization
def greet(): ← function object created in memory
print("Hello!")
greet() ← Python looks up "greet" → finds function → executes body
Stack Frame:
┌─────────────────────┐
│ greet() frame │ ← created when called
│ (executes body) │
└─────────────────────┘ ← destroyed when function returns
Real-Life Example
A function is like a recipe 📋. You write the recipe once and can cook the dish (call the function) as many times as you want.
2️⃣ Parameters & Arguments¶
Pass data into a function using parameters.
Output:
Multiple Parameters
Output:
Parameter vs Argument
- Parameter — variable in the function definition:
def greet(name) - Argument — actual value passed when calling:
greet("Alice")
3️⃣ Return Values¶
Use return to send a value back to the caller.
Output:
Return Multiple Values
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([3, 1, 7, 2, 9, 4])
print(f"Min: {low}, Max: {high}")
Output:
Memory Visualization
result = add(3, 5)
┌────────────────────────┐
│ add() frame │
│ a = 3, b = 5 │
│ return 8 ───────────┼──► result = 8
└────────────────────────┘
frame destroyed after return
Functions Without return
🎛️ Types of Arguments¶
4️⃣ Default Arguments¶
Provide a default value when no argument is passed.
def greet(name, message="Hello"):
print(f"{message}, {name}!")
greet("Alice") # uses default
greet("Bob", "Good morning") # overrides default
greet("Charlie", "Hey")
Output:
Default Arguments Must Come Last
5️⃣ Keyword Arguments¶
Pass arguments by name — order doesn't matter.
def describe(name, age, city):
print(f"{name} is {age} years old from {city}.")
# Positional
describe("Alice", 25, "Chennai")
# Keyword — any order
describe(age=25, city="Chennai", name="Alice")
describe(name="Bob", city="Mumbai", age=30)
Output:
Alice is 25 years old from Chennai.
Alice is 25 years old from Chennai.
Bob is 30 years old from Mumbai.
6️⃣ *args — Variable Positional Arguments¶
Accept any number of positional arguments as a tuple.
def total(*args):
print(f"Arguments: {args}")
return sum(args)
print(total(1, 2, 3))
print(total(10, 20, 30, 40, 50))
Output:
Memory Visualization
def total(*args):
total(1, 2, 3) called:
args = (1, 2, 3) ← packed as a tuple automatically
┌─────────────────┐
│ args[0] = 1 │
│ args[1] = 2 │
│ args[2] = 3 │
└─────────────────┘
7️⃣ **kwargs — Variable Keyword Arguments¶
Accept any number of keyword arguments as a dictionary.
def profile(**kwargs):
print(f"Data: {kwargs}")
for key, value in kwargs.items():
print(f" {key}: {value}")
profile(name="Alice", age=25, city="Chennai")
profile(language="Python", level="Beginner")
Output:
Data: {'name': 'Alice', 'age': 25, 'city': 'Chennai'}
name: Alice
age: 25
city: Chennai
Data: {'language': 'Python', 'level': 'Beginner'}
language: Python
level: Beginner
Combining All Argument Types
def demo(a, b, *args, key="default", **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"key={key}")
print(f"kwargs={kwargs}")
demo(1, 2, 3, 4, 5, key="custom", x=10, y=20)
Output:
Argument Order Rule
🌍 Scope¶
8️⃣ Local vs Global Scope¶
x = 10 # global variable
def show():
x = 99 # local variable — separate from global x
print(f"Inside: {x}")
show()
print(f"Outside: {x}")
Output:
Memory Visualization
Global Scope:
┌──────────────────────────┐
│ x = 10 │
│ show = <function> │
└──────────────────────────┘
show() Local Scope: ← created when show() is called
┌──────────────────────────┐
│ x = 99 (separate!) │
└──────────────────────────┘ ← destroyed when show() returns
global Keyword¶
Modify a global variable from inside a function.
Output:
nonlocal Keyword¶
Modify a variable from an enclosing (but not global) scope.
def outer():
x = 10
def inner():
nonlocal x
x += 5
print(f"Inner: {x}")
inner()
print(f"Outer: {x}")
outer()
Output:
Avoid Overusing global
Modifying global variables inside functions makes code hard to debug. Pass values as arguments and return results instead.
⚡ Lambda Functions¶
9️⃣ Lambda (Anonymous Functions)¶
A one-line function with no name — for simple, short operations.
# Regular function
def square(x):
return x ** 2
# Equivalent lambda
square = lambda x: x ** 2
print(square(5)) # 25
print(square(10)) # 100
Output:
Common Use Cases
numbers = [5, 2, 8, 1, 9, 3]
# Sort with lambda
sorted_nums = sorted(numbers, key=lambda x: x)
print(sorted_nums)
# Sort list of dicts
students = [
{"name": "Alice", "gpa": 3.8},
{"name": "Bob", "gpa": 3.5},
{"name": "Charlie", "gpa": 3.9}
]
ranked = sorted(students, key=lambda s: s["gpa"], reverse=True)
for s in ranked:
print(f"{s['name']}: {s['gpa']}")
Output:
Don't Overuse Lambdas
🔁 Recursion¶
🔟 Recursive Functions¶
A function that calls itself to solve a smaller version of the same problem.
Real-Life Example
Russian nesting dolls 🪆 — each doll contains a smaller doll until you reach the smallest one (base case).
Factorial
def factorial(n):
if n == 0 or n == 1: # base case
return 1
return n * factorial(n - 1) # recursive case
print(factorial(5))
print(factorial(0))
Output:
Memory Visualization
factorial(5)
└─ 5 × factorial(4)
└─ 4 × factorial(3)
└─ 3 × factorial(2)
└─ 2 × factorial(1)
└─ returns 1 ← base case
Unwinding:
2 × 1 = 2
3 × 2 = 6
4 × 6 = 24
5 × 24 = 120 ✅
Fibonacci
def fibonacci(n):
if n <= 1: # base case
return n
return fibonacci(n-1) + fibonacci(n-2)
for i in range(8):
print(fibonacci(i), end=" ")
Output:
Recursion Limit
Every Recursive Function Needs
- Base case — stops the recursion
- Recursive case — calls itself with a smaller input
Without a base case → infinite recursion →
RecursionError!
🏗️ Higher-Order Functions¶
1️⃣1️⃣ map(), filter(), sorted()¶
Functions that take other functions as arguments.
# Keep items where function returns True
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)
# Filter names longer than 4 chars
names = ["Al", "Alice", "Bob", "Charlie", "Eve"]
long_names = list(filter(lambda n: len(n) > 4, names))
print(long_names)
🎨 Decorators¶
1️⃣2️⃣ Function Decorators¶
A decorator wraps a function to add extra behaviour without modifying it.
def shout(func):
def wrapper():
result = func()
return result.upper()
return wrapper
@shout
def greet():
return "hello, world"
print(greet())
Output:
Practical: Timer Decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"⏱️ {func.__name__} took {end - start:.4f}s")
return result
return wrapper
@timer
def slow_add(a, b):
time.sleep(0.1)
return a + b
print(slow_add(3, 5))
Output:
Memory Visualization
@shout
def greet(): ...
is equivalent to:
def greet(): ...
greet = shout(greet) ← greet is replaced by wrapper
greet() → wrapper() → original greet() → .upper()
Common Built-in Decorators
| Decorator | Use |
|---|---|
@staticmethod |
Method that doesn't need self |
@classmethod |
Method that receives the class |
@property |
Access method like an attribute |
@functools.lru_cache |
Cache function results (memoization) |
✅ Quick Reference Summary¶
| Concept | Syntax | Use When |
|---|---|---|
| Basic function | def func(): |
Reusable block of code |
| Parameters | def func(a, b): |
Pass data into function |
| Return value | return value |
Send result back to caller |
| Default args | def func(a, b=10): |
Optional parameters |
| Keyword args | func(a=1, b=2) |
Pass by name, any order |
*args |
def func(*args): |
Unknown number of positional args |
**kwargs |
def func(**kwargs): |
Unknown number of keyword args |
| Lambda | lambda x: x**2 |
Short one-line functions |
| Recursion | def f(n): return f(n-1) |
Problem that reduces to itself |
global |
global x |
Modify global var inside function |
nonlocal |
nonlocal x |
Modify enclosing scope var |
| Decorator | @decorator |
Wrap function with extra behaviour |