Python Functions: How to Define and Use Them

Functions are the building blocks of readable, maintainable Python code. Instead of writing the same logic over and over, you wrap it in a function once and call it wherever you need it. In this guide you'll learn how to define functions, pass data in through parameters, get data back with return values, and use Python's powerful shorthand tools like lambda.

Why Functions? The DRY Principle

DRY stands for Don't Repeat Yourself. If you find yourself copying and pasting the same block of code in multiple places, that's a sign it should be a function. Functions make your code shorter, easier to read, and much simpler to fix β€” change the function once and every call site benefits.

Rule of thumb: If a piece of logic appears more than twice, extract it into a function.

Defining a Function

Use the def keyword, followed by the function name, parentheses, and a colon. The indented block beneath it is the function body.

python
def greet():
    print("Welcome to GrowBit!")

# Call the function
greet()   # Welcome to GrowBit!

Indentation matters in Python β€” the standard is 4 spaces. Every line at the same indentation level is part of the same block. A function ends when indentation returns to the previous level.

Parameters and Arguments

Parameters are the variable names inside the function definition. Arguments are the actual values you pass when you call the function.

python
def plant(crop, x, y):
    print(f"Planting {crop} at ({x}, {y})")

# Positional arguments β€” order matters
plant("wheat", 0, 0)      # Planting wheat at (0, 0)
plant("carrot", 3, 2)     # Planting carrot at (3, 2)

# Keyword arguments β€” order doesn't matter
plant(y=4, crop="tomato", x=1)   # Planting tomato at (1, 4)

Default Parameter Values

You can give parameters a default value so callers don't have to provide them every time. Default parameters must come after non-default ones.

python
def water_plot(x, y, amount=1.0):
    print(f"Watering ({x},{y}) with {amount}L")

water_plot(0, 0)          # uses default: 1.0L
water_plot(2, 3, 2.5)     # overrides: 2.5L
water_plot(1, 1, amount=0.5)   # keyword override

Return Values

Use return to send a value back to the caller. Without an explicit return, a function returns None.

python
def calculate_profit(cost, sell_value):
    return sell_value - cost

profit = calculate_profit(10, 35)
print(f"Profit: {profit} gold")   # Profit: 25 gold

# Return multiple values (Python returns a tuple)
def crop_stats(name, cost, value):
    profit = value - cost
    roi = (profit / cost) * 100
    return profit, roi

p, r = crop_stats("wheat", 10, 25)
print(f"Profit: {p}, ROI: {r:.1f}%")   # Profit: 15, ROI: 150.0%

Variable Scope

Variables created inside a function are local β€” they only exist while the function runs. Variables created outside are global. A function can read a global variable, but to modify it you need the global keyword (which is generally a code smell β€” prefer returning values instead).

python
gold = 100   # global

def buy_seed(cost):
    # Local variable β€” disappears after function returns
    new_balance = gold - cost
    return new_balance

remaining = buy_seed(15)
print(remaining)   # 85
print(gold)        # still 100 β€” global unchanged

# Using global (avoid when possible)
def earn_gold(amount):
    global gold
    gold += amount

earn_gold(50)
print(gold)   # 150
Best practice: Avoid global. Pass values in as arguments and return the updated value β€” it keeps functions predictable and testable.

Lambda Functions

A lambda is a compact, anonymous function defined in a single expression. Use it when you need a short throwaway function β€” especially as an argument to sorted(), map(), or filter().

python
# Regular function
def double(x):
    return x * 2

# Equivalent lambda
double = lambda x: x * 2

# Most useful as an inline key function
crops = [
    {"name": "wheat",  "value": 25},
    {"name": "carrot", "value": 15},
    {"name": "tomato", "value": 40},
]

sorted_crops = sorted(crops, key=lambda c: c["value"], reverse=True)
for c in sorted_crops:
    print(c["name"], c["value"])
# tomato 40 / wheat 25 / carrot 15

Built-in Functions Worth Knowing

Python ships with dozens of ready-made functions. Here are the ones you'll use constantly:

  • len(x) β€” number of items in a list, string, or dict
  • range(start, stop, step) β€” generates a sequence of integers
  • print(*args) β€” output to the console
  • type(x) β€” returns the type of a value
  • sorted(iterable, key=..., reverse=False) β€” returns a sorted list
  • sum(iterable), min(iterable), max(iterable)
  • abs(x) β€” absolute value
  • round(x, ndigits) β€” round a float

Practical: Farming Functions

Here's a small set of functions that power a GrowBit-style farm simulation. Notice how each function does one thing, takes clear inputs, and returns a value instead of mutating globals.

python
def plant(crop, x, y, grid):
    """Place a crop dict onto the grid at position (x, y)."""
    grid[y][x] = {"name": crop, "days": 0, "mature": False}
    print(f"Planted {crop} at ({x},{y})")

def advance_day(grid, growth_days=5):
    """Age every planted crop by one day; mark mature when ready."""
    for row in grid:
        for cell in row:
            if cell is not None:
                cell["days"] += 1
                if cell["days"] >= growth_days:
                    cell["mature"] = True

def harvest(x, y, grid, sell_value=25, cost=10):
    """Remove a mature crop and return the profit."""
    cell = grid[y][x]
    if cell and cell["mature"]:
        grid[y][x] = None
        return sell_value - cost
    return 0   # not ready yet