🧩 Lesson 7: Functions
Package your logic into reusable building blocks — write once, call anywhere, and keep your code organized.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Write function declarations and understand hoisting
- Create function expressions and arrow functions
- Use parameters, arguments, and default values
- Return values from functions and use them in expressions
- Understand the difference between parameters and arguments
- Write clean functions that do one thing well
Estimated Time: 55 minutes
Project: Build a mini utility library of reusable helper functions
📑 In This Lesson
Introduction
So far you've been writing code that runs straight from top to bottom. As your programs grow, you'll notice yourself copying and pasting the same logic in multiple places. Functions solve this — they let you wrap a block of code into a named, reusable unit that you can call whenever you need it.
Think of a function like a recipe. You define it once (the recipe card), and you can follow it (call it) whenever you want that dish. You can even tweak the ingredients (parameters) each time.
Function Declarations
A function declaration is the most traditional way to create a function. It uses the function keyword followed by a name.
// Defining a function
function greet() {
console.log("Hello, world!");
}
// Calling (invoking) the function
greet(); // "Hello, world!"
greet(); // "Hello, world!" — call it as many times as you want
The parentheses () after the function name are what call (invoke) it. Without them, you're just referencing the function itself, not running it.
console.log(greet); // [Function: greet] — the function object
console.log(greet()); // "Hello, world!" — runs the function
Hoisting
Function declarations are hoisted — JavaScript moves them to the top of their scope before execution. This means you can call a function before it's defined in your code.
// This works! Declaration is hoisted.
sayHi(); // "Hi there!"
function sayHi() {
console.log("Hi there!");
}
💡 Should You Rely on Hoisting?
Hoisting is useful to know about, but many developers prefer to define functions before calling them for readability. You'll see both styles in real codebases. The important thing is to be consistent within your projects.
Parameters & Arguments
Parameters are placeholders defined in the function signature. Arguments are the actual values you pass when calling the function.
// parameter
function greet(name) {
console.log(`Hello, ${name}!`);
}
// argument
greet("Ray"); // "Hello, Ray!"
greet("Maria"); // "Hello, Maria!"
Multiple Parameters
function introduce(name, role, city) {
console.log(`I'm ${name}, a ${role} from ${city}.`);
}
introduce("Ray", "developer", "Henderson");
// "I'm Ray, a developer from Henderson."
Default Parameters
You can give parameters default values that kick in when no argument is provided.
function greet(name = "stranger") {
console.log(`Hello, ${name}!`);
}
greet("Ray"); // "Hello, Ray!"
greet(); // "Hello, stranger!"
// Practical example
function createUser(name, role = "viewer", active = true) {
console.log(`${name} — ${role} — ${active ? "Active" : "Inactive"}`);
}
createUser("Alex"); // "Alex — viewer — Active"
createUser("Jordan", "admin"); // "Jordan — admin — Active"
createUser("Sam", "editor", false); // "Sam — editor — Inactive"
What Happens with Missing Arguments?
function showInfo(a, b, c) {
console.log(a, b, c);
}
showInfo(1, 2, 3); // 1 2 3
showInfo(1, 2); // 1 2 undefined ← missing args become undefined
showInfo(1); // 1 undefined undefined
⚠️ Parameters vs. Arguments
People often use these terms interchangeably, but technically: parameters are the names in the function definition, and arguments are the values passed when calling. Think: parameters are the parking spots, arguments are the cars that park in them.
Return Values
Functions can return a value back to the code that called them. This is what makes functions truly powerful — they can compute something and hand the result back.
function add(a, b) {
return a + b;
}
const sum = add(3, 5);
console.log(sum); // 8
// Use returned values directly in expressions
console.log(add(10, 20) * 2); // 60
Key Rules About return
// 1. return immediately exits the function
function checkAge(age) {
if (age < 0) {
return "Invalid age"; // Function exits here
}
return age >= 18 ? "Adult" : "Minor";
}
console.log(checkAge(-5)); // "Invalid age"
console.log(checkAge(25)); // "Adult"
// 2. A function without return gives undefined
function sayHello(name) {
console.log(`Hello, ${name}!`);
// No return statement
}
const result = sayHello("Ray");
console.log(result); // undefined
Returning Objects and Arrays
function getUser(name, age) {
return {
name: name,
age: age,
isAdult: age >= 18
};
}
const user = getUser("Ray", 30);
console.log(user.name); // "Ray"
console.log(user.isAdult); // true
// Returning an array
function getMinMax(numbers) {
return [Math.min(...numbers), Math.max(...numbers)];
}
const [min, max] = getMinMax([4, 2, 9, 1, 7]);
console.log(`Min: ${min}, Max: ${max}`); // "Min: 1, Max: 9"
Function Expressions
A function expression stores a function in a variable. The function itself can be named or anonymous (unnamed).
// Anonymous function expression
const multiply = function(a, b) {
return a * b;
};
console.log(multiply(4, 5)); // 20
// Named function expression (name useful for debugging)
const divide = function divideNumbers(a, b) {
if (b === 0) return "Cannot divide by zero";
return a / b;
};
console.log(divide(10, 2)); // 5
Declarations vs. Expressions
| Feature | Declaration | Expression |
|---|---|---|
| Syntax | function name() {} | const name = function() {} |
| Hoisted? | Yes — can call before definition | No — must define before calling |
| Named? | Always named | Can be anonymous |
| Use case | General-purpose functions | Callbacks, conditional definitions |
// ❌ Function expressions are NOT hoisted
// sayBye(); // Error: Cannot access 'sayBye' before initialization
const sayBye = function() {
console.log("Goodbye!");
};
sayBye(); // "Goodbye!" — works after definition
Arrow Functions
Arrow functions (introduced in ES6) provide a shorter syntax for writing function expressions. They use the => "fat arrow" notation.
Basic Syntax
// Traditional function expression
const add = function(a, b) {
return a + b;
};
// Arrow function — same thing, shorter
const addArrow = (a, b) => {
return a + b;
};
// Even shorter — implicit return (single expression)
const addShort = (a, b) => a + b;
console.log(addShort(3, 7)); // 10
Arrow Function Shortcuts
// One parameter — parentheses optional
const double = n => n * 2;
console.log(double(5)); // 10
// No parameters — empty parentheses required
const getRandom = () => Math.floor(Math.random() * 100);
console.log(getRandom()); // random number 0–99
// Multiple parameters — parentheses required
const greet = (first, last) => `Hello, ${first} ${last}!`;
console.log(greet("Ray", "De La Paz")); // "Hello, Ray De La Paz!"
// Returning an object — wrap in parentheses
const makeUser = (name, age) => ({ name, age, active: true });
console.log(makeUser("Alex", 28));
// { name: "Alex", age: 28, active: true }
Arrow Functions in Practice
Arrow functions really shine as callbacks — short functions you pass to other functions.
const numbers = [1, 2, 3, 4, 5];
// Traditional function
const doubled = numbers.map(function(num) {
return num * 2;
});
// Arrow function — much cleaner
const doubledArrow = numbers.map(num => num * 2);
console.log(doubledArrow); // [2, 4, 6, 8, 10]
// Filtering with arrow function
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
💡 When to Use Which?
Arrow functions are great for short callbacks, array methods, and simple utilities. Function declarations are better for top-level functions and methods that need their own this context (you'll learn about this later). A good rule of thumb: if the function has a name and stands on its own, use a declaration. If it's short and passed inline, use an arrow.
Best Practices
1. Do One Thing
Each function should have a single, clear purpose. If you find yourself naming it "doThisAndThat," it should probably be two functions.
// ❌ Does too much
function processUser(name, email, age) {
// validates, formats, saves, sends welcome email...
}
// ✅ Each function has one job
function validateEmail(email) {
return email.includes("@");
}
function formatName(name) {
return name.trim().toLowerCase();
}
2. Use Descriptive Names
// ❌ Vague
function process(d) { /* ... */ }
function calc(x, y) { /* ... */ }
// ✅ Clear and descriptive
function calculateTax(subtotal, taxRate) { /* ... */ }
function formatPhoneNumber(rawNumber) { /* ... */ }
function isValidPassword(password) { /* ... */ }
3. Keep Functions Short
If a function is longer than about 20 lines, consider breaking it into smaller helper functions. Short functions are easier to test, debug, and reuse.
4. Use Early Returns
// ❌ Nested
function getDiscount(price, isMember) {
let discount = 0;
if (price > 0) {
if (isMember) {
discount = price * 0.1;
}
}
return discount;
}
// ✅ Guard clauses with early returns
function getDiscount(price, isMember) {
if (price <= 0) return 0;
if (!isMember) return 0;
return price * 0.1;
}
✅ Functions Are the Building Blocks
Good functions are like good LEGO bricks — small, well-shaped, and easy to snap together. When you write a function, ask: "Could someone understand what this does just from its name and parameters?"
Hands-on Exercise
🏋️ Exercise: Mini Utility Library
Objective: Build a small collection of reusable helper functions using declarations, expressions, and arrow functions.
Instructions:
// Mini Utility Library
// 1. capitalize(str) — Function Declaration
// Takes a string and returns it with the first letter
// capitalized and the rest lowercase.
// capitalize("hello") → "Hello"
// capitalize("jAVAsCRIPT") → "Javascript"
// 2. clamp(value, min, max) — Function Expression
// Returns the value if it's within the range,
// otherwise returns the nearest boundary.
// clamp(5, 1, 10) → 5
// clamp(-3, 1, 10) → 1
// clamp(15, 1, 10) → 10
// 3. pluralize(count, singular, plural) — Arrow Function
// Returns the correct word form based on count.
// Use a default parameter for 'plural' (singular + "s").
// pluralize(1, "cat") → "1 cat"
// pluralize(3, "cat") → "3 cats"
// pluralize(2, "child", "children") → "2 children"
// 4. temperatureConverter(value, fromUnit) — Your choice!
// Converts between Celsius and Fahrenheit.
// temperatureConverter(32, "F") → "32°F = 0°C"
// temperatureConverter(100, "C") → "100°C = 212°F"
// If fromUnit is invalid, return "Invalid unit"
💡 Hint
capitalize: Use str[0].toUpperCase() for the first character and str.slice(1).toLowerCase() for the rest.
clamp: Use Math.max(min, Math.min(max, value)) or chain conditionals.
pluralize: Set the default parameter plural = singular + "s", then use a ternary to pick the right form.
temperatureConverter: F to C: (value - 32) * 5/9. C to F: value * 9/5 + 32.
✅ Solution
// 1. capitalize — Function Declaration
function capitalize(str) {
if (!str) return "";
return str[0].toUpperCase() + str.slice(1).toLowerCase();
}
console.log(capitalize("hello")); // "Hello"
console.log(capitalize("jAVAsCRIPT")); // "Javascript"
console.log(capitalize("")); // ""
// 2. clamp — Function Expression
const clamp = function(value, min, max) {
return Math.max(min, Math.min(max, value));
};
console.log(clamp(5, 1, 10)); // 5
console.log(clamp(-3, 1, 10)); // 1
console.log(clamp(15, 1, 10)); // 10
// 3. pluralize — Arrow Function
const pluralize = (count, singular, plural = singular + "s") =>
`${count} ${count === 1 ? singular : plural}`;
console.log(pluralize(1, "cat")); // "1 cat"
console.log(pluralize(3, "cat")); // "3 cats"
console.log(pluralize(2, "child", "children")); // "2 children"
// 4. temperatureConverter
function temperatureConverter(value, fromUnit) {
const unit = fromUnit.toUpperCase();
if (unit === "F") {
const celsius = ((value - 32) * 5 / 9).toFixed(1);
return `${value}°F = ${celsius}°C`;
}
if (unit === "C") {
const fahrenheit = (value * 9 / 5 + 32).toFixed(1);
return `${value}°C = ${fahrenheit}°F`;
}
return "Invalid unit";
}
console.log(temperatureConverter(32, "F")); // "32°F = 0.0°C"
console.log(temperatureConverter(100, "C")); // "100°C = 212.0°F"
console.log(temperatureConverter(72, "K")); // "Invalid unit"
🎯 Quick Quiz
Question 1: What does a function return if it has no return statement?
Question 2: Which can be called before it's defined in the code?
Question 3: What is the output of const fn = n => n * 3; console.log(fn(4));?
Summary
🎉 Key Takeaways
- Function declarations are hoisted and use the
functionkeyword - Function expressions store a function in a variable and are not hoisted
- Arrow functions (
=>) are concise expressions, ideal for callbacks - Parameters are placeholders; arguments are the actual values passed in
- Default parameters provide fallback values for missing arguments
returnsends a value back to the caller — without it, functions returnundefined- Write functions that do one thing, have clear names, and use early returns
📚 Additional Resources
- MDN — Functions Guide
- javascript.info — Functions
- MDN — Arrow Functions
- javascript.info — Arrow Functions
🚀 What's Next?
Now that you can create reusable functions, the next big question is: where can your variables be accessed? In the next lesson, you'll explore scope and hoisting — understanding which parts of your code can see which variables, and why that matters for writing bug-free programs.