Course: JavaScript Beginner
Lesson 10: Functions as Reusable Logic
Level
Beginner
Estimated Reading Time
10–15 minutes
Goal
By the end of this lesson, the learner will be able to:
- Declare functions using function declarations and arrow functions
- Define parameters and use return values to pass data out of a function
- Explain why functions make code easier to reuse, test, and understand
Frontend Development Context
Functions are the fundamental unit of reuse in JavaScript. When you find yourself writing the same logic in two places, or when a block of code grows long enough that you have to scroll to understand it, the right tool is a function. Wrapping logic in a named function lets you call it by name, pass in different data each time, and get a result back.
In frontend development, functions are used everywhere: to handle button clicks, to format data before displaying it, to validate form fields, to fetch data from an API, and to update the DOM based on state. Every event handler you write is a function. Learning to write good functions — with clear names, focused responsibilities, and meaningful return values — is one of the highest-leverage skills in JavaScript.
Explanation
A function declaration defines a named function using the function keyword. It can be called anywhere in the same scope, even before the line where it is defined (because of hoisting). A function expression assigns a function to a variable — it is not hoisted, so it must be defined before it is called. An arrow function is a compact syntax introduced in ES6 that is commonly used in modern JavaScript, especially for callbacks and short transformations.
Parameters are the named inputs a function expects. When you call a function, you pass arguments — the actual values — which are received as the parameter names. A function can have zero or many parameters. Parameters without a matching argument are undefined inside the function.
The return keyword sends a value back to the caller and immediately exits the function. A function without a return statement (or with return;) returns undefined. The returned value can be stored in a variable, passed to another function, or used in an expression.
Functions should do one clear thing. A function named formatPrice should format a price — not also fetch data and update the DOM. Keeping functions focused makes them easier to understand, reuse, and debug.
Default parameter values let you define a fallback for parameters that might not be provided: function greet(name = "Guest") means if no name is passed, it defaults to "Guest".
Code Example
This example uses functions to format a product's price and render a product card to the DOM:
function formatPrice(amount, currency = "USD") {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency,
}).format(amount);
}
function createProductCard(product) {
const card = document.createElement("div");
card.className = "product-card";
card.innerHTML = `
<h3>${product.name}</h3>
<p>${formatPrice(product.price)}</p>
`;
return card;
}
const product = { name: "Wireless Keyboard", price: 79.99 };
const card = createProductCard(product);
document.querySelector("#product-grid").appendChild(card);
Code Explanation
formatPrice takes an amount and an optional currency (defaulting to "USD") and uses the built-in Intl.NumberFormat API to produce a properly formatted currency string. This logic is wrapped in a function so it can be called many times across the app with different amounts and currencies without repeating the formatting setup.
createProductCard takes a product object and builds a DOM element representing that product. It calls formatPrice internally to format the price — this is function composition, where one function uses another. The function returns the completed DOM element rather than directly appending it to the page, giving the caller flexibility over where it ends up.
The last three lines create one product and add its card to the DOM. If there were 50 products in an array, you could loop over them and call createProductCard for each one — which is exactly how real product listing pages work.
Notice that neither function uses global state or modifies the DOM directly from inside the formatting function. formatPrice is a pure function — given the same input, it always returns the same output. This makes it easy to test and reuse.
Pattern Highlights
✅ Positive Pattern: Name functions after what they do (a verb or verb phrase):
formatPrice,createCard,validateEmail,fetchUser. Clear function names make code self-documenting.
⚠️ Neutral Note: Arrow functions do not have their own
thisbinding, which matters inside object methods or event handlers in some contexts. For simple utility functions and callbacks, arrow functions are ideal.
❌ Negative Pattern: Writing functions that do too many things — fetching data, formatting it, validating it, and updating the DOM — in one function makes it impossible to reuse any single part and very hard to debug.
Common Mistakes
Mistake 1: Forgetting to return a value
A function that performs a calculation but forgets return gives back undefined to the caller, which causes silent failures.
function double(n) {
const result = n * 2;
// Missing: return result
}
const value = double(5);
console.log(value); // undefined — not 10
If a function computes something the caller needs, always end with a return statement.
Mistake 2: Using a function expression before it is defined
Function expressions assigned to const or let are not hoisted. Calling them before they are defined throws a ReferenceError.
const greeting = sayHello("Alex"); // ReferenceError: Cannot access before initialization
const sayHello = (name) => `Hello, ${name}!`;
Either use a function declaration (which is hoisted) or ensure the expression is defined before it is called.
Mistake 3: Modifying a parameter directly when it is an object
When you pass an object to a function and modify its properties inside, you are modifying the original object — not a copy.
function applyDiscount(product) {
product.price = product.price * 0.9; // modifies the original!
}
const item = { name: "Hat", price: 30 };
applyDiscount(item);
console.log(item.price); // 27 — the original object was changed
If you do not intend to mutate the original, create a new object inside the function: return { ...product, price: product.price * 0.9 }.
Quick Recap
- Functions are named, reusable blocks of logic that can accept parameters and return a value
- Use
returnto send a value back to the caller — without it, the function returnsundefined - Default parameters (
function fn(x = 0)) provide fallback values when an argument is not passed - Keep functions focused on one responsibility — small, well-named functions are easier to reuse and debug