← Fundamentals
Guided lesson 6 questions · 6 languages

Function calls

Arguments in, a result out, and back to the code that made the call.

A call pauses the code that made it. The function runs with its own new variables, which receive the call’s values, and then the program continues at the place of the call with the function’s result.

Before you begin

Why this matters

Almost every line of a program calls a function. If you can follow a call step by step, you can predict what a program prints and say why a function did, or did not, change your variable.

What this helps you reason about

  • Predict the order in which a call’s pieces run, including default values.
  • Tell whether a change made inside a function reaches the code that called it.
  • Pass a function to another function and know when it runs.

This lesson follows one function call from start to finish: the values that go in, the code that waits, and the result that comes back.

Already know this? The short version
  • A call evaluates its arguments left to right, then runs the body in a new frame with parameters bound by position; the caller waits until the call returns, and continues at the call with the result.
  • A default parameter is evaluated inside the call, at every call where the argument is missing or undefined, not once when the function is defined.
  • A parameter holds a copy of the argument’s value: a number is independent, an object reference reaches the same object. Reassigning a parameter never changes the caller’s variable; mutating the object is visible to the caller.
  • return ends the call at once; without one, the call returns undefined. Functions are values and can be passed and called later.

Go to the final check· Go to the practice tickets

Words in this lesson (6)
argument
A value written in a call, such as 3 in area(3, 4). Each argument is computed before the function’s body starts.
parameter
A name in a function’s definition, such as width in function area(width, height). Each call creates its own parameters and gives them the argument values, matched by position.
frame
The record of one function call that has not finished: its parameters and local variables. It is created when the call starts and removed when the call returns.
default parameter
A parameter with a value written in the definition, such as b: number = 10. The value is computed inside the call, when the argument is left out or is undefined.
reference
A value that leads to an object. A variable whose value is an object holds a reference, not a copy of the object, so copying the variable gives a second way to reach the same object.
return value
The value a call gives back to the code that called it. return ends the call and supplies it; a function that ends without return gives undefined.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / The call

What happens when a function is called?

A function is a named piece of code. Calling it, as in area(3, 4), runs that code. The values in the parentheses, 3 and 4, are the arguments. The names in the function’s definition, width and height, are its parameters: variables that get the argument values when the call starts.

While area runs, the code that called it waits at the call. When area finishes, the program continues at the place of the call, and the call’s result, 12, is stored in size.

The stepper shows this with a frame list. A frame is the record of one call that has not finished: its parameters and local variables. Calling area adds a frame on top of the caller’s frame, and returning removes it.

main.ts
function area(width: number, height: number): number {
  const result = width * height;
  return result;
}

console.log("before");
const size = area(3, 4);
console.log("after", size); // after 12

A picture of one call: which code is running and which names exist. It is a model, not a memory layout.

A conceptual model, not a physical memory layout or live code execution.

1 / 3

Before the call

caller
running
area
not called yet

The caller is running. size does not have a value yet.

A call pauses the caller, runs in its own frame, and comes back to the place of the call.

Precisely: frames are a model

The language promises that each call has its own bindings and that a return continues the caller. A frame is a model of that promise. The JavaScript engine, V8 in Node.js, may keep values in processor registers or copy a small function’s body into its caller, as long as the program behaves the same.

Experiment

Follow a call there and back

Calling area pauses the code that called it. area gets a new frame with its own width and height, set from the arguments 3 and 4. When area returns, its frame is removed, and the caller continues at the place of the call with the result, 12.

Why it matters A call always comes back to the place it was made. Two calls in one line run one after the other, each in its own frame.

Predict which frames the frame list shows while area runs, and whether the caller already has size. Then change the call line to const size = area(3, 4) + area(5, 2); and predict: are both area frames ever in the list at the same time?

function area(width: number, height: number): number {
  const result = width * height;
  return result;
}

console.log("before");
const size = area(3, 4);
console.log("after", size);
What to look for

Step until the stop inside area. The frame list shows area on top of <top level>, the code that called it. area’s frame has width 3 and height 4. Select <top level>: it does not list size yet, because size gets its value only when area returns. After the return, area’s frame is gone and size shows 12. After the edit, area’s frame appears twice, one call after the other: first with width 3, then with width 5. The two frames are never in the list together, and the run prints after 22. <top level> is the stepper’s name for the code outside every function. A function declared with function is also a variable, so the panel lists area as function area(width, height). Step sometimes stops on the same line two or three times; that is normal for this debugger.

Expected output

before
after 12

02 / Arguments

When are the arguments computed?

Before a function’s body starts, every argument is computed, from left to right. In add(left(), right()), left() runs first, then right(), and only then does add start, with a = 1 and b = 2. An argument is computed even if the body never uses it.

A default parameter is the exception. Its value, written in the definition as b: number = fallback(), is computed inside the call, after the arguments, and only when the call leaves that argument out. Passing undefined counts as leaving it out.

main.ts
function add(a: number, b: number = fallback()): number {
  return a + b;
}
add(left(), right()); // left, then right, then add
add(left()); // left, then add, which runs fallback()

Arguments run left to right before the body; a default runs inside the call, only when needed.

Coming from Python: A Python default is computed once, when def runs, so a default list is shared by every call. A TypeScript default is computed again at every call that needs it, so b: string[] = [] gives each call a new array.

Experiment

Watch the arguments run first

Arguments are computed from left to right before add starts. A default parameter is different: its expression, fallback(), is computed inside the call, and only when the argument is missing or undefined.

Why it matters A default is computed again at every call that needs it. It does not run when a value is passed, and passing undefined does not count as passing a value.

Predict the order of the printed lines for each of the two calls. Then change add(left()) to add(left(), undefined) and predict whether evaluate default is still printed.

function left(): number {
  console.log("evaluate left");
  return 1;
}

function right(): number {
  console.log("evaluate right");
  return 2;
}

function fallback(): number {
  console.log("evaluate default");
  return 10;
}

function add(a: number, b: number = fallback()): number {
  console.log("add runs");
  return a + b;
}

console.log(add(left(), right()));
console.log(add(left()));
What to look for

In the first call, left and right each get a frame, print, and return before add’s frame appears with a 1 and b 2. In the second call, add’s frame appears first, holding only a; then fallback’s frame appears on top of add: the default is computed inside the call, after the arguments. After the edit, the output does not change. Passing undefined counts as leaving the argument out, so fallback still runs and b is 10.

Expected output

evaluate left
evaluate right
add runs
3
evaluate left
evaluate default
add runs
11

03 / Parameters

What does the function receive?

Arguments are matched to parameters by position: the first argument goes to the first parameter, whatever the variables are called. area(height, width) passes height as width.

Each parameter is a new variable that holds a copy of the argument’s value. As in Variables, a number is copied, so addBonus can change its score without touching the caller’s score. An object argument copies a reference: a value that leads to an object. Both frames then reach the same object, so card.total = 15 is seen by the caller.

Assigning a new object to the parameter, card = { total: 0 }, changes only the parameter. The caller’s variable still refers to the original object.

main.ts
function addBonus(score: number, card: { total: number }): void {
  score = score + 10;
  card.total = card.total + 10;
}
let score = 5;
const card = { total: 5 };
addBonus(score, card);
console.log(score, card.total); // 5 15

A parameter gets a copy of the value; for an object, that value is a reference to the same object.

Coming from Python: There are no keyword arguments: add(b = 1, a = 2) does not name parameters. To pass values by name, pass one object, such as add({ a: 2, b: 1 }).

Coming from React: This is why changing state in place does not re-render. card.total = 15 followed by setCard(card) passes the same reference, so React sees no change. A new object, setCard({ ...card, total: card.total + 10 }), is a new reference, and React renders again.

Precisely: no pass by reference

JavaScript has one passing rule: the parameter is initialized with the argument’s value. For objects, some writers call this rule “pass by sharing”. No function can make the caller’s variable hold a different value; only a return value or a shared object can carry a change back.

Experiment

Change a parameter, then an object

score and card in addBonus are new parameters. score received a copy of the number 5, so changing it changes only addBonus’s score. card received a copy of the reference, so card.total changes the one object both frames reach. Assigning a new object to card changes only addBonus’s card.

Why it matters A function can change an object you pass to it, but it cannot change which value your variable holds. To give the caller a new number or a new object, return it.

Predict both printed numbers. Then change card.total = card.total + 10; to card = { total: card.total + 10 }; and predict again.

function addBonus(score: number, card: { total: number }): void {
  score = score + 10;
  card.total = card.total + 10;
}

let score = 5;
const card = { total: 5 };
addBonus(score, card);
console.log(score, card.total);
What to look for

Step into addBonus and select each frame in turn. After score = score + 10, addBonus’s score shows 15 while <top level>’s score still shows 5. After card.total = card.total + 10, both frames show card as {total: 15}: they reach one object. The run prints 5 15. After the edit, addBonus’s card shows {total: 15} but <top level>’s card keeps {total: 5}, and the run prints 5 5.

Expected output

5 15

04 / Return

How does the result get back to the code that called the function?

return ends the call at once and gives its value back. The value takes the place of the call in the caller’s code: entryFee(4) + entryFee(30) becomes 0 + 3. The lines after a return do not run in that call.

When the call ends, its frame is removed, and its parameters and local variables can no longer be used. Only the returned value reaches the caller. A function that ends without return gives undefined; its type is written void.

main.ts
function entryFee(age: number): number {
  if (age < 5) {
    return 0;
  }
  return 3;
}
const total = entryFee(4) + entryFee(30); // 3

return ends the call; its value replaces the call in the caller’s expression.

Coming from Python: A function that ends without return gives undefined, where Python gives None. If its declared result type is number, TypeScript rejects a path that ends without return.

05 / Functions as values

Can a function be passed to another function?

A function is a value, like a number. You can store it in a variable or pass it to another function, which can call it later. In applyTo(4, triple), triple is passed without parentheses, so it is not called yet; applyTo calls it as step(value).

The parameter type (n: number) => number means “a function that takes a number and returns a number”. (n) => n + 1 is an arrow function: a function without a name, written where it is used.

A function that uses variables from the code around it is called a closure. Those variables can stay alive after the function that created them has finished; how that works is the subject of Stack and heap in the Runtimes module.

main.ts
function applyTo(value: number, step: (n: number) => number): number {
  return step(value);
}
function triple(n: number): number {
  return n * 3;
}
console.log(applyTo(4, triple)); // 12
console.log(applyTo(4, (n) => n + 1)); // 5

Without parentheses a function is passed, not called.

Precisely: what the stepper shows

The variables panel shows a variable that holds a function by the function’s name and parameters, such as function triple(n), or arrow function (n) for an arrow function. An arrow function has no name of its own, so the frame list names its frame by its line, such as arrow function (line 8). When an arrow function is stored in a variable, its frame takes the variable’s name.

06 / Try it

Can you follow a fresh call?

Follow the number and the array separately, and then the returned value. The next lesson, Recursion, uses these two facts: each call gets its own parameters and variables, and a call returns to the code that called it. Its link is at the end of the page.

main.ts
function shift(value: number, list: number[]): number {
  value = value + 1;
  list[0] = list[0] + 1;
  return value;
}

let value = 1;
const list = [1];
const result = shift(value, list);
console.log(value, list[0], result);

What is printed?

Reveal the reasoning

1 2 2. shift’s value is a copy of the caller’s 1, so the caller’s value stays 1. list is a reference to the same array, so list[0] becomes 2 for both. The returned value, 2, is stored in result.

Practice tickets

  1. CALL-101 · Charge both rides to the bus pass → Free
  2. CALL-102 · Grow the plant for two weeks → Sign in · Pro
  3. CALL-103 · Number the orders at each coffee cart → Sign in · Pro

Keep exploring

Recursion →

Primary references