Functions: naming and reusing behaviour · Lesson 18 of 77 · concept
Function expressions and arrow functions
About 50 minutes.
By the end of this lesson you can
- Store a function in a const and call it through that name.
- Write the same function three ways: declaration, function expression and arrow.
- Use an arrow's concise body, and say when you must write return yourself.
- Explain why a function stored in a const cannot be called on a line above it.
- Name what an arrow function does not have, and know where that will matter later.
A function is a value
Explanation
So far a function has been a thing you declare. Here is the idea that unlocks the rest of JavaScript: a function is also a value, like the number 7 or the string "hello". You can put it in a variable, hand it to another function as an argument, or return it from a function — which is lesson six.
Once you accept that, a second way of making a function is obvious. Instead of declaring it, create one and assign it to a const, exactly as you would assign a number. The function has no name of its own in this form; the name is the const you stored it in, and you call it the same way as always.
Function expressions
Explanation
A function expression looks almost like a declaration with the name removed: the keyword function, brackets for the parameters, then the body in braces — all on the right-hand side of an assignment. Because the whole thing is one assignment statement, it ends with a semicolon after the closing brace, which is the small visual difference to watch for.
Everything you know still applies. Parameters, defaults, return, guard clauses — none of that changes. What changes is when the name becomes usable, which is the subject of two sections down.
Arrow functions
Explanation
An arrow function is a shorter way of writing a function expression: the parameters in brackets, then an arrow made of an equals sign and a greater-than sign, then the body. This is the form you will see most often in modern JavaScript, so it is worth being fluent in it even before you need the space it saves.
Arrows have two body styles and the difference is the one thing beginners trip on. If the body is a single expression written directly after the arrow — no braces — that expression's value is returned automatically. This is called a concise body, and there is no return keyword anywhere.
If you write braces after the arrow, you are back to a normal function body with statements in it, and nothing is returned unless you write return yourself. Braces switch the automatic return off. That is the whole rule.
The silent undefined
A common mistake
Take a working concise arrow, add braces around the body so you can add a line to it later, and you have just broken it. The expression is still there, still evaluated, and its value is now thrown away — the function returns undefined and nothing complains.
It is the same failure as lesson three's return-on-its-own-line: legal code that quietly means something else. When an arrow function returns undefined for no visible reason, look for braces that should not be there, or a missing return inside braces that should.
Why a const-stored function cannot be called early
A mental model
Lesson one showed that a function declaration can be called from above. A function stored in a const cannot, and the reason is worth understanding rather than memorising.
The engine knows about the const before the code runs — the name is reserved — but it deliberately refuses to let you use it until execution reaches the line that gives it a value. Between the top of the block and that line, touching the name throws a ReferenceError. That gap has a name in the specification: the temporal dead zone.
This is a feature, not an oversight. A function declaration can honestly be hoisted because its whole body is known in advance. A const's value is whatever the right-hand side works out to when that line runs, so there is nothing sensible to hand you beforehand, and JavaScript refuses to guess.
The practical consequence: with const-stored functions, definition order matters. Define before you use.
What arrow functions do not have
Explanation
An arrow function is not merely shorter. It is missing several things a normal function has, and this course meets them at different points, so here they are named once so that nothing later comes as a surprise.
An arrow has no this of its own and no arguments object of its own — two keywords this course has not needed yet. It cannot be used with new to build objects. And it has no prototype property, which is the one you can observe right now with typeof, and which the exercise below uses to check that you really did write arrows.
For everything you are doing in this module, none of the missing pieces matter. Treat the two forms as interchangeable for now, prefer whichever reads better, and know that the differences exist so that when you meet this in a later module the behaviour is not mysterious.
Which form should you actually use?
Why it matters
Codebases vary, and neither answer is wrong. A common convention: declarations for the main named operations of a file, because they can be defined in whatever order reads best; arrows for short one-expression helpers, and especially for functions handed to other functions, which module five leans on heavily.
The thing that actually matters is not picking a side but being able to read all three forms without slowing down. When you open somebody else's project you do not get a vote on their style.
Recap
Recap
A function is a value and can be stored in a const. A function expression is the keyword function with no name on the right of an assignment; an arrow function is the shorter form. An arrow with no braces returns its single expression automatically; an arrow with braces returns nothing unless you write return. Const-stored functions cannot be called above the line that defines them, so definition order matters for them and not for declarations.
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.
The same idea, stored two ways
A function expression and an arrow function side by side, both stored in a const and both called normally. The last line makes the underlying point: what is in the variable is a function.
1const double = function (n) {2 return n * 2;3};4 5const triple = (n) => n * 3;6 7console.log(double(6));8console.log(triple(6));9console.log(typeof double);- line 1
A function expression: the keyword function with no name after it, on the right of an assignment.
- line 3
The semicolon closes the assignment statement. A declaration would not have one here.
- line 5
The same function as an arrow with a concise body: no braces and no return, because the single expression after the arrow is what comes back.
- line 9
typeof reports "function". The const holds a function value, exactly as another const might hold a number.
What it prints
1218function
Braces switch off the automatic return
Three arrows that look nearly identical. Two of them add one to their input; the third throws the answer away. The only difference is the punctuation.
1const shortWay = (n) => n + 1;2 3const longWay = (n) => {4 return n + 1;5};6 7const noReturn = (n) => {8 n + 1;9};10 11console.log(shortWay(1));12console.log(longWay(1));13console.log(noReturn(1));- line 1
Concise body: no braces, so the value of n + 1 is returned automatically.
- line 3
Braces make this an ordinary body, so the return keyword is required — and is present.
- line 8
Braces again, but no return. The addition is performed and the result is discarded, so the call produces undefined. This is legal code and nothing warns you.
What it prints
22undefined
One observable difference between the forms
A difference you can see today with nothing but typeof. Arrow functions have no prototype property; functions written with the keyword function do. The exercise below uses this to check your work.
1const arrow = (n) => n;2function classic(n) {3 return n;4}5const expression = function (n) {6 return n;7};8console.log(typeof arrow.prototype);9console.log(typeof classic.prototype);10console.log(typeof expression.prototype);- line 8
The arrow has no prototype property at all, so reading it gives undefined and typeof reports "undefined".
- line 9
A function declaration has one, so typeof reports "object".
- line 10
So does a function expression: this is about the keyword function, not about being named or stored in a const.
What it prints
undefinedobjectobject
Calling a const-stored function too early
The failure the temporal dead zone produces. Compare this with lesson one's hoisting example, which is the same shape and works: the only change is how the function was created.
1console.log(later(1));2 3const later = (n) => n;- line 1
This throws a ReferenceError. The name later is known to exist but has not been given its value yet, and JavaScript refuses to let you touch it until line 3 runs.
- line 3
Move this above line 1 and the program works. With const-stored functions, definition order is not optional.
This one does not run hereLine 1 throws a ReferenceError on purpose, so the program stops there and prints nothing. It is here to be read, not run. The exact wording of the message differs between JavaScript engines; the error name, ReferenceError, does not.
Check yourself
Not scored, not stored. Getting one wrong is the useful part.
Two arrows, one pair of braces apart. What does this print?
const half = (n) => n / 2;const halfBlock = (n) => { n / 2;};console.log(half(10));console.log(halfBlock(10));
Which of these arrow functions returns the doubled number?
A file calls format(3) on line 1 and defines const format = (n) => n + "px"; on line 9. What happens?
Write it yourself
Exercise · core · 8 tests
Rewrite two functions as arrows
Both functions in the starter are declarations, and both are wrong. Rewrite each as an arrow function stored in a const, and fix the logic while you are there. isTeenager(age) is true only for ages 13 to 19 inclusive — the starter forgets the upper limit. Write it with a concise body, so no braces and no return. gradeFor(score) returns "A" for 90 and above, "B" for 70 to 89, and "C" below 70. That needs more than one statement, so give it a braced body with explicit returns.
isTeenager returns true or false and is true only for 13 through 19 inclusive. gradeFor returns exactly one of the strings A, B or C, with 90 counting as an A and 70 counting as a B. One test checks that both really are arrow functions: it reads the prototype property, which arrow functions do not have and keyword-function forms do. Both return their answer; neither prints.
Define isTeenager and gradeFor at the top level of your code — the tests call them by name.
Worth remembering
- When does an arrow function return its value automatically?
Only with a concise body: a single expression written straight after the arrow, with no braces. Add braces and nothing is returned unless you write return yourself.
- Why can a function stored in a const not be called on an earlier line?
The name is reserved from the top of the block but has no value until execution reaches the assignment. Touching it before then throws a ReferenceError — the temporal dead zone.
Check this lesson against the source
We wrote the explanation; we did not invent the facts. This is the page that backs them.
MDN — Arrow function expressions (JavaScript reference)
Check there: The page states that an arrow with an expression body returns that expression implicitly while a braced body needs an explicit return, and that arrow functions have no own this or arguments, cannot be used as constructors, and have no prototype property.
Check there: That a function expression is not hoisted the way a declaration is.
Check there: That const declarations are hoisted but uninitialised, so accessing the name before the declaration line throws a ReferenceError (the temporal dead zone).
Prefer to read the source in a structured breakdown? Turn this doc into a breakdown