Contents

Chapter 9

Functions: Giving Work a Name

Parameters, return types, and arrow functions.

Three chapters have ended by pointing here. Chapter 3 had you copying variable declarations and changing the names. Chapter 5 had you writing the same two lines per question. Chapter 7 had four pricing rules tangled together where the ordering kept getting harder. All three are the same complaint, and this is the answer.

Naming a piece of work

function greet(personName: string): string {
  return `Hello, ${personName}!`;
}

console.log(greet("Ada"));
console.log(greet("Grace"));
Hello, Ada!
Hello, Grace!

Four parts:

personName only exists inside the function. It’s a stand-in, filled with whatever the caller passed. Call greet twice with different text and you get two different answers from one piece of code, which is the entire point.

Not everything returns something

A function that only prints has nothing to hand back:

function shout(text: string) {
  console.log(text.toUpperCase());
}

shout("hi");
HI

No return, and no return type written. TypeScript works out that this one returns nothing and calls that void. You can write : void yourself if you want it stated, and most code doesn’t bother.

TypeScript works out the return type

You don’t have to write : string on greet. TypeScript can see what comes out:

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

const wrong: string = area(2, 3);
error TS2322: Type 'number' is not assignable to type 'string'.

It knew area gives back a number without being told, and caught the mistake at the point of use.

So why write return types at all? Because an annotation is a check, not a hint. Write : number and TypeScript confirms the body actually delivers one. Leave it off and whatever the body happens to produce becomes the answer, including a mistake.

function broken(n: number): number {
  console.log(n);
}
error TS2355: A function whose declared type is neither 'undefined',
'void', nor 'any' must return a value.

That’s the annotation earning its place: it caught a missing return. Without it, this function would have quietly been a void function and every use of its result would have been the problem instead.

Calling it wrongly

function add(a: number, b: number) {
  return a + b;
}

console.log(add(1));
console.log(add(1, 2, 3));
error TS2554: Expected 2 arguments, but got 1.
error TS2554: Expected 2 arguments, but got 3.

Both caught before running. In plain JavaScript the first would have run with b as undefined and produced NaN, quietly, exactly the kind of thing chapter 6 warned turns up in your output later looking like an answer.

Arrow functions

There’s a second, shorter way to write one:

const double = (n: number): number => n * 2;

console.log(double(21));
42

An arrow function, stored in a const like any other value. When the body is a single expression, the braces and the return both disappear, the value of that expression is what comes back.

For longer bodies you keep the braces, and then you need return again:

const describe = (n: number): string => {
  const half = n / 2;
  return `${n} is ${half} doubled`;
};

Both forms are ordinary TypeScript and you’ll see plenty of each. This book uses function for named, standalone jobs and arrows for short ones, which is roughly what most code does.

Exercise 1 · Pay chapter 5's debt

Chapter 5’s mad-libs ended by noting that every question was the same two lines with a different piece of text. Write

function ask(question: string, answer: string): string

that prints the question and gives back the answer, then rebuild the program around it.

The reading still happens once, up front, exactly as chapter 5 requires. What the function removes is the repetition around it.

Check yourself

1. function f(n) { return n * 2; } with no type on n. What happens?

Not quite. That is what happens to a variable declared as let x;. A parameter is held to a higher standard.

Yes. A loose parameter is a promise to every caller, so TypeScript refuses to let you make it by accident, with no extra configuration needed.

Not quite. TypeScript does not work backwards from the body to invent a parameter type. It wants to be told.

2. Why write : number as a return type when TypeScript can work it out?

Not quite. It compiles fine without one. The return type is inferred from whatever the body returns.

Yes. Without it, a missing return just changes what the function is. With it, you get TS2355 pointing straight at the problem.

Not quite. Types are gone by the time anything runs, which is chapter 22. They are for checking, not speed.

3. Which can be called on a line above the one that defines it?

Yes. The whole file knows about function declarations before any of it runs, so they can be written in whatever order reads best.

Not quite. A const does not exist until its line runs, so this gives you TS2448, used before its declaration.

Not quite. They are close, but ordering is a genuine difference between them and the main practical reason to pick one.

Project

Rules with names

Roughly 45 minutes

Two refactors, both of programs you already have. No new behaviour, which is the point: you’re making working code clearer without changing what it does.

Part one: the ticket pricing from chapter 7.

That project ended with four rules interacting and an ordering problem that got worse as you added a fifth. Split it into named functions:

function basePrice(age: number): number
function ticketPrice(age: number, weekend: boolean): number

basePrice answers one question, what this age pays before any surcharge. ticketPrice handles the free case and the weekend surcharge, and calls basePrice for the part it doesn’t own.

The free-under-5 rule that kept fighting the surcharge stops fighting the moment one function owns “is this free” and returns early. Rules that used to interact by accident now interact on purpose, in one place you can point at.

Check it still works. Run the same boundary ages as chapter 7, 4, 5, 12, 13, 64, 65, on both a weekday and a weekend, and confirm every answer matches what you got before. A refactor that changes an answer isn’t a refactor, it’s a bug.

Part two: the guessing game from chapter 8.

Pull the comparison out into a function that turns a guess into a verdict:

function judge(guess: number, secret: number): string

giving back "too low", "too high" or "right". The loop keeps the counting and the break, and the deciding moves out.

Now the loop reads like what it does rather than how it does it, and you can test judge on its own without playing a whole game.

Stretch: write isEven(n: number): boolean and use it to make chapter 8’s continue example read as if (isEven(i)) continue;. Notice the function is shorter than its own name and the line still got clearer. That’s naming doing work, not code reduction.