Functions
Summary
A function is a named, reusable block of code that takes zero or more
inputs (parameters), does work, and optionally produces an output (a
return value). Functions are the primary tool for breaking a program into
understandable pieces: instead of one long block of code, a program
becomes a set of named operations that call each other — TakeDamage,
FindClosestEnemy, IsGrounded — each readable on its own.
Mental Model
A function is a small machine with a name, a slot for each input, and (at most) one output slot. You don't need to know how the machine works internally to use it correctly — only its name, what it needs, and what it gives back. That boundary (the signature: name, parameter types, return type) is a contract; the body behind it can change entirely without affecting anyone who only relies on the signature.
Calling a function is handing values to the machine and waiting for its answer; the machine runs its own internal steps (which may themselves call other machines) and hands back a result, then execution resumes exactly where it left off, at the call site.
Core Explanation
- Parameters vs. arguments. A parameter is the named placeholder in a
function's declaration (
void TakeDamage(int amount)—amountis the parameter). An argument is the actual value supplied at the call site (TakeDamage(10)—10is the argument). The distinction matters when reading error messages and documentation precisely. - Return values. A function that produces a result declares a return
type and uses
return value;to hand it back — the call site can then use that value as an expression (see Expressions). A function with no meaningful result to hand back is declaredvoid(C#/C++) and simply performs its work;return;with no value can still exit it early. - Pass by value vs. pass by reference. For value-type parameters
(
int,struct), the function receives its own copy — modifying the parameter inside the function does not change the caller's variable. For reference-type parameters (classinstances), the function receives the same reference the caller holds — modifying the object through it is visible to the caller, though reassigning the parameter itself to a different object is not (see Data Types for the value/ reference distinction this rests on). C# and C++ both offer explicit by-reference parameters (ref/outin C#,&in C++) when a value type genuinely needs to be modified in place by the callee. - Overloading lets multiple functions share a name as long as their
parameter lists differ (in count or type) — the compiler picks the
matching one based on the arguments supplied at each call site.
Attack()andAttack(int bonusDamage)can coexist as two distinct functions with the same name. - Default parameters let a parameter be omitted at the call site, substituting a declared default value — reducing the need for several overloads that only differ by a trailing, usually-unchanged argument.
- Recursion — a function calling itself, with each call working on a smaller version of the problem until a base case stops the recursion. Powerful for naturally self-similar problems (traversing a tree of parent/child transforms) but requires a guaranteed-reachable base case to avoid recursing until the call stack overflows.
- Pure functions vs. functions with side effects. A pure function's
output depends only on its inputs, and it changes nothing outside
itself (
Add(a, b)); a function with side effects also modifies state beyond its return value (TakeDamagemutateshealth). Neither is wrong, but knowing which kind a function is changes what a caller can safely assume about calling it more than once, or in a different order.
Syntax / Examples
C# (Unity)
// Signature: name, parameters, return type.
int Clamp(int value, int min, int max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
void TakeDamage(int amount) // void: no return value
{
health = Clamp(health - amount, 0, maxHealth);
}
// Overloading
void Attack() => Attack(baseDamage);
void Attack(int bonusDamage) => target.TakeDamage(baseDamage + bonusDamage);
// Default parameter
void Log(string message, bool isWarning = false) { /* ... */ }
C++ (Unreal)
int32 Clamp(int32 Value, int32 Min, int32 Max)
{
if (Value < Min) return Min;
if (Value > Max) return Max;
return Value;
}
void ATakeDamage(int32 Amount)
{
Health = Clamp(Health - Amount, 0, MaxHealth);
}
// Pass by reference — modifies the caller's variable directly
void Normalize(FVector& Vector)
{
Vector = Vector.GetSafeNormal();
}
Unreal Blueprint
A Function (created in the My Blueprint panel) has an entry node with output pins for each parameter and, if it returns a value, a Return Node with an input pin for the result — the same name/parameters/return-type signature, expressed as pins instead of a text declaration. Calling it is a single node with input pins matching the parameters and (if any) an output pin matching the return value.
Practical Example
Extracting repeated "closest target" logic (seen in Loops) into a function makes it reusable and gives it a name that documents intent:
Enemy FindClosest(Vector3 origin, List<Enemy> candidates)
{
Enemy closest = null;
float closestDistance = float.MaxValue;
foreach (Enemy enemy in candidates)
{
float distance = Vector3.Distance(origin, enemy.position);
if (distance < closestDistance)
{
closest = enemy;
closestDistance = distance;
}
}
return closest;
}
// Call sites — the loop itself no longer needs to be re-written each time.
Enemy nearestToPlayer = FindClosest(player.position, enemies);
Enemy nearestToTurret = FindClosest(turret.position, enemies);
The function's signature (origin, candidates, returns an Enemy) is
everything a caller needs to know; neither call site needs to know the
loop exists inside it.
Common Mistakes
- Functions that do too much. A function handling input, game logic,
and rendering all at once is hard to test, hard to reuse, and hard to
name honestly. A function whose name needs "and" to describe it
(
UpdateAndRenderAndSave) is a sign it should be split. - Assuming a class-type parameter can't be affected by the callee — it can: mutating the object's fields inside the function is visible to the caller, because both hold a reference to the same object, even though the parameter variable itself is a local copy of the reference.
- Recursion without a reachable base case — a stack overflow crash, not a graceful failure; every recursive function needs a condition that is guaranteed to eventually be true.
- Overloading in a way that makes call sites ambiguous or surprising — overloads should be genuinely equivalent operations at different levels of detail, not unrelated behaviors sharing a name.
- Ignoring a return value that reports failure — a function that
returns
boolor an error code to signal success/failure is silently useless if every caller discards the result without checking it.
Best Practices
- Name a function after what it does, specifically enough that the name alone tells a caller when to use it.
- Keep a function focused on one responsibility; extract a helper function when a block starts handling more than one concern.
- Prefer returning a value over mutating an out-parameter when either would work — it makes the function easier to use as an expression.
- Document (via the signature and, where needed, a short comment) whether a function has side effects a caller must be aware of.
- Give every recursive function an explicit, obviously-reachable base case, written and tested before the recursive case.
Exercises
- Extract a block of repeated code (write a small example with the same five lines appearing twice) into a function, and verify both call sites still behave identically.
- Write two overloads of a
Healfunction — one taking no arguments (heals a fixed default amount) and one taking an explicit amount — and explain how the compiler chooses between them. - Write a recursive function that sums the values in a singly linked list, identifying its base case explicitly.
- Take a function that mutates a
class-typed parameter's field and explain, using the value/reference distinction, why the caller sees the change.
Related Concepts
- Loops — a loop body that grows complex is a strong candidate for extraction into a named function.
- Classes — a class's methods are functions that operate on a particular object's state.
References
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules.- C# Language Reference — Methods: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/methods
- cppreference — Functions: https://en.cppreference.com/w/cpp/language/functions