← Fundamentals
Guided lesson 5 questions · 6 languages

Return values and multiple results

Where a result goes, what a call gives when there is no result, and how to give back two.

A call becomes the value its function returns. A function with a result type must give a value on every path, and a function with two results gives them back together, in an order or with names that every caller must match.

Before you begin

Why this matters

Many bugs are a result that went nowhere: computed and thrown away, missing on one path, or two results taken in the wrong order. Knowing where a result goes lets you find these by reading the code.

What this helps you reason about

  • Predict what a call gives back on every path, including undefined.
  • Let TypeScript check that every path returns a value.
  • Give back two results so that callers cannot mix them up.

A TypeScript function gives back one value. This lesson follows that value to the place of the call, and shows what a call gives when there is no value, when one path forgets it, and when there are two.

Already know this? The short version
  • A call is an expression: its value replaces the call wherever it was written, and a call on its own line throws the value away without a warning.
  • A void function returns undefined; TypeScript refuses to use that value as a number or string.
  • Without a written result type, a path that reaches the closing brace makes the type T | undefined and the call gives undefined; with : T written, the compiler reports TS2366 instead.
  • There are no multiple results: return an object, read by property names, or an array, read by position, where a swap compiles silently.

Go to the final check· Go to the practice tickets

Words in this lesson (6)
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.
expression statement
An expression written on its own as a statement, such as a call on its own line. Its value is thrown away.
undefined
A special JavaScript value that means “no value yet”. A let variable declared without a value holds undefined.
void
A result type that means a function gives back no useful value. A call to it still gives undefined.
early return
A return that ends the call before the function’s last line, usually inside an if. The lines after it do not run in that call.
object
A value that groups named properties, written with braces: { value: 3 } is an object with one property, value.

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 / Result

Where does a returned value go?

As Function calls showed, return ends a call, and the returned value takes the place of the call in the caller’s code. A call is an expression, so its value can go wherever a value can: into a variable, into a larger expression, or straight into another call as an argument.

In cost(packs(12)), packs runs first and gives 3. That 3 is never stored under a name. It becomes cost’s argument, so cost’s parameter count starts with 3.

A call can also stand on its own line, as an expression statement. Then the call runs and its value is thrown away. TypeScript accepts such a line and gives no warning that packs(20) computed 5 for nothing.

main.ts
function packs(tiles: number): number {
  return tiles / 4;
}
function cost(count: number): number {
  return count * 25;
}
const total = cost(packs(12)); // 75
packs(20); // computes 5 and throws it away

A call becomes its value; the value is stored, used, passed on, or thrown away.

Precisely: what the compiler checks

The compiler checks that a result’s type fits the place it goes, not that it is used. Linting tools can report some dropped values: the typescript-eslint rule no-floating-promises reports a Promise that is never awaited. No common rule reports a dropped number.

Experiment

Pass a result straight into another call

packs(12) is an expression whose value is packs’s result. Here that expression is cost’s argument, so the result becomes cost’s parameter without being stored in the caller. A call on its own line is an expression statement: the call runs, and its result is thrown away.

Why it matters Follow a result to the place where its call was written. If that place is a line of its own, the result is lost, and the compiler accepts it without a warning.

Predict what count holds in cost’s frame, and whether any variable in the caller ever holds the 3. Then add the line packs(20); just before the line that prints, and predict whether the output changes.

function packs(tiles: number): number {
  return tiles / 4;
}

function cost(count: number): number {
  return count * 25;
}

console.log(cost(packs(12)));
What to look for

Step into packs: its frame shows tiles 12. After packs returns, the stepper stops in cost, whose frame shows count 3. <top level> lists no variable that holds the 3: the result went straight from packs into cost’s parameter. The run prints 75. After the edit, the stepper first stops in packs with tiles 20 and then goes back to <top level>; no variable receives the 5, and the run still prints 75. <top level> is the stepper’s name for the code outside every function. The panel lists packs and cost as function packs(tiles) and function cost(count): a function declared with function is also a variable. Step sometimes stops on the same line twice; that is normal for this debugger.

Expected output

75

02 / No result

What does a function without a result give back?

Some functions are called only for what they do, such as printing. Their result type is written void: a result type that means the function gives back no useful value. A void function ends at its closing brace, or earlier at return; with no value.

A call to it still gives a value, undefined. TypeScript lets you store it, but not use it as a number: const n: number = logPacks(12); is a compile error, Type 'void' is not assignable to type 'number'.

main.ts
function logPacks(tiles: number): void {
  console.log(tiles / 4, "packs");
}
const done = logPacks(12); // prints 3 packs
console.log(done); // undefined

A void function’s call gives undefined, and TypeScript refuses to use it as a number.

Precisely: void in a function type

A function type with a void result, such as (n: number) => void, also accepts a function that returns something; the caller simply ignores the value. That is why a callback whose last expression returns a number can be passed where a void callback is expected. A declared function with : void, as above, may not return a value.

03 / Every path

What happens when a path ends without return?

An early return ends the call before the function’s last line, usually inside an if. A function can have several. Each path through the function must end at one of them.

If a path reaches the closing brace instead, the call gives undefined. When the result type is not written, TypeScript works it out from the return lines. Without : string and without its last return, boxLabel below would get the type string | undefined: a string, or undefined. The missing case would compile, and boxLabel(20) would give undefined.

With the result type written, a missing return is a compile error: Function lacks ending return statement and return type does not include 'undefined'.

main.ts
function boxLabel(tiles: number): string {
  if (tiles < 20) {
    return "small box";
  }
  if (tiles > 20) {
    return "large box";
  }
  return "exactly 20";
}

Write the result type, so TypeScript checks that every path returns.

Coming from Java: javac refuses a missing return on any path. TypeScript refuses it only when the result type is written and does not include undefined.

Precisely: noImplicitReturns

The compiler option noImplicitReturns reports every path that ends without return, even when the result type allows undefined, with the message Not all code paths return a value. The stepper does not set it.

Experiment

Reach the closing brace without return

Each return ends the call at once. For 20, neither test is true, so the run reaches the closing brace, and a call that ends without return gives undefined. TypeScript worked out the result type string | undefined from the code, so it accepted the gap until the type was written as string.

Why it matters Write a function’s result type. Then TypeScript checks that every path ends with a return, and a gap is found before the program runs.

Predict both printed lines. Then write : string after (tiles: number) and predict whether the program still runs.

function boxLabel(tiles: number) {
  if (tiles < 20) {
    return "small box";
  }
  if (tiles > 20) {
    return "large box";
  }
}

console.log(boxLabel(12));
console.log(boxLabel(20));
What to look for

For 12, the stepper stops on the first if and on return "small box", then goes back to <top level>; the lines after the return never run. For 20, it passes both ifs and stops at the closing brace. The run prints small box, then undefined. After the edit, nothing runs and the output panel stays empty: the compiler reports Line 1: Function lacks ending return statement and return type does not include 'undefined'.

Expected output

small box
undefined

04 / Two results

How can a function give back more than one value?

A function returns one value. To give back two numbers, put them in one value: an object, with a name for each part, or an array.

split below returns an object. The caller reads each part by its property name, so the order in which the properties are written does not matter. Math.floor rounds a number down to a whole number, and % gives the remainder of a division.

With an array, return [packs, left], the caller picks the parts by position: const [packs, left] = split(14); uses the bracket form from Assignment and reassignment. Then the order matters, and two names written the wrong way round still compile.

main.ts
function split(tiles: number) {
  return { packs: Math.floor(tiles / 4), left: tiles % 4 };
}
const order = split(14);
console.log(order.packs, order.left); // 3 2

For several results, return an object: each part has a name, and no caller can take them in the wrong order.

Coming from Go: There are no multiple results. Return an object, and the caller reads the parts by name instead of receiving them in order.

Coming from Python: return packs, left does not return two values: TypeScript reports Left side of comma operator is unused and has no side effects. Write the brackets or the braces.

Experiment

Return an object with two named parts

split returns one object. The caller reads each part by its property name, order.packs and order.left, so the order in which the properties were written makes no difference.

Why it matters Return an object when a function has several results: each part has a name, and a caller cannot take them in the wrong order. With an array, the order matters again.

Predict both printed numbers. Then change the return line to return { left: tiles % 4, packs: Math.floor(tiles / 4) }; and predict again.

function split(tiles: number) {
  return { packs: Math.floor(tiles / 4), left: tiles % 4 };
}

const order = split(14);
console.log(order.packs, "packs,", order.left, "left over");
What to look for

Step into split: its frame shows tiles 14, while <top level> shows order as not initialized yet, the stepper’s words for a const whose line has not run. After the return, order shows {packs: 3, left: 2}, and the run prints 3 packs, 2 left over. After the edit, order shows {left: 2, packs: 3}, and the run still prints 3 packs, 2 left over.

Expected output

3 packs, 2 left over

05 / Try it

Can you follow a fresh example?

Follow each call to the place where it was written. The next lesson, Recursion, uses a call’s result inside the function that made the call: each call combines the result of a smaller call.

main.ts
function tins(walls: number): number {
  if (walls === 0) {
    return 0;
  }
  return walls * 2 + 1;
}

tins(3);
const needed = tins(4) + tins(0);
console.log(needed);

What is printed, and what happens to the result of tins(3)?

Reveal the reasoning

9. tins(3) gives 7, but the call stands on its own line, so the 7 is thrown away. tins(4) gives 9. tins(0) returns early with 0, and the line after its if never runs. So needed is 9 + 0.

Practice tickets

  1. RET-101 · Homes with pets do not get the longer cleaning time → Free
  2. RET-102 · The energy screen charges for solar power instead of grid power → Sign in · Pro
  3. RET-103 · A fridge at exactly 0 degrees shows no data → Sign in · Pro

Keep exploring

Recursion →

Primary references