Return Values

Mental model: Every call produces a value. If you never say what it is, it is undefined.

Level: beginner · about 9 minutes

function double(n) { return n * 2; }
function shout(text) { console.log(text.toUpperCase()); }

const a = double(4);
const b = shout('hey');

console.log(a); // → 8
console.log(b); // → undefined   shout logs, it does not return

Two functions that look similar and do not behave alike.

A return ends the call immediately and hands a value back to the caller. A function with no return, or with a bare return;, evaluates to undefined. Logging is not returning: console.log prints for a human and gives back undefined.

One exit, or many?

Single exit

function fee(kind) {
  let amount = 0;
  if (kind === 'gold') amount = 0;
  else if (kind === 'silver') amount = 5;
  else amount = 10;
  return amount;
}

Early returns

function fee(kind) {
  if (kind === 'gold') return 0;
  if (kind === 'silver') return 5;
  return 10;
}

Early returns win when each branch is independent and short: no let, no reassignment, nothing to hold in your head. A single exit earns its keep when there is shared cleanup or logging at the end. Both are fine; mixing them badly is not.

function broken() {
  return
    { ok: true };   // unreachable, ASI already ended the statement
}

function fine() {
  return (
    { ok: true }
  );
}

console.log(broken()); // → undefined
console.log(fine());   // → { ok: true }

Returning more than one thing

function parseName(full) {
  const [first, ...rest] = full.split(' ');
  return { first, last: rest.join(' ') };     // names travel with the values
}

function minMax(numbers) {
  return [Math.min(...numbers), Math.max(...numbers)];  // order is the contract
}

const { first, last } = parseName('Ada Lovelace');
const [low, high] = minMax([4, 9, 1]);
console.log(first, last, low, high);  // → 'Ada' 'Lovelace' 1 9

An object for named results, an array for ordered ones.

Use an object when the caller should not have to remember an order, and an array when there genuinely is one, as with useState style pairs. Returning a function is the third option, and it is how every factory and every decorator works.

function multiplier(factor) {
  return (n) => n * factor;   // a function is just another value
}

const triple = multiplier(3);
console.log(triple(7));       // → 21

The arrow brace trap

Braces: a body, so you must return

const square = (n) => { n * n };
console.log(square(4)); // undefined

// fixed:
const ok = (n) => { return n * n; };

No braces: the expression is the result

const square = (n) => n * n;
console.log(square(4)); // 16

// returning an object needs parens:
const point = (x) => ({ x, y: 0 });

Braces after => start a block. Anything inside is a statement, and statements do not produce a value for the caller. That is also why an object literal needs wrapping parentheses: without them the { is read as the start of a block, not an object.

const nums = [1, 2, 3];
const doubled = nums.map((n) => { n * 2 });
console.log(doubled);

The braces make a function body, the expression n * 2 is evaluated and thrown away, and the callback returns undefined for every element. map faithfully collects three undefined values. Drop the braces, or add return.

Try it yourself

Four shapes of result

const asValue = (n) => n * 2;
const asArray = (n) => [n, n * 2];
const asObject = (n) => ({ input: n, doubled: n * 2 });
const asFunction = (n) => () => n * 2;

console.log(asValue(5));
console.log(asArray(5));
console.log(asObject(5));
console.log(asFunction(5)());

function stats(numbers) {
  if (numbers.length === 0) return null;    // say "nothing" deliberately
  return {
    count: numbers.length,
    total: numbers.reduce((a, b) => a + b, 0),
  };
}

console.log(stats([1, 2, 3]));
console.log(stats([]));

Change stats to return an array instead of an object and update the caller. Which version would you rather read six months from now?

Exercises

Fix the silent undefined

The starter looks right and returns nothing useful. Fix squareAll(numbers) so it returns a new array of squares, and leaves the input array untouched.

First non-empty value

Write firstNonEmpty(values) which returns the first item that is a string with at least one non-whitespace character. Return null when there is no such item. Use an early return rather than a flag.

Check yourself

What does this log?
undefined — The braces make a block body, so list[0] is evaluated as a statement and discarded. With no return, the call produces undefined. Write (list) => list[0] for the concise body.
How do you return an object literal from a concise arrow?
() => ({ a: 1 }) — Wrap the literal in parentheses so the { is parsed as an object rather than the start of a block. () => ({ a: 1 }) is the idiom, and it turns up constantly in map callbacks.
A function that only calls console.log and has no return. What does the caller receive?
undefined — Printing and returning are unrelated. The call evaluates to undefined, which is why testing a function by logging inside it can look correct while the code around it does nothing useful.

Common mistakes

  • Logging inside a function instead of returning, then finding the caller has undefined.
  • Adding braces to an arrow and forgetting the return, which silently produces undefined.
  • Putting the returned value on the line after return, where automatic semicolon insertion discards it.

Takeaways

  • No return means the call evaluates to undefined.
  • Early returns keep independent branches flat and free of reassignment.
  • Return an object for named results, an array when the order is genuinely the contract.
  • Braces after => need an explicit return, and an object literal needs wrapping parentheses.