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
Retrieve 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?
Today, prefer function results over logging inside calculation functions. Log at the outer call site so logic remains reusable.
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
A function is a small machine with a name. Arguments go in, statements process them, and a return value comes out. Define the machine once, then 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. They are local names available during each call. 4 and 6 are arguments. Defining a function does not run its body. add(4, 6) performs the call.
When execution reaches return, the current function stops and sends a value to its caller. Code after an unconditional return is unreachable. Without a return statement, a function returns undefined:
function showMessage() {
console.log("Hello");
}
const result = showMessage(); // logs Hello
console.log(result); // undefined
Logging and returning solve different problems. Logging helps a person observe a value. Returning gives a value to other code. A calculator should normally return its answer; the caller chooses whether to log it, display it, store it, or compare it.
Input, processing, output
Design a function by writing its contract in plain language:
- input: two number arguments;
- processing: multiply them;
- output: product number.
Then name it with a verb or verb phrase:
function calculateArea(width, height) {
return width * height;
}
Function declarations are available throughout their containing scope, but define before use in beginner code because top-to-bottom reading is clearer.
Pure functions
function applyDiscount(price, rate) {
return price - (price * rate);
}
For the same arguments this returns the same result, and it changes no outside state. That makes it easy to test. Not every function can be pure, because applications eventually update interfaces and communicate with systems. Keep calculation and validation logic pure when practical, then perform effects at clear boundaries.
Worked Example: Reusable Price Calculator
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. Its parameters receive 200, 3, and 0.10. The subtotal function returns 600. The discount function receives 600 and returns 60. The final function returns 540. Each helper has one clear purpose, while calculateFinalTotal composes the results.
The functions do not log. This permits:
const isWithinBudget = calculateFinalTotal(200, 3, 0.10) <= 550;
console.log(isWithinBudget); // true
If the calculation only logged, it could not participate in this comparison.
Intermediate Example: Validation With Guard Returns
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 answers one Boolean question. Guards make invalid inputs leave immediately. In getGrade, each successful threshold returns, so else is unnecessary. This flat shape is easier to scan than deeply nested branches. The function does not silently coerce "84"; callers must provide the contract's number input.
Optional Advanced Extension: Default Parameters
A default parameter applies when an argument is missing or explicitly undefined:
function greetUser(name, greeting = "Hello") {
return `${greeting}, ${name}!`;
}
console.log(greetUser("Ilan"));
console.log(greetUser("Ilan", "Welcome"));
Expected output:
Hello, Ilan! Welcome, Ilan!
Defaults belong in the parameter list, not in truthy fallbacks that accidentally replace valid 0 or empty-string arguments. Use defaults only when omission has a clear meaning.
Deep Dive: Function Forms, Parameters, IIFEs, and arguments
JavaScript has several function forms with different semantics.
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 are real arrays. Prefer them over the legacy arguments object in new code.
The arguments object
Traditional functions expose an array-like arguments object.
function showArguments() {
console.log(arguments.length);
console.log(arguments[0]);
}
showArguments("a", "b");
Arrow functions do not have their own arguments.
IIFE
An Immediately Invoked Function Expression executes as soon as it is created.
(() => {
const privateValue = 42;
console.log(privateValue);
})();
IIFEs were historically used to create private scope before ES modules became standard. You should understand them for legacy code, while preferring modules for modern application boundaries.
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.
Debug by calling a function with one small known input, logging the returned value and type, and testing boundary/invalid cases. If a result is undefined, inspect every control-flow path for a return.
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 results and log calls with at least two sets of arguments.
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/prices. Under 12 receives 50% off, age 60 or above receives 25% off, and others pay full price. Use small validation and discount functions.
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 reusable logic. Parameters describe inputs, arguments supply call values, and return sends output. Pure functions make input-output relationships explicit, while guard returns keep invalid cases separate.
- 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.
