
Python
Write functions that are maintainable, efficient, and a joy to work with.
# โ Bad โ unclear what it does
def process(x):
return x * 2
# โ
Good โ clear purpose
def double_number(x):
return x * 2
# โ
Better โ even more descriptive
def calculate_doubled_value(value):
return value * 2# โ
Good โ verbs indicate what function does
def get_user_name(user_id):
pass
def validate_email(email):
pass
def send_notification(message):
pass
# โ Bad โ unclear action
def user_name(user_id):
pass
def email(email):
passdef calculate_bmi(weight_kg, height_m):
"""
Calculate Body Mass Index.
Args:
weight_kg (float): Weight in kilograms
height_m (float): Height in meters
Returns:
float: BMI value
Example:
>>> calculate_bmi(70, 1.75)
22.86
"""
return weight_kg / (height_m ** 2)def divide_safe(a, b):
"""
Divide two numbers safely.
Args:
a (float): Numerator
b (float): Denominator
Returns:
float: Result of a / b, or None if division by zero
Note:
Returns None rather than raising exception to handle
division by zero gracefully.
"""
if b == 0:
return None
return a / bEach function should do ONE thing:
# โ Bad โ does too much
def process_user_data(user_id):
user = fetch_user_from_db(user_id)
user["age"] = 2024 - user["birth_year"]
send_email_notification(user["email"])
save_to_cache(user)
log_activity(user_id)
return user
# โ
Good โ each function has one job
def get_user_with_age(user_id):
user = fetch_user_from_db(user_id)
user["age"] = 2024 - user["birth_year"]
return user
def notify_user(user):
send_email_notification(user["email"])
def cache_user(user):
save_to_cache(user)
def log_access(user_id):
log_activity(user_id)
# In main code
user = get_user_with_age(user_id)
notify_user(user)
cache_user(user)
log_access(user_id)Shorter functions are easier to test and debug:
# โ Bad โ 30 lines doing multiple things
def process_order(order_id):
# Fetch order
# Validate items
# Calculate totals
# Apply discounts
# Check inventory
# Process payment
# Send confirmation
# Update database
# ... (24 more lines)
# โ
Good โ small, focused functions
def validate_order(order):
return all(item["quantity"] > 0 for item in order["items"])
def calculate_order_total(order):
return sum(item["price"] * item["quantity"] for item in order["items"])
def apply_discount(total, discount_percent):
return total * (1 - discount_percent / 100)
def process_order(order_id):
order = fetch_order(order_id)
if not validate_order(order):
raise ValueError("Invalid order")
total = calculate_order_total(order)
total = apply_discount(total, order.get("discount", 0))
process_payment(total)
return orderAlways return the same type:
# โ Bad โ inconsistent return types
def find_user(name):
if name:
return {"name": name} # dict
else:
return "Not found" # string
# โ
Good โ always returns dict or None
def find_user(name):
if name:
return {"name": name}
return None
# โ
Better โ explicit error handling
def find_user(name):
if not name:
raise ValueError("Name cannot be empty")
return {"name": name}from functools import lru_cache
# โ Bad โ recalculates every time
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(35) # Takes 3+ seconds!
# โ
Good โ caches results
@lru_cache(maxsize=128)
def fibonacci_cached(n):
if n <= 1:
return n
return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)
fibonacci_cached(35) # Instant!# โ Bad โ calculates length multiple times
def process_list(items):
result = []
for i in range(len(items)): # Length calculated each iteration
if items[i] > 0: # Accessing item again
result.append(items[i] * 2) # Accessing again
return result
# โ
Good โ cleaner and more efficient
def process_list(items):
return [item * 2 for item in items if item > 0]Validate at function boundaries:
# โ Bad โ no validation
def divide(a, b):
return a / b
# โ
Good โ validates inputs
def divide(a, b):
if not isinstance(a, (int, float)):
raise TypeError("a must be numeric")
if not isinstance(b, (int, float)):
raise TypeError("b must be numeric")
if b == 0:
raise ValueError("Division by zero not allowed")
return a / bUse sensible defaults:
# โ Bad โ forces users to specify everything
def create_config(model, temperature, max_tokens, top_p, frequency_penalty):
pass
# โ
Good โ reasonable defaults
def create_config(
model="GPT-4",
temperature=0.7,
max_tokens=1000,
top_p=1.0,
frequency_penalty=0.0
):
pass
# Usage
config1 = create_config() # All defaults
config2 = create_config(temperature=0.3) # Override oneFunctions shouldn't modify external state:
# โ Bad โ modifies global state
global_list = []
def add_item(item):
global_list.append(item) # Side effect!
return len(global_list)
# โ
Good โ pure function
def add_item(item, items=None):
if items is None:
items = []
items = items + [item] # Creates new list
return items, len(items)# โ Bad โ flag parameter makes function do two things
def process_data(data, save_to_file=False):
result = transform(data)
if save_to_file:
write_file("output.txt", result)
return result
# โ
Good โ separate concerns
def process_data(data):
return transform(data)
def process_and_save(data, filename):
result = process_data(data)
write_file(filename, result)
return result# โ Bad โ too many parameters
def create_user(name, email, age, city, country, phone, address, zip_code):
pass
# โ
Good โ group related data
def create_user(name, email, details):
# details = {age, city, country, phone, address, zip_code}
pass
# Even better โ use dataclass
from dataclasses import dataclass
@dataclass
class UserDetails:
age: int
city: str
country: str
phone: str
address: str
zip_code: str
def create_user(name: str, email: str, details: UserDetails):
pass# โ Bad โ modifies input list
def add_prefix(prefix, items):
for i in range(len(items)):
items[i] = prefix + items[i] # Modifies!
return items
# โ
Good โ returns new list
def add_prefix(prefix, items):
return [prefix + item for item in items]Write functions that are easy to test:
# โ Bad โ hard to test (depends on external systems)
def process_order(order_id):
order = fetch_from_database(order_id) # External dependency
total = calculate_total(order)
send_to_payment_api(total) # External dependency
return True
# โ
Good โ pure logic, testable
def calculate_order_total(order):
"""Pure function โ no side effects."""
return sum(item["price"] * item["qty"] for item in order["items"])
# Dependency injection โ pass dependencies in
def process_order(order_id, db_fetch, payment_api):
order = db_fetch(order_id)
total = calculate_order_total(order)
payment_api.charge(total)
return True
# Tests
def test_calculate_total():
order = {"items": [{"price": 10, "qty": 2}]}
assert calculate_order_total(order) == 20Choose appropriate error strategies:
# Strategy 1: Raise exceptions
def get_positive_number(n):
if n <= 0:
raise ValueError("Must be positive")
return n
# Strategy 2: Return None
def safe_divide(a, b):
if b == 0:
return None
return a / b
# Strategy 3: Return default
def get_config(key, default=None):
config = load_config()
return config.get(key, default)
# Choose based on context:
# - Raise: Program error, must be fixed
# - Return None: Optional operation, caller can handle
# - Default: Configuration, sensible fallbackโ
Clear, descriptive name
โ
Docstring with Args/Returns
โ
Single responsibility
โ
Small and focused
โ
Consistent return types
โ
Input validation
โ
Sensible defaults
โ
No side effects
โ
Easy to test
โ
Efficient algorithm
โ
Handles edge cases
โ
Good error messages| Practice | Why | Example |
|---|---|---|
| Descriptive names | Clarity | `calculate_total` not `calc` |
| Docstrings | Documentation | """Calculate total cost""" |
| Single responsibility | Maintainability | One job per function |
| Small size | Testability | <20 lines ideal |
| Pure functions | Predictability | No side effects |
| Input validation | Robustness | Check before processing |
| Defaults | Usability | Sensible fallback values |
| Testability | Quality | Dependency injection |
Ask yourself:
Ready to level up? Explore:
The Functions topic now covers:
โ Why functions matter
โ Parameters and arguments
โ Return statements
โ Local vs global scope
โ Advanced features (*args, **kwargs, defaults)
โ Lambda functions
โ Decorators
โ Functional programming (map, filter, reduce)
โ Best practices and patterns
You're now a functions expert!
Ready to put it all together? Try comprehensive challenges or take the full quiz
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep โข Build Real โข Verify Skills โข Launch Forward