Data structures & algorithms · Foundations
Functions, parameters & scope
A name, a private set of variables, one value back out. This page traces nCr(5, 2) frame by frame, then shows why passing a number leaves the caller untouched and passing an array does not.
Pass a number, then pass an array →01 The idea
A name for a block, and a wall around its variables
You write a loop that multiplies 1 through 5 together. Then you need the same loop for 2, and again for 3. Copy it three times and you now have three places to fix when the multiplication is wrong, and three throwaway variable names to keep the answers apart. Give the loop a name once — fact — and you have one place and no extra names.
Naming a block does a second thing that matters more. It puts a wall around the variables inside it. Every call creates a fresh private workspace called a stack frame; the parameters and locals live in that frame, and they are destroyed when the call returns. So the only things that cross the wall are the arguments going in and the return value coming out. That is the whole model, and almost every question about functions is really a question about what exactly crossed.
02 Hand trace
nCr(5, 2), one frame at a time
Take nCr(5, 2) — how many ways to choose 2 things out of 5 — written the way the formula reads: 5! / (2! × 3!). Running the section 03 program end to end, main calls show, show calls nCr, and nCr makes three calls to the same fact function. Each box below is one live stack frame, newest at the right. Follow what crosses the boundary: a number goes in, a number comes out, and nothing else.
Three calls to the same four lines of code, three separate frames, and not one of them could see the others. The first call finished with its p at 120; the second still started its own p at 1, because that is a different box created fresh at the call and thrown away at the return. Meanwhile nCr never learned anything about how fact works — it sent 5 and got 120. Section 04 shows the one case where something bigger than a number crosses that boundary.
03 Code
The same program, before and after the split
Here is nCr(5, 2) written twice. On the left it is one main with the loop copied out three times. On the right it is four named functions. The answer is 10 either way — the left prints the bare 10, the right prints 5 choose 2 is 10 — so read the diff for what it changes about names and scope, not about speed.
Before · one long main
main: p = 1 for i in 1..5: p = p * i # 5! = 120 q = 1 for i in 1..2: q = q * i # 2! = 2 s = 1 for i in 1..3: s = s * i # 3! = 6 print p / (q * s) # 10
After · four named functions
function fact(k): # returns a value p = 1 for i in 1..k: p = p * i return pfunction nCr(n, r): # returns a value return fact(n) / (fact(r) * fact(n - r))function show(n, r): # void: prints, returns nothing print n, "choose", r, "is", nCr(n, r)function main(): show(5, 2) # 5 choose 2 is 10
The loop earned a name because it appeared three times. The left version needs three throwaway names — p, q, s — purely to keep three results apart. On the right there is one fact called three times, so a mistake in the multiplication is one edit instead of three, and the extra names are gone.
k, p and i exist only inside fact. That is local scope, and it is exactly what the trace in section 02 showed: the first call ended with p at 120 and the second still began with p at 1. On the left, one i is shared by all three loops in a single scope, so moving a loop or forgetting a reset silently inherits the previous loop's leftovers.
n and r inside nCr are new locals, not show's variables. show(5, 2) copies 5 into show's n and 2 into show's r; the call nCr(n, r) on line 8 then copies those two values again into nCr's own n and r. Same spelling, four separate boxes. Arguments bind by position, not by name, so the first value always becomes n whatever it was called at the call site — and nothing nCr does to its n can reach back out.
show is void; fact and nCr return values. x = nCr(5, 2) stores 10. x = show(5, 2) stores nothing at all — None in Python, undefined in JavaScript, a compile error in Java or C. That is why nCr returns instead of printing: printing is a side effect, and something else in the program needed the number.
05 Cheat sheet
What gets asked about functions and scope
| Construct | What actually happens | The gotcha |
|---|---|---|
| fact(5) — a call | A frame is pushed; the parameter k is created as a local holding 5 | Arguments bind by position, not by name. |
| return p | Hands one value to the caller and destroys the frame | Code after return in that branch never runs. |
| show(5, 2) — void | Prints and hands back nothing | x = show(5,2) stores nothing, not 10. |
| Local variable | Created at the call, destroyed at the return | Two calls to fact never share p or i. |
| Global variable | One box outside every function, alive for the whole run | Reading is free; assigning needs global in Python. |
| Shadowing | A local of the same name hides the global inside that function | The global is untouched and reappears after the return. |
| Passing a number | The value is copied into the parameter | x = x + 1 moves only the copy. |
| Passing an array or object | The reference is copied — two names, one object | a[0] = 6 is visible to the caller. |
06 Where & why
When to split, and what to hand back
Splitting code into functions is not automatically better. Every call costs a frame and a jump, and a badly placed boundary makes code harder to follow, not easier. The decision that actually shows up in interviews is narrower than “should I write a function”: should this function return a new value, or change the object it was handed?
Reach for a function when…
Do something else when…
| Situation | Better choice | Why |
|---|---|---|
| You want the caller's number to change | Return the new value and let the caller assign it | Reassigning a parameter only moves the copy. n = bump(n) is explicit and works in every language. |
| The function reorders or edits the list it was given | Copy the argument first, or return a new list | An array argument shares one object. A helper that quietly mutates its input is the classic “it worked yesterday” bug, and the return value still looks correct. |
| Several functions need the same value | Pass it as a parameter | A global makes the result depend on state you cannot see at the call site, and makes the function impossible to test on its own. |
| The block is two lines and used once | Leave it inline | A wrapper called once adds a frame, a jump and one more hop for the reader, and the name says nothing the code did not. |
07 Interview questions
Say these out loud
Questions 7 to 9 — pass by value, the number that would not change, the list that did — are the ones that separate people who have read about parameters from people who have traced them.
What is a function, and why write one instead of more lines in main?
What is the difference between a parameter and an argument?
What does scope mean?
What actually happens in memory when a function is called?
What does return actually do?
Void versus value-returning — what is the difference?
Is Java pass by value or pass by reference?
I changed a number inside a function and the caller's variable did not change. Why?
Then why does sorting a list inside a function change the caller's list?
What is shadowing?
Why are global variables discouraged?
When would you not extract a function?
08 Practice problems
Draw the frames before you write the code
For each one, sketch a box per frame plus a box for any object on the heap, then decide which arrows point at the same box. The answer usually falls out of the drawing.