Functions: naming and reusing behaviour · Lesson 17 of 77 · practice
Return values and early return
About 55 minutes.
By the end of this lesson you can
- Explain the difference between printing a value and returning one.
- Write a function that hands a value back, and use that value in a bigger expression.
- State what a call evaluates to when the function has no return statement.
- Use an early return to deal with a bad case first and keep the main path flat.
- Recognise the return-on-its-own-line bug and say why it produces undefined.
Printing is for humans; returning is for code
Explanation
console.log puts text on a screen. That is all it does. It is a message to a person, and the program itself gains nothing from it — you cannot add it to a total, compare it, or store it for later.
Returning is the other thing: handing a value back to whoever called the function, so the calling code can use it. A function that returns is a machine that produces something. A function that only prints is a machine that talks about what it is doing.
Almost every function you write in real work returns. Printing is for while you are figuring things out, and for the very edge of a program where a human is finally looking.
The notation, and what it does to control flow
Explanation
Write the keyword return, then a value or an expression. Two things happen at once, and both matter. First, the function stops immediately: no line after the return runs, not even in the same block. Second, the call itself turns into that value.
That second part is the one to sit with. If double is a function that returns n times 2, then the text double(5) IS the number 10 as far as the surrounding code is concerned. You can store it, add to it, compare it, or pass it straight into another call. The call expression has become its result.
You can return anything: a number, a string, a boolean, or later on a whole collection. A function is allowed to have several return statements in different branches; whichever one runs first wins, and the rest of the function never happens.
The vending machine
An analogy
Calling a function is pressing a button on a vending machine. A return is the can dropping into the tray: you walk away holding something you can use.
A function that only prints is a machine that flashes a message saying THANK YOU and gives you nothing. It has communicated. You are still thirsty. If your code needs the can, the function has to drop it in the tray.
No return means undefined
Explanation
A function that reaches the end of its body without hitting a return produces undefined. So does a bare return; with nothing after it. This is not an error and there is no warning: undefined is a genuine value, and it will be stored, printed or added to things quite happily.
That is why the classic beginner bug is so slippery. You write a function that calculates something and prints it, then store the result of calling it, and the result is undefined — because printing is not returning. The calculation happened; the value simply was not handed back.
When a variable holds undefined and you cannot see why, ask this first: does the function I called actually return anything, or does it only print?
Early return: handle the awkward case and leave
A mental model
Because return stops the function on the spot, you can use it to get the awkward cases out of the way at the top. Check the thing that must not happen, return an answer for it immediately, and everything below that point can be written as though the world is well behaved.
This shape has a name — a guard clause — and it is one of the few pieces of style that is close to universally agreed. The alternative is wrapping the real work in an if, then wrapping that in another if, until the interesting code is buried four levels deep and you have to read backwards to know what conditions it depends on.
A good rule of thumb: if you are about to write else, look at whether the if branch ends in a return. If it does, you do not need the else at all, and deleting it moves the main path back out to the left margin where it can be read.
Three ways return goes wrong
A common mistake
Putting the value on the next line. JavaScript will insert a semicolon after a lone return, so return followed by a newline and then the value means return nothing, and the value line becomes unreachable. There is no error message. The value you want must start on the same line as the word return. The second worked example below shows this happening for real.
Writing console.log where you meant return. The function looks like it works — you can see the right number in the output — but every caller receives undefined. Check the body, not the console.
Writing code after a return in the same block and expecting it to run. It cannot. Once a return is taken, the function is over. This is usually a leftover from an edit and it is worth deleting rather than leaving as scenery.
Recap
Recap
return does two things at once: it ends the function immediately, and it turns the call into a value the surrounding code can use. Falling off the end, or writing a bare return, produces undefined. Because return exits on the spot, you can put guard clauses at the top of a function and keep the main path flat and readable. Keep the returned value on the same line as the word return.
Worked examples
Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.
A returned value is usable
The point of returning, in five lines. The result of one call is stored in a variable, printed, and then fed straight back into the same function.
1function double(n) {2 return n * 2;3}4 5const result = double(5);6console.log(result);7console.log(double(result));- line 2
The function stops here and the call becomes this value. Nothing is printed by the function itself.
- line 5
double(5) has become the number 10, so result holds 10. This line is where the value is caught.
- line 7
A returned value can be passed straight back in. double(result) is double(10), which is 20.
What it prints
1020
Printing versus returning, side by side
Two functions that look equally successful in the output, and are not. One of them hands its caller a number; the other hands its caller undefined and merely mentions the number on the way past.
1function logTotal(price) {2 console.log(price * 2);3}4 5function getTotal(price) {6 return price * 2;7}8 9const printed = logTotal(4);10const returned = getTotal(4);11console.log(typeof printed);12console.log(typeof returned);13console.log(returned + 1);- line 2
This prints 8 — that is the first line of output — but the function has no return, so the call produces undefined.
- line 9
printed holds undefined. The 8 you saw went to the screen and nowhere else; it cannot be recovered.
- line 11
Hence "undefined": the calculation ran, but nothing came back.
- line 13
Only a returned value can take part in an expression like this one. 8 + 1 is 9.
What it prints
8undefinednumber9
The return-on-its-own-line bug
Two functions that differ only in where the line break falls. One of them is silently broken, and the output proves it. This is a real bug that reaches production; meeting it here costs you thirty seconds instead of an afternoon.
1function brokenAdd(a, b) {2 return3 a + b;4}5 6function workingAdd(a, b) {7 return a + b;8}9 10console.log(brokenAdd(2, 3));11console.log(workingAdd(2, 3));- line 2
A return with nothing after it on the same line. JavaScript ends the statement here, so the function returns undefined and exits.
- line 3
This line is now unreachable. The function already exited on line 2. No error, no warning, no output.
- line 7
The same expression, kept on the same line as return. This is the only difference between the two functions.
What it prints
undefined5
Early return keeps the main path flat
Three answers from one function, with the awkward case handled first. Notice that there is not a single else, and that the last line is the normal case — the one you read the function to find.
1function describeAge(age) {2 if (age < 0) {3 return "That is not a real age.";4 }5 if (age < 18) {6 return "Not an adult yet.";7 }8 return "An adult.";9}10 11console.log(describeAge(-4));12console.log(describeAge(9));13console.log(describeAge(42));- line 3
The impossible input is dealt with immediately and the function exits. Nothing below this line has to worry about negative ages ever again.
- line 5
No else is needed: if the first check had matched, we would not be here at all.
- line 8
The ordinary answer, at the left margin, easy to find. This is what guard clauses buy you.
What it prints
That is not a real age.Not an adult yet.An adult.
Check yourself
Not scored, not stored. Getting one wrong is the useful part.
This function has three return statements. What does the program print?
function firstPositive(a, b) { if (a > 0) { return a; } if (b > 0) { return b; } return "none";}console.log(firstPositive(5, 9));console.log(firstPositive(-5, 9));console.log(firstPositive(-5, -9));
A function called report has a body containing exactly one line: console.log(total). What does the variable x hold after const x = report(); ?
A function's body is: return, then on the very next line, price * 1.2; — what does calling it produce?
Write it yourself
Exercise · core · 6 tests
Divide, but guard against zero
Write safeDivide(a, b) so that it returns a divided by b — except when b is 0, in which case it returns the string "Cannot divide by zero" instead. The starter divides without checking. Run it first and look at what safeDivide(7, 0) gives you: JavaScript does not raise an error for division by zero, it produces Infinity, which will quietly poison every calculation downstream. Use an early return to deal with the zero case before the division happens.
safeDivide returns a number for every call where b is not 0, including when a is 0 or negative — 10 and 4 give 2.5, not a rounded value. When b is 0 it returns exactly the string "Cannot divide by zero" and performs no division at all. It returns its answer; it does not print anything.
Define safeDivide at the top level of your code — the tests call it by name.
Worth remembering
- What are the two things a return statement does?
It ends the function immediately, so no later line in it runs; and it turns the call expression itself into that value, so the caller can store or use it.
- What does a call produce when the function only prints and never returns?
undefined. Printing sends characters to a human; it hands nothing back to the code. No error is raised, which is why this bug is quiet.
Check this lesson against the source
We wrote the explanation; we did not invent the facts. This is the page that backs them.
MDN — return (JavaScript reference)
Check there: The page states that return ends function execution and specifies the value to be returned, that a function with no return expression returns undefined, and it warns about automatic semicolon insertion when the expression starts on the line after return.
Check there: That undefined is the value of a variable that has not been assigned one.
Prefer to read the source in a structured breakdown? Turn this doc into a breakdown