CyberCodeLab logo — neon green lab flask with terminal symbolCyberCodeLab
Code editor showing a JavaScript function with parameters, filter and map calls, try-catch and return statements — functions deep dive tutorial

web-development · Intermediate · 2026-07-05

JavaScript Functions Deep Dive: Parameters, Returns & Arrow Functions

Go beyond the basics — function declarations vs arrow functions, default parameters, return values, scope and the early-return pattern, building a real tip calculator.

You met functions in JavaScript Basics. Now we go deeper: the three ways to write them, how parameters and returns really behave, and the patterns — like early return — that separate tidy code from spaghetti. We build a working tip calculator along the way.

Three ways to write the same function

// 1. Declaration — hoisted (usable before its line)
function add(a, b) {
  return a + b;
}

// 2. Function expression
const add = function (a, b) {
  return a + b;
};

// 3. Arrow function — the modern favourite
const add = (a, b) => a + b;

The arrow's one-line form has an implicit return — no braces, no return keyword, the expression's value is returned automatically. Our calculator uses this:

const calcTip = (bill, percent) => bill * (percent / 100);

Add braces and the implicit return disappears — (a, b) => { a + b; } returns undefined. That trap catches everyone once.

Parameters, arguments and defaults

Parameters are the placeholders; arguments are the real values you pass. Missing arguments become undefined — unless you set a default:

function greet(name = "friend") {
  return "Hello, " + name + "!";
}

greet();        // "Hello, friend!"
greet("Sara");  // "Hello, Sara!"

Return values: functions as producers

A function that returns is reusable anywhere — in variables, in other calls, in template strings. A function that only console.logs can do exactly one thing. Compare:

const tip = calcTip(1200, 15);       // usable: store it
formatMoney(calcTip(1200, 15));      // usable: feed it onward

Rule of thumb: compute in returning functions, display in one place. The calculator follows it — calcTip and formatMoney compute; only the click handler touches the page.

Scope: where variables live

Variables declared inside a function exist only there:

function demo() {
  const secret = 42;
}
console.log(secret); // ReferenceError

Inner functions can read outer variables (that is how the click handler sees calcTip), but not the reverse. Keeping variables in the smallest scope that works prevents whole categories of bugs.

The early-return pattern

Instead of wrapping your logic in an ever-deeper if, reject bad input first and leave:

if (!bill || bill <= 0) {
  showError();
  return;          // stop here — nothing below runs
}
// happy path continues, unindented

The calculator uses exactly this for invalid bills. It is the single most useful pattern for readable functions.

Try it in the editor

The calculator below combines all of it: an arrow function with implicit return, a classic declaration, Number() conversion of input values, and an early return guarding against bad input.

  1. Enter a bill and press Calculate.
  2. Change the default tip by moving selected to another option in the HTML.
  3. Break it on purpose: remove the early return and enter a negative bill. What happens?

Press Run after each change to see the result instantly.

Practice exercises

All three extend the tip calculator in the editor.

Exercise 1 (easy): Give calcTip a default parameter of 15 for percent, then call it from the console-style test: add console.log(calcTip(1000)); — it should log 150.

Solution:

const calcTip = (bill, percent = 15) => bill * (percent / 100);
console.log(calcTip(1000)); // 150

Exercise 2 (medium): Add a "split between N people" feature: a new <input id="people" type="number" value="1" /> in the HTML, and a returning function perPerson(total, people) that guards against people < 1 with an early return of total.

Solution:

function perPerson(total, people) {
  if (!people || people < 1) return total;
  return total / people;
}
// in the handler:
const people = Number(document.getElementById("people").value);
const each = perPerson(bill + tip, people);
// append to the result:
" | Each: " + formatMoney(each);

Exercise 3 (challenge): Refactor the display into its own function render(message) so the click handler contains zero direct DOM calls — it should only compute and call render. This is the compute/display separation from above, applied fully.

Solution:

function render(message) {
  document.getElementById("result").textContent = message;
}
// handler now ends with:
render("Tip: " + formatMoney(tip) + " | Total: " + formatMoney(bill + tip));
// and the early return becomes:
render("Enter a valid bill amount.");
return;

Practise what you learned

Edit the code below and press Run to see your changes live.

practice-editor
preview — press Run to refresh

Test yourself

Answer from memory first, then check yourself against the answer.

Q1What is the difference between a function declaration and an arrow function?

Declarations (function name() {}) are hoisted — callable before they appear in the file — and get their own this. Arrow functions (const f = () => …) are values assigned to variables, are not hoisted, inherit this from their surroundings, and allow implicit returns for one-liners.

Q2What does a function return if it has no return statement?

undefined. Every JavaScript function returns something; without an explicit return, that something is undefined — a common source of 'undefined' appearing in output.

Q3What is the early-return pattern and why use it?

Handle invalid cases first and return immediately, so the happy path continues unindented below. It replaces nested if/else pyramids with flat, readable code — see the bill validation in the calculator.