049: Functions I: Declarations, Expressions, Parameters, and Returns
Outcomes
By the end of this lesson, you can:
- define and call a function declaration;
- distinguish parameters from arguments;
- return a value and use it at the call site;
- decompose repeated logic into readable functions;
- validate inputs with guard returns; and
- describe a pure function as predictable input-to-output logic without external side effects.
Prerequisites and Retrieval
Before starting, retrieve your understanding of variables, conditions, and loops.
- Why is
constthe default declaration? - How can a guard condition reject an invalid score?
- What value does an accumulator hold after a loop?
One convention will matter throughout this lesson: calculation functions should generally return their results rather than log them. Log at the outer call site. That keeps the calculation reusable by code that may need to display, store, compare, or further process the result.
Terms
- function: A reusable procedure invoked to perform a task and optionally return a value. — Source: MDN: Functions
- function declaration: function name() {} syntax defining a named hoisted function. — Source: MDN: Functions
- function body: The braced statements executed on call. — Source: MDN: Functions
- parameter: Named placeholder in the definition receiving a call-time value. — Source: MDN: Glossary — Parameter
- argument: Actual value passed into a call. — Source: MDN: Glossary — Argument
- call/invocation: Executing a function via name(args), transferring control. — Source: MDN: Functions
- return value: The value delivered back to the caller by return. — Source: MDN: return
- call site: Location in code where the function is invoked. — Source: MDN: Functions
- guard return: Early return exiting when inputs invalid before main logic. — Source: MDN: Functions
- pure function: Same output for same input with no side effects. — Source: MDN: Glossary — Pure function (see Function)
- side effect: Observable change outside the function (mutation, I/O, logging). — Source: MDN: Functions
- Function (official): "A function is a callable object that executes a block of code." — Source: MDN: Functions
- Parameter vs Argument (official): "Parameters are names in the function definition; arguments are values passed at call time." — Source: MDN: Functions — Parameters
- Return value (official): "The return statement specifies the value to be returned by a function." — Source: MDN: return
Beginner Explanation and Mental Model
The useful starting model is a small named machine. Arguments enter, the statements in the function process them, and a return value comes back out. You define the machine once and can call it with different inputs:
function add(firstNumber, secondNumber) {
return firstNumber + secondNumber;
}
const total = add(4, 6);
console.log(total); // 10
firstNumber and secondNumber are parameters: local names that are available while a particular call is running. 4 and 6 are arguments: the actual values supplied by this call. Defining add does not execute its body. The expression add(4, 6) is the call that transfers execution into the function.
When execution reaches return, the current function stops and sends a value back to its caller. Any code after an unconditional return is unreachable. If execution finishes without a return statement, the function's result is undefined:
function showMessage() {
console.log("Hello");
}
const result = showMessage(); // logs Hello
console.log(result); // undefined
This is where people usually get confused: logging and returning are not interchangeable. Logging lets a person observe a value. Returning makes a value available to other code. A calculator should normally return its answer; the caller can then decide whether to log it, display it, save it, or compare it.
Input, processing, output
Start a function design by stating its contract in plain language:
- input: two number arguments;
- processing: multiply them;
- output: product number.
Then choose a verb or verb phrase that tells the reader what the function does:
function calculateArea(width, height) {
return width * height;
}
Function declarations are available throughout their containing scope because declarations are hoisted. Even so, define a function before its first use in beginner code. Reading examples from top to bottom makes the execution flow easier to follow and avoids teaching hoisting before it is needed.
Pure functions
function applyDiscount(price, rate) {
return price - (price * rate);
}
For the same arguments, this function produces the same result and changes no state outside itself. That predictable relationship makes it straightforward to test. Not every function can be pure: real applications eventually update an interface, write data, or communicate with another system. When practical, keep calculation and validation pure, then perform side effects at clear boundaries where they are easy to see and debug.
Worked Example: Reusable Price Calculator
Repeated calculations are a good opportunity to separate responsibilities. The following example uses one function for each meaningful step and a final function to compose those steps:
function calculateSubtotal(unitPrice, quantity) {
return unitPrice * quantity;
}
function calculateDiscount(subtotal, discountRate) {
return subtotal * discountRate;
}
function calculateFinalTotal(unitPrice, quantity, discountRate) {
const subtotal = calculateSubtotal(unitPrice, quantity);
const discount = calculateDiscount(subtotal, discountRate);
return subtotal - discount;
}
const firstTotal = calculateFinalTotal(200, 3, 0.10);
const secondTotal = calculateFinalTotal(150, 2, 0);
console.log("First total:", firstTotal);
console.log("Second total:", secondTotal);
Expected output:
First total: 540
Second total: 300
Trace the first call rather than treating the result as magic. Its parameters receive 200, 3, and 0.10. calculateSubtotal returns 600. That value becomes the first argument to calculateDiscount, which returns 60. calculateFinalTotal then returns 540. Each helper has one clear purpose, and calculateFinalTotal composes their results into the answer the caller needs.
None of these functions logs anything. That leaves the result usable in other expressions:
const isWithinBudget = calculateFinalTotal(200, 3, 0.10) <= 550;
console.log(isWithinBudget); // true
If the calculation only logged its answer, the caller would have no value to compare against the budget.
Intermediate Example: Validation With Guard Returns
Validation is often clearest when invalid input exits early. Here, the validator answers one Boolean question, and the grading function can assume that its input has passed that check:
function isValidScore(score) {
if (typeof score !== "number") {
return false;
}
if (Number.isNaN(score)) {
return false;
}
return score >= 0 && score <= 100;
}
function getGrade(score) {
if (!isValidScore(score)) {
return "Invalid score";
}
if (score >= 90) {
return "A";
}
if (score >= 75) {
return "B";
}
if (score >= 60) {
return "C";
}
return "F";
}
console.log(getGrade(84));
console.log(getGrade(105));
console.log(getGrade("84"));
Expected output:
B
Invalid score
Invalid score
The validator rejects non-numbers and NaN before checking the score range. Those checks are guard returns: invalid input leaves immediately, so the rest of the function only handles valid input. In getGrade, each successful threshold returns as soon as it matches, which means no else chain is needed. The flat control flow is easier to scan than deeply nested branches.
The function also refuses to silently coerce "84" into 84. That is deliberate. Callers must meet the contract's number requirement instead of relying on an implicit conversion that could hide a bug at an input boundary.
Optional Advanced Extension: Default Parameters
A default parameter supplies a value when the corresponding argument is omitted or explicitly set to undefined:
function greetUser(name, greeting = "Hello") {
return `${greeting}, ${name}!`;
}
console.log(greetUser("Ilan"));
console.log(greetUser("Ilan", "Welcome"));
Expected output:
Hello, Ilan!
Welcome, Ilan!
Put defaults in the parameter list rather than using a truthy fallback that can accidentally replace valid values such as 0 or an empty string. A default is appropriate only when omission has a clear, intentional meaning.
Deep Dive: Function Forms, Parameters, IIFEs, and arguments
JavaScript supports several function forms. They can all represent callable logic, but their semantics are not identical, so the form matters when you read or maintain existing code.
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
const formatCurrency = function (amount) {
return `₹${amount.toFixed(2)}`;
};
const isPositive = (value) => value > 0;
Default and rest parameters
function createOrder(customer = "Walk-in", ...items) {
return { customer, items };
}
Rest parameters collect the remaining arguments into a real array. In new code, prefer them over the legacy arguments object when you need a variable number of inputs.
The arguments object
Traditional functions expose an array-like arguments object containing the values passed to the call.
function showArguments() {
console.log(arguments.length);
console.log(arguments[0]);
}
showArguments("a", "b");
Arrow functions do not have their own arguments. If an arrow function needs a variable number of arguments, define a rest parameter explicitly.
IIFE
An Immediately Invoked Function Expression, or IIFE, is created and called immediately:
(() => {
const privateValue = 42;
console.log(privateValue);
})();
Before ES modules became standard, IIFEs were commonly used to create a private scope. You should be able to recognize them in legacy code, but for modern application boundaries, prefer modules.
Mistakes and Debugging
- Defining but not calling:
calculateTotalrefers to the function;calculateTotal()invokes it. - Logging instead of returning: callers receive
undefined. Return the data, then log the call result. - Forgetting to use the result: assign it, log it, compare it, or pass it onward.
- Parameter/argument confusion: parameters are definition names; arguments are call values.
- Missing path return: if some branches return and another falls through, unexpected
undefinedappears. - Code after
return: it never executes. - Wrong argument order: named parameters receive arguments by position. Keep related signatures simple.
- Changing outer variables from a calculator: return a result instead to reduce hidden dependencies.
- Silently coercing invalid inputs: define and enforce a clear contract.
When debugging, start with one small, known input. Log both the returned value and its type, then try boundary and invalid cases. If the result is undefined, inspect every control-flow path and verify that each path either returns the intended value or intentionally produces undefined. This method helps distinguish a missing return from a calculation that simply produced the wrong number.
Best Practices
- Give functions verb-based names that describe their output or action.
- Keep each function focused on one responsibility.
- Return calculated data rather than logging inside reusable logic.
- Use parameters instead of reading globals.
- Prefer pure functions for calculation and validation.
- Validate at clear boundaries and return early for invalid inputs.
- Avoid mutating argument objects unless the function name and contract make that explicit.
- Keep parameter counts manageable; do not add speculative options.
- Define a function before its first call for readable example code.
Tiered Exercises
Core
Write subtract(a, b), multiply(a, b), and isEven(number). Return the results, then log calls with at least two sets of arguments so you exercise each function with different inputs.
Practice
Write functions for rectangle area and perimeter. Add isValidDimension(value) that accepts finite positive numbers. Return "Invalid dimensions" when either dimension fails validation.
Professional Extension
Write calculateTicketPrice(age, basePrice). Reject invalid ages and prices. A customer under 12 receives 50% off, a customer aged 60 or above receives 25% off, and everyone else pays the full price. Use small validation and discount functions rather than putting every rule in one large function.
Complete Solutions
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function isEven(number) {
return number % 2 === 0;
}
console.log(subtract(10, 3)); // 7
console.log(subtract(5, 8)); // -3
console.log(multiply(4, 6)); // 24
console.log(multiply(2, 9)); // 18
console.log(isEven(12)); // true
console.log(isEven(7)); // false
function isValidDimension(value) {
return typeof value === "number" && Number.isFinite(value) && value > 0;
}
function calculateArea(width, height) {
if (!isValidDimension(width) || !isValidDimension(height)) {
return "Invalid dimensions";
}
return width * height;
}
function calculatePerimeter(width, height) {
if (!isValidDimension(width) || !isValidDimension(height)) {
return "Invalid dimensions";
}
return 2 * (width + height);
}
console.log(calculateArea(5, 3)); // 15
console.log(calculatePerimeter(5, 3)); // 16
console.log(calculateArea(-1, 3)); // Invalid dimensions
function isValidNonNegativeNumber(value) {
return typeof value === "number" && Number.isFinite(value) && value >= 0;
}
function getTicketDiscountRate(age) {
if (age < 12) {
return 0.50;
}
if (age >= 60) {
return 0.25;
}
return 0;
}
function calculateTicketPrice(age, basePrice) {
if (!isValidNonNegativeNumber(age) || !isValidNonNegativeNumber(basePrice)) {
return "Invalid input";
}
const rate = getTicketDiscountRate(age);
return basePrice - (basePrice * rate);
}
console.log(calculateTicketPrice(10, 200)); // 100
console.log(calculateTicketPrice(65, 200)); // 150
console.log(calculateTicketPrice(30, 200)); // 200
Recap and Exit Questions
Functions package logic that can be reused. Parameters name the inputs in a definition, arguments provide the values at a call site, and return sends output back to the caller. Pure functions make their input-to-output relationship explicit. Guard returns keep invalid cases separate from the main calculation.
- How do defining and calling differ?
- How do parameters and arguments differ?
- Why is returning more reusable than logging?
- What happens when execution reaches
return? - What makes a calculation function pure?
Official References
- MDN: Functions guide
- MDN: function declaration
- MDN:
return - MDN: default parameters
- ECMA-262: function definitions
References checked 2026-08-24.
