What a function is
A function is a named, reusable block of code: values go in (parameters), work happens, a value comes out (return value).
# Python
def area_of_circle(radius):
return 3.14159 * radius * radius
print(area_of_circle(2)) # 12.56636
print(area_of_circle(10)) # 314.159
// Java
static double areaOfCircle(double radius) {
return 3.14159 * radius * radius;
}
// C++
double areaOfCircle(double radius) {
return 3.14159 * radius * radius;
}
Vocabulary, precisely:
- Define a function: write its name, parameters and body. Nothing runs yet.
- Call (invoke) it:
area_of_circle(2)— now the body runs, withradius = 2. - Parameter — the variable in the definition (
radius). Argument — the actual value passed in the call (2). - Return value — what the call turns into.
print(area_of_circle(2))first becomesprint(12.56636).
A function call is a jump: execution leaps into the body, runs it, and comes back carrying the return value.
Why functions exist (it's not just "avoid repetition")
The shallow reason: write the circle formula once, use it everywhere; fix a bug once, it's fixed everywhere (DRY — Don't Repeat Yourself).
The deep reason: functions let you stop thinking. Once
area_of_circle works, you never again think about π — you think "area,"
one level higher. This is abstraction, the same move as Level 0's
APIs: a name in front of hidden machinery. Software
is towers of functions calling functions; your entire growth as an engineer
is learning to build good towers.
The kitchen analogy: a function is a recipe card. "Make the sauce (see card 12)" — the main recipe stays readable because details live elsewhere.
# This reads like a sentence — each call hides honest work
def process_order(order):
validate(order)
charge(order.customer, total_price(order))
schedule_delivery(order)
send_receipt(order)
Return early, return often
A function ends the moment it hits return. Use that for guard clauses
(met in Control Flow):
def withdraw(account, amount):
if amount <= 0:
return "Invalid amount" # bail out early
if amount > account.balance:
return "Insufficient funds" # and again
account.balance -= amount # happy path, un-nested
return "Success"
A function with no explicit return hands back "nothing" (None in Python,
void functions in Java/C++) — fine for functions called for their effects
(printing, saving) rather than their answer.
Scope: where a name exists
Scope is the region of code where a variable is visible. Variables created inside a function are local — born when the call starts, gone when it returns:
def calculate():
result = 42 # local to calculate()
return result
calculate()
print(result) # ❌ NameError — 'result' doesn't exist out here
Each call gets fresh locals: two simultaneous calls to the same function don't see each other's variables. That isolation is precisely why functions are safe to reuse.
Variables defined outside all functions are global — visible everywhere. Convenient, dangerous:
A global can be modified by any function, so to debug it you must suspect every function. Ten functions sharing globals aren't ten independent tools — they're one tangled machine. Pass data in as parameters and out as return values; keep the plumbing visible. (Code review will enforce this; so do interviewers in Level 5 design rounds.)
Pass-by-value vs pass-by-reference
The payoff of the references idea from Variables & Data Types: what does a function receive — the thing, or a way to reach the thing?
def add_bonus(score): # numbers: reassignment is invisible outside
score = score + 10
def add_item(cart): # lists: MUTATION is visible outside
cart.append("gift")
my_score = 50
add_bonus(my_score)
print(my_score) # 50 — unchanged
my_cart = ["bread"]
add_item(my_cart)
print(my_cart) # ['bread', 'gift'] — changed!
The model that explains both: arguments are copies of what's in the box.
For my_score, the box holds the value 50 → the function gets a copy of
50; reassigning its copy touches nothing outside. For my_cart, the box
holds a reference (an address) → the function gets a copy of the address —
pointing at the same list. Reassigning cart inside would be invisible;
mutating through it (append) changes the one shared list.
- Python and Java both work exactly this way (copies of references for objects).
- C++ is the language of explicit choice:
f(int x)copies the value,f(int& x)passes a true reference (even reassignment shows outside),f(const std::string& x)passes a reference but forbids changes — the efficient read-only default you'll see everywhere in C++ code.
Writing functions people thank you for
Four rules, learnable now, judged in every interview and code review forever:
- One job. "Calculate the total" — yes. "Calculate the total and email the user and update the log" — three functions wearing a trenchcoat. (Formally the Single Responsibility Principle — Level 5.)
- Name = verb phrase that tells the truth.
send_receipt,is_valid_email(booleans read as questions). If an honest name needs "and" in it, see rule 1. - Few parameters. More than three is a smell — bundle related ones into
an object (
Order), or your calls becomef(true, false, 7, null, true). - Prefer returning over mutating.
clean(text) → new_textis easier to reason about than silently editing shared data — see the QA above for why.
Common beginner mistakes
- Defining but never calling — then wondering why nothing printed. A definition is a recipe card; you still have to cook.
- Confusing
printwithreturn.printshows a value to a human;returnhands it to code. A function that prints its answer instead of returning it can't be composed:total(prices) + taxbreaks. - Capturing nothing: calling
s.upper()in Python and ignoring that it returns the new string (strings are immutable — it can't edit in place):name = name.upper(). - One mega-function. A 300-line
main()works until it doesn't. If you can't summarize a function in one sentence, split it. - Same name, different things — shadowing a global or built-in
(
list = [1,2]in Python kills thelist()built-in for the rest of the program).
Interview perspective
Practice
Beginner
- Write
celsius_to_fahrenheit(c)(formula:c × 9/5 + 32), then use it in a loop printing a conversion table for 0–40°C in steps of 5. - Write
is_even(n)returning a boolean — then rewrite FizzBuzz from the previous page using helper functionsis_multiple_of(n, k).
Intermediate
- Write
largest(numbers)without using built-inmax— accumulator pattern inside a function. Then writesecond_largest(numbers); the edge cases (duplicates, short lists) are the actual exercise. - Predict the output, then run:
Explain every printed value using the box-and-reference model.Python
def mystery(items): items.append(99) items = [0] items.append(1) return items data = [1, 2] result = mystery(data) print(data, result)
Advanced
- Write
apply_twice(f, x)that returnsf(f(x)), and use it with adouble(n)function. Congratulations — functions are values too (higher-order functions), the gateway to callbacks (Level 8) and functional programming. - Refactor: take your guess-the-number game from the previous page and split
it into
read_guess(),give_hint(guess, secret)andplay(). Notice how testable each piece became.
Next: Recursion — functions that call themselves, and the call stack earning its keep.