Skip to content

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:

JavaScript
function greet() {
  console.log("Hello there!");
  console.log("Welcome to the lesson.");
}

greet();
greet();
Output
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:

JavaScript
function announce() {
  console.log("The function body ran.");
}

console.log("Before the call");
announce();
console.log("After the call");
Output
Before the call
The function body ran.
After the call

JavaScript 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.

JavaScript
function greet2(name) {
  console.log("Hello, " + name + "!");
}

greet2("Ana");
greet2("Ben");
Output
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:

JavaScript
function introduce(name, city) {
  console.log(name, "lives in", city);
}

introduce("Ana", "Lisbon");
introduce("Lisbon", "Ana");
Output
Ana lives in Lisbon
Lisbon lives in Ana

The 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:

JavaScript
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);
Output
A large pizza with cheese
A small pizza with mushroom
A medium pizza with cheese
...and extra sauce

size 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.

JavaScript
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);
Output
12
70
82

The 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:

JavaScript
function double(number) {
  console.log(number * 2);
}

const answer = double(21);
console.log("The variable holds:", answer);
Output
42
The variable holds: undefined

The 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:

JavaScript
function double2(number) {
  return number * 2;
}

const answer2 = double2(21);
console.log("The variable holds:", answer2);
console.log(double2(5) + double2(10));
Output
The variable holds: 42
30

return 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:

JavaScript
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]));
Output
-2
null

The 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:

JavaScript
const square = (number) => number * number;
console.log(square(5));
console.log(square(12));
Output
25
144

When a function has exactly one parameter, the parentheses around it are optional:

JavaScript
const cube = number => number ** 3;
console.log(cube(3));
Output
27

Both 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:

JavaScript
const isEven = (number) => {
  const remainder = number % 2;
  return remainder === 0;
};

console.log(isEven(4));
console.log(isEven(7));
Output
true
false

Arrow 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:

JavaScript
console.log(sayHi());

function sayHi() {
  return "Hi!";
}
Output
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:

JavaScript
console.log(sayBye());

const sayBye = () => "Bye!";
Output
ReferenceError: Cannot access 'sayBye' before initialization

In 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.

JavaScript
let message = "I am global";

function show() {
  let message = "I am local";
  console.log("Inside:", message);
}

show();
console.log("Outside:", message);
Output
Inside: I am local
Outside: I am global

Declaring 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:

JavaScript
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]));
Output
3

i 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.

JavaScript
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));
Output
55
6765
832040

Walking 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

JavaScript
const makeObj = () => { name: "Ada" };
console.log(makeObj());
Output
undefined

JavaScript 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:

JavaScript
const makePoint = (x, y) => ({ x: x, y: y });
console.log(makePoint(3, 4));
Output
{ x: 3, y: 4 }

Letting a missing return value poison later arithmetic

JavaScript
function square(number) {
  console.log(number * number);
}

const result = square(6);
console.log(result + 1);
Output
36
NaN

square 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

JavaScript
function greet3(name, greeting) {
  return greeting + ", " + name;
}

console.log(greet3("Ana"));
console.log(greet3("Ana", "Hi", "extra"));
Output
undefined, Ana
Hi, Ana

JavaScript 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.