Parameters and Return Values

The previous lesson treated a function as a named block. This one opens the two doors in that block: how data gets in, and how the answer gets out. Get this data flow into your bones, because it is the same flow you will trace later through decompiled and obfuscated code, where none of the helpful names survive.

Think of a function as a ticket window. You slide your items through the slot in a fixed order, the clerk works out of sight, and one result slides back out. The clerk never sees your names for the items, only the order they arrived in. Slide them through in the wrong order and the clerk cheerfully computes the wrong thing, because the window has no way to know what you meant.

Parameters receive, arguments are sent

Two words, two sides of the door. Parameters are the names a function declares in its definition: applyDamage(health, amount). Arguments are the actual values a caller sends: applyDamage(100, 35). At the moment of the call, each argument is bound to the matching parameter by position: first to first, second to second. The parameter names are invisible to the caller, and the argument expressions are invisible to the body. All that crosses the boundary is values, in order.

Positional binding has one sharp edge: swap the arguments and nothing warns you. applyDamage(35, 100) is perfectly legal and quietly wrong. The switcher below shows exactly that trap in every language.

Return: one value, and the door closes

The return statement does two things at once: it hands a value back to the caller, and it ends the call immediately. Any code after an executed return never runs on that call. Back at the call site, the whole call expression is replaced by the returned value, so result = applyDamage(100, 35) stores 65 exactly as if the number had been written there.

A function that returns early from several places, returning 0 for the edge case up top and the computed value at the bottom, is using return as control flow, and that pattern is everywhere in real code.

Watch the data flow through a call

The teaching language has no functions, and for once that is an advantage: the program below is a function call unrolled by hand, every step visible. The first two assignments are the argument-to-parameter copies. The middle is the body. The last assignment is the return value landing back in the caller's variable. Step through it and watch each value hop.

The second half of that trace is the positional trap made visible: identical body, swapped inputs, different answer, and not one error anywhere. When you trace a real call in a debugger, this is exactly the view you get: parameter slots receiving values, a body transforming them, one value flowing back.

The expert wrinkle is what exactly gets copied. For simple values, numbers, booleans, and strings in most languages, the parameter receives a copy, so reassigning it inside the body cannot touch the caller's variable. For collections and objects, most languages copy a reference: the parameter is a second name for the same underlying structure, so mutating it through the parameter is visible to the caller after the call returns. Go makes this explicit with pointers, Java copies object references by value, and Lua tables behave the reference way. Half of all subtle bugs in real codebases live on this line.

Why this matters for protection

Parameter names are documentation the runtime never needs, so obfuscators erase them for free: applyDamage(health, amount) ships as a(b, c) with zero behaviour change. But the data flow itself cannot be erased. Values must still arrive in order and one result must still come back, because that is behaviour, not documentation. Reverse engineers exploit that: even in stripped code, watching what flows into a function and what comes out often reveals its purpose without reading a line of its body. In Level 5 you will do exactly that, and in Level 7 you will see how virtualization makes even the call boundary hard to find.

Arguments bind to parameters by position, as copied values. The body computes in its own world. Return delivers one value and ends the call. Names on either side are documentation; the flow of values is the behaviour, and behaviour is the one thing no transform may change.

Frequently asked questions

Can a function return more than one value?

Some languages allow it directly: Lua and Go return multiple values natively, Python returns a tuple that unpacks like several values. Languages that allow only one return value get the same effect by returning a small collection or object. Either way the principle holds: everything that comes back, comes back through the return.

What if a function has no return statement at all?

The call still ends when the body runs out, and the caller receives the language's empty value: nil in Lua, undefined in JavaScript, None in Python. Functions like that are called for their effects, printing, saving, mutating state, rather than for an answer.

Keep learning

  • Functions: Reusable Blocks of Work
  • Arrays and Lists
  • Where Values Live: Memory, Stack, and Heap
  • How Code Executes, Step by Step
  • Lua Obfuscator
  • Go Obfuscator
  • Python Obfuscator

All lessons