JavaScript lesson 9 of 9
JavaScript Functions: Declaring, Calling, and Returning
Learn JavaScript functions from scratch - function declarations, arrow functions, parameters and default values, return values, and local versus global scope.
Published · Every example on this page was run before it was published.
By now you can store values, make decisions, and repeat work with a loop. The next problem you will run into is not about any single feature — it is about size. As a program grows, the same handful of lines keeps reappearing in slightly different places, and one long script becomes impossible to hold in your head. A function solves both problems at once: it lets you give a name to a piece of behaviour, write that behaviour exactly once, and then use it as often as you like just by saying its name.
What a Function Is
A function is a named block of code that only runs when you ask it to. JavaScript gives you two main ways to write one, and this lesson covers both, starting with the function declaration:
function greet() {
console.log("Hello there!");
console.log("Welcome to the lesson.");
}
greet();
greet();Hello there!
Welcome to the lesson.
Hello there!
Welcome to the lesson.Four pieces make up that definition. The keyword function tells JavaScript a function is being
defined. greet is the function name, following the same camelCase convention as a variable name.
The empty parentheses will soon hold inputs. And the curly braces hold the function body, the code
that belongs to the function.
Writing greet() is a function call: the parentheses are the instruction to actually run the body.
Leave them off and nothing happens, because the bare name greet just refers to the function without
running it. Defining and calling are genuinely separate events:
function announce() {
console.log("The function body ran.");
}
console.log("Before the call");
announce();
console.log("After the call");Before the call
The function body ran.
After the callJavaScript read the function block first, but it did not execute the console.log inside it — it
simply stored the body under the name announce and moved on. Only the call line released it.
Parameters and Arguments
A function that always does exactly the same thing is limited. Most functions need an input, and that is what the parentheses are for. A parameter is a name listed in the function's own definition; an argument is the actual value you hand over when you call it.
function greet2(name) {
console.log("Hello, " + name + "!");
}
greet2("Ana");
greet2("Ben");Hello, Ana!
Hello, Ben!One definition, two calls, two different results. A function can take several parameters, separated by commas, and by default JavaScript matches arguments to parameters strictly by position — first to first, second to second:
function introduce(name, city) {
console.log(name, "lives in", city);
}
introduce("Ana", "Lisbon");
introduce("Lisbon", "Ana");Ana lives in Lisbon
Lisbon lives in AnaThe second call is not an error as far as JavaScript is concerned — it filled the slots in the order it was given them and printed a perfectly formed sentence that happens to be nonsense. Getting the order right is your responsibility, not the language's.
A parameter can carry a default value, used whenever the caller does not supply that argument at all:
function orderPizza(size, topping = "cheese", extraSauce = false) {
console.log("A", size, "pizza with", topping);
if (extraSauce) {
console.log("...and extra sauce");
}
}
orderPizza("large");
orderPizza("small", "mushroom");
orderPizza("medium", "cheese", true);A large pizza with cheese
A small pizza with mushroom
A medium pizza with cheese
...and extra saucesize has no default, so leaving it out entirely still fills the slot with undefined rather than
raising an error — more on that in the Common Mistakes section. Note that JavaScript matches arguments
by position only; there is no way to skip topping and supply only extraSauce by name the way you
can with some other languages' functions. If a function has several optional settings and you want to
pick and choose which ones to override, the common JavaScript pattern is to accept a single object
parameter instead of a long list of positional ones.
Sending a Value Back with return
Logging shows a result to a human. It does not give the result back to the rest of your program. For
that you need return, which hands a value out of the function to whoever called it.
function areaOfRectangle(width, height) {
return width * height;
}
const small = areaOfRectangle(3, 4);
const large = areaOfRectangle(10, 7);
console.log(small);
console.log(large);
console.log(small + large);12
70
82The call areaOfRectangle(3, 4) does not just run — it becomes the value 12, so it can be stored in
a variable, added to something else, or passed straight into another function.
A function with no return statement still produces a value: JavaScript gives back undefined. This
surprises nearly everyone once:
function double(number) {
console.log(number * 2);
}
const answer = double(21);
console.log("The variable holds:", answer);42
The variable holds: undefinedThe 42 appeared because the function logged it. The variable is empty because nothing was returned.
Swap console.log for return and the value survives the call:
function double2(number) {
return number * 2;
}
const answer2 = double2(21);
console.log("The variable holds:", answer2);
console.log(double2(5) + double2(10));The variable holds: 42
30return also ends the function immediately — any lines after it never run. That makes it a neat way to
leave early as soon as an answer is known:
function firstNegative(numbers) {
for (const number of numbers) {
if (number < 0) {
return number;
}
}
return null;
}
console.log(firstNegative([4, 7, -2, 9, -8]));
console.log(firstNegative([1, 2, 3]));-2
nullThe loop stops the instant it meets -2; the -8 further along is never even looked at. null is a
deliberate choice for "nothing found" here — a clear signal, chosen on purpose, exactly the way the
lesson on variables described it.
Arrow Functions
JavaScript's second way to write a function is the arrow function, a more compact syntax built
around =>, usually assigned to a const:
const square = (number) => number * number;
console.log(square(5));
console.log(square(12));25
144When a function has exactly one parameter, the parentheses around it are optional:
const cube = number => number ** 3;
console.log(cube(3));27Both of those have a concise body: a single expression, with no curly braces and no return
keyword, whose value is automatically returned. For anything longer than one expression, use a
block body instead, with curly braces and an explicit return, exactly like a function declaration:
const isEven = (number) => {
const remainder = number % 2;
return remainder === 0;
};
console.log(isEven(4));
console.log(isEven(7));true
falseArrow functions are extremely common in modern JavaScript, especially for short functions passed as
arguments to other functions — you've already used this shape with .map(), .filter(), and
.forEach() in earlier lessons.
Function Declarations Are Hoisted, Arrow Functions Are Not
Here is a real difference between the two styles. A function declaration is hoisted: JavaScript makes the whole function available throughout its scope before the code actually runs line by line, so you can call it before the line where it's written:
console.log(sayHi());
function sayHi() {
return "Hi!";
}Hi!An arrow function assigned to a const, by contrast, follows the same temporal dead zone rule as any
other const you met in the lesson on variables — it does not exist yet at all until its declaration
line actually runs:
console.log(sayBye());
const sayBye = () => "Bye!";ReferenceError: Cannot access 'sayBye' before initializationIn practice this means function declarations are more forgiving about where you place them in a file, while arrow functions need to be declared before anything that calls them.
Local and Global Scope
Scope is the region of a program where a particular name can be seen. Every function call creates a fresh private workspace, and any variable declared inside the function lives only in that workspace — it is local, created when the call starts and thrown away when the call ends. A variable declared at the top level of your file, outside every function, is global and can be read from anywhere.
let message = "I am global";
function show() {
let message = "I am local";
console.log("Inside:", message);
}
show();
console.log("Outside:", message);Inside: I am local
Outside: I am globalDeclaring message inside show did not touch the global one — JavaScript created a brand-new, local
message that only exists while show is running. Parameters follow the same rule: they are local to
the function they belong to, which is why the same name can be reused freely across different
functions without them ever interfering with each other.
Blocks — the curly braces after an if or a for — create their own smaller scope too, for anything
declared with let or const inside them:
function countPositives(numbers) {
let count = 0;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] > 0) {
count++;
}
}
return count;
}
console.log(countPositives([3, -1, 4, -2, 5]));3i only exists inside the for loop itself, while count was declared at the top of the function and
is visible throughout it, including after the loop ends — this is exactly why count had to be
declared outside the loop.
A Worked Example
This function returns the nth Fibonacci number, remembering answers it has already worked out so it never repeats the same calculation twice — a technique called memoization. It combines a default parameter, recursion, and an object used as a cache.
function fibonacci(n, memo = {}) {
if (n in memo) {
return memo[n];
}
if (n <= 1) {
return n;
}
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
return memo[n];
}
console.log(fibonacci(10));
console.log(fibonacci(20));
console.log(fibonacci(30));55
6765
832040Walking through it: memo = {} is a default parameter, so a caller who only passes n gets a fresh,
empty cache object automatically. Because default parameter expressions are evaluated fresh on every
call that omits the argument, calling fibonacci(20) right after fibonacci(10) starts with a brand
new empty memo rather than reusing anything left over from the first call — each top-level call gets
its own cache.
Inside the function, n in memo checks whether this value of n has already been solved — remember
from the lesson on objects that object keys are always strings, so n in memo is really checking for
the string form of n, and it works correctly because every read and write in this function goes
through the same coercion. If the answer is cached, it's returned immediately. The two base cases,
fibonacci(0) and fibonacci(1), are answered directly without touching the cache at all. Every other
call computes its answer by calling fibonacci recursively on the two smaller numbers before it,
stores the result in memo[n] before returning it, and every one of those recursive calls threads the
same memo object through by passing it explicitly — which is exactly what lets later calls reuse
work that earlier calls already did.
Common Mistakes
Forgetting parentheses around an object literal in an arrow function
const makeObj = () => { name: "Ada" };
console.log(makeObj());undefinedJavaScript always treats a { immediately after => as the start of a block body, never as an object
literal, so { name: "Ada" } here is read as a block containing a labeled statement, not a
returned object — and a block with no explicit return gives back undefined. Wrapping the object in
parentheses removes the ambiguity, forcing JavaScript to treat it as an expression:
const makePoint = (x, y) => ({ x: x, y: y });
console.log(makePoint(3, 4));{ x: 3, y: 4 }Letting a missing return value poison later arithmetic
function square(number) {
console.log(number * number);
}
const result = square(6);
console.log(result + 1);36
NaNsquare prints 36 but never returns anything, so result is undefined. undefined + 1 does not
throw — it quietly becomes NaN, because JavaScript tries to convert undefined to a number for the
addition and fails. The bug shows up several lines away from its real cause, which is the missing
return inside square.
Assuming JavaScript checks how many arguments you pass
function greet3(name, greeting) {
return greeting + ", " + name;
}
console.log(greet3("Ana"));
console.log(greet3("Ana", "Hi", "extra"));undefined, Ana
Hi, AnaJavaScript never validates argument counts. Leaving out greeting entirely does not raise an error —
the parameter simply becomes undefined, which then gets silently converted to the text "undefined"
the moment it's concatenated into a string. Passing an extra, unexpected "extra" argument on the
second call is just as quietly accepted and ignored. Neither mistake crashes your program, which is
exactly what makes both of them dangerous — the wrong output can travel a long way before anyone
notices.
Next Steps
The linked practice problem, nth-fibonacci-memoized, is the natural place to take this. It asks you
to write a function that returns the nth Fibonacci number and to remember answers it has already worked
out, which means calling your own function from inside itself and carrying a cache of known results
between calls — a direct workout for parameters, defaults, and return.
Before you start, open the JavaScript playground and experiment. Write a
function with a default parameter and call it a few different ways. Log the return value of a function
that has no return statement and confirm you get undefined. Then try the missing-parentheses arrow
function trap deliberately, so that the first time you meet it in real code you recognise it instantly
instead of losing an afternoon to it.
When you want a harder one
Interview-style problems graded against hidden tests — a big step up from the exercises. Come back to these when the ideas in this lesson feel comfortable rather than new.