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 returnTwo 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 9An 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 noreturn, the call producesundefined. 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 inmapcallbacks.- A function that only calls
console.logand has noreturn. What does the caller receive? undefined— Printing and returning are unrelated. The call evaluates toundefined, 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 producesundefined. - Putting the returned value on the line after
return, where automatic semicolon insertion discards it.
Takeaways
- No
returnmeans the call evaluates toundefined. - 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 explicitreturn, and an object literal needs wrapping parentheses.