Viscosity
Review
Part I · JavaScript for engine builders · Day 1

1. Functions: naming a piece of work

Every interpreter, compiler and engine in this book is built out of one humble idea: give a piece of work a name, then use it as often as you like.

About 17 minutes, including the exercises and quiz
By the end you will be able to
  • Write a function with parameters and a return value, and call it.
  • Explain the difference between a parameter and an argument.
  • Predict what a function returns, including when it returns nothing.
  • Explain why variables made inside a function vanish when it finishes.

Same work, written twice

You already know enough JavaScript to get real work done. Say you’re working out the area of two rectangles:

const w1 = 3;
const h1 = 4;
console.log(w1 * h1); // 12

const w2 = 5;
const h2 = 2;
console.log(w2 * h2); // 10

That works. But look at what’s going on: the idea “multiply width by height” appears twice, glued to particular numbers each time. If you had a hundred rectangles you’d write it a hundred times. And if you got it wrong (say you meant to add a border first), you’d have to fix it in a hundred places.

What you really want is to write the idea down once, give it a name, and then just say the name.

Intuition

A function is a recipe card. You write the steps once (“multiply the width by the height”), give the card a title (area), and put it on the shelf. Whenever you need an area, you don’t rewrite the recipe. You say “do area, with these numbers”, and you get the answer back.

A machine with a slot and a chute

Here is the picture to keep in your head. A function is a little machine. You drop a value in the top. The machine does its work. A value comes out of the bottom.

The function double: the argument 3 goes in through the parameter n, the body computes n times 2, and 6 comes out as the return value.ARGUMENT3function double(n)return n * 2;the body: the workPARAMETER n = 36RETURN VALUE
A function turns an input into an output. The value you pass in (the argument, 3) lands in the function's parameter n. The body does the work. The value after return (6) comes out.

In code, that machine looks like this:

function double(n) {
  return n * 2;
}

console.log(double(3)); // 6
console.log(double(10)); // 20

Read it out loud: “function double, taking n: return n times two.” The first three lines define the function: they build the machine and put it on the shelf. Nothing is computed yet. The console.log lines call it: each double(...) runs the machine once.

Definition

A function is a named block of code that can be run on demand. You call it by writing its name followed by parentheses: double(3). Inside the parentheses go the arguments: the values you hand it. In the definition, the names in parentheses (n) are the parameters: the names the function uses for whatever it was handed.

People mix up parameter and argument all the time, so here’s the trick for keeping them apart. The parameter is the placeholder name, written once in the definition. The argument is the actual value, and it can be different on every call. In double(3), the argument is 3 and the parameter n holds it for the length of that call.

Try it. Change the numbers, call double a few more times, or make a triple function of your own.

Try itA function that doubles
function double(n) {
  return n * 2;
}

console.log(double(3));
console.log(double(10));
console.log(double(3) + 1);

Getting a value back out: return

The word return does two jobs at once:

  1. It decides what value comes out of the call. The call double(3) becomes 6, as if you’d typed 6 there yourself. That’s why you can use a call anywhere you could use a value: double(3) + 1 is 7.
  2. It stops the function immediately. Any lines after a return that runs are skipped.

The second job makes return handy inside an if:

function sign(x) {
  if (x < 0) {
    return 'negative';
  }
  if (x === 0) {
    return 'zero';
  }
  return 'positive';
}

console.log(sign(-5)); // negative
console.log(sign(7));  // positive

When x is -5, the first return runs and the function is finished. The other two checks never happen.

What if a function never reaches a return? Then the call still produces a value: the special value undefined, which is JavaScript’s way of saying “nothing”.

function greet(name) {
  console.log('Hello, ' + name);
}

const result = greet('Ada');
console.log(result); // undefined

greet prints something, but it doesn’t return anything. Printing and returning are different things, and mixing them up is one of the most common beginner bugs. Printing shows a value to a human. Returning hands a value back to the code that made the call, so that code can keep working with it.

Pitfall

console.log inside a function does not give the value back to the caller. If you write const a = double(3); and double only prints 6, then a is undefined. If the caller needs the value, return it.

Each call gets a fresh page

Here is the idea that matters most later in the book. Every time you call a function, JavaScript gives that call its own fresh, private page to write on. The parameters are written on it first, holding the arguments. Any variables the body creates with let or const go on that page too. They are called local variables.

When the call finishes, the page is thrown away. Only the returned value survives. Step through it:

Step throughWhat happens during a call
function area(w, h) {
const a = w * h;
return a;
}
const r = area(3, 4);
console.log(r);
The call's page of local variables, and the outer variable rTHE CALL’S PAGE (only while area runs)no call runningOUTSIDEr
JavaScript reads the function first, but nothing runs yet. A function definition is a recipe on a shelf: written down, not cooked.
1/7

That’s why this doesn’t work:

function area(w, h) {
  const a = w * h;
  return a;
}

area(3, 4);
console.log(a);
// ReferenceError: a is not defined

a existed only on the page for that one call, and that page is gone. The outside code never sees it. This is a good thing: two functions can both use a variable called a without stepping on each other, because each call writes on its own page.

Why this matters for engines

That “fresh page per call” really exists inside every JavaScript engine, and you’ll build one yourself. In Part III, our interpreter keeps an environment for each call: literally a little table of names and values. In Part IV, the virtual machine keeps them in a stack frame. In Part VII, you’ll see why it matters to Viscosity whether a value can escape its page, because values that never escape can be stored however we like.

Functions that use functions

Once a piece of work has a name, other functions can use it. That’s how big programs get built: small, named, trustworthy pieces, stacked up.

function square(x) {
  return x * x;
}

function hypot(a, b) {
  return Math.sqrt(square(a) + square(b));
}

console.log(hypot(3, 4)); // 5

hypot calls square twice, and each of those calls gets its own page (with its own x). Math.sqrt is a function too: it’s built into JavaScript, and you call it exactly the same way.

Every program in this book, from a 50-line calculator to Viscosity itself, is just this idea repeated: name a piece of work, then build bigger work out of named pieces. Crafting Interpreters, the book this one borrows its shape from, builds a whole programming language this way, one small function at a time[3].

Your turn

Each exercise has tests that check your function, hints if you get stuck, and a full worked solution. Try for a few minutes before peeking: the struggle is where the learning happens. The second one is extra practice: do it if you have time, or come back to it later.

ExerciseThe average of two numbers

Write a function average(a, b) that returns the average of its two arguments. For example average(2, 4) should return 3.

function average(a, b) {
  // your code here
}

console.log(average(2, 4));
Extra practiceKeep a number in range

Write clamp(x, lo, hi): it returns lo if x is below lo, returns hi if x is above hi, and otherwise returns x unchanged. Games use this all the time, for example to keep a player inside the screen.

function clamp(x, lo, hi) {
  // your code here
}

console.log(clamp(15, 0, 10));

What you learned

  • A function names a piece of work. Defining it puts it on the shelf. Calling it (name(args)) runs it.
  • Parameters are the placeholder names in the definition. Arguments are the actual values in a call.
  • return sends a value back and stops the function. With no return, a call produces undefined.
  • Printing is not returning.
  • Each call gets a fresh page for its local variables, and the page is thrown away when the call ends.

Next, you’ll meet the first way of holding many values at once: arrays.

Check your understanding

0/6
  1. 1.

    What does this print?

    function f(n) {
      return n + 1;
    }
    console.log(f(f(1)));
  2. 2.

    In function greet(name) { ... } called as greet('Ada'), what is 'Ada'?

  3. 3.

    What does this print?

    function g(x) {
      if (x > 0) {
        return 'yes';
      }
      return 'no';
      console.log('done');
    }
    console.log(g(5));
  4. 4.

    A function prints a number with console.log but has no return. You write const r = f();. What is r?

  5. 5.

    Why can two different functions both have a local variable called total without any conflict?

  6. 6.

    Which line calls a function (rather than defining it)?

References

  1. [1] MDN contributors. Functions (JavaScript Guide). MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions (checked 2026-10-31)
  2. [2] MDN contributors. console: log() static method. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/API/console/log_static (checked 2026-10-31)
  3. [3] Robert Nystrom (2021). Crafting Interpreters. Free web edition at craftinginterpreters.com (© 2015–2021); source, text and test suite at github.com/munificent/craftinginterpreters. github.com/munificent/craftinginterpreters (checked 2026-10-31)