Functions and Scope
Table of Contents
- Why Functions?
- Three Ways to Write a Function
- Parameters, Defaults, and Rest
- Return Values
- Pure Functions and Side Effects
- Scope: Who Can See What
- Hoisting, Briefly
- Closures
- Callbacks: Passing a Function In
- Try It Yourself
1. Why Functions?
A function is a named, reusable piece of code. You write the steps once, give them a name, and call them whenever you need them. This gives you three things:
- No repetition: fix a bug in one place, not ten.
- Names that explain:
formatYear(2025)tells the reader more than four lines of arithmetic. - Small pieces: a big program becomes many small functions that you can understand and test one at a time.
Dara's page will need functions for almost everything: showing a project, checking a form, switching to dark mode. This chapter gives you the tools to write them well.
2. Three Ways to Write a Function
Here is the same idea written three ways.
A function declaration starts with the keyword function:
function greet(name) {
return "Hello, " + name + "!";
}
console.log(greet("Dara")); // "Hello, Dara!"
A function expression stores a function in a variable:
const double = function (n) {
return n * 2;
};
An arrow function is a shorter form of an expression:
const triple = (n) => n * 3;
const add = (a, b) => {
const sum = a + b;
return sum;
};
Two arrow-function rules to remember:
- If the body is a single expression, you can drop the braces and the
word
return; the value is returned automatically (tripleabove). - To return an object this way, wrap it in parentheses, so JavaScript does not think the braces are a function body:
const makeBadge = (title) => ({ label: title, isNew: true });
| Form | Looks like | Notes |
|---|---|---|
| Declaration | function greet() {} | Can be called before its line (see hoisting) |
| Expression | const greet = function () {} | Only works after its line |
| Arrow | const greet = () => {} | Short; best for small functions and callbacks |
Which should you use? Pick one style and stay consistent. A common choice, and the one in this series: declarations for the main named functions, arrows for short helpers and callbacks.
3. Parameters, Defaults, and Rest
The names in the parentheses are parameters; the values you pass in are arguments. A parameter is like a variable that is created fresh each time the function runs.
function describe(title, year = 2025, ...tags) {
return `${title} (${year}) [${tags.join(", ")}]`;
}
console.log(describe("Recipe Finder"));
console.log(describe("Recipe Finder", 2024, "html", "css"));
console.log(describe("Quick Note", undefined, "js"));
The output is:
Recipe Finder (2025) []
Recipe Finder (2024) [html, css]
Quick Note (2025) [js]
- Default value:
year = 2025is used when the argument is missing orundefined. In the third call,undefinedwas passed on purpose to keep the default. - Rest parameter:
...tagscollects all remaining arguments into a real array. It must be the last parameter. - If you pass too few arguments, the missing parameters are
undefined. If you pass too many, the extras are ignored (unless you use rest).
4. Return Values
return ends the function and sends a value back to the caller:
function sign(n) {
if (n < 0) {
return "negative";
}
return "not negative";
}
console.log(sign(-1)); // "negative"
console.log(sign(3)); // "not negative"
The line after a return that runs is never reached. This lets you
write early returns: handle the unusual case first, then continue
with the normal case. It avoids deeply nested if blocks.
A function with no return gives back undefined:
function noReturn() {
const x = 1;
}
console.log(noReturn()); // undefined
A very common beginner mistake is to print a result inside a function
(console.log) when you meant to return it. A printed value is gone;
a returned value can be stored, tested, and passed to the next function.
5. Pure Functions and Side Effects
A function is pure when it depends only on its arguments, and the only thing it does is return a result. Give it the same input and you always get the same output.
const withDiscount = (price, percent) => price - (price * percent) / 100;
console.log(withDiscount(100, 10)); // 90
A function has a side effect when it also changes something outside itself: printing, changing a variable, editing the page, saving a file.
const log = [];
function addToLog(message) {
log.push(message); // changes data outside the function
}
addToLog("page loaded");
console.log(log); // [ 'page loaded' ]
Side effects are not bad; programs need them to do anything useful. But pure functions are far easier to test and to trust. A good habit: keep the calculating in pure functions, and keep the side effects in a few small, clearly named places. Dara's page will follow this: one pure function decides which projects to show, and a separate function puts them on the page.
6. Scope: Who Can See What
Scope is the area of code where a variable can be used. JavaScript has three levels you need to know.
| Scope | Created by | Visible |
|---|---|---|
| Global | Top level of the file | Everywhere |
| Function | A function body | Only inside that function |
| Block | Any { }, such as if or for | Only inside those braces (let and const) |
const siteName = "Dara Sok"; // global
function showScope() {
const local = "inside"; // function scope
if (true) {
const blockOnly = "block"; // block scope
}
console.log(blockOnly); // ReferenceError: blockOnly is not defined
return siteName + " " + local;
}
Inner code can see outer variables, but outer code cannot see inner
ones. Inside showScope, siteName is visible. Outside it, local is
not:
console.log(local); // ReferenceError: local is not defined
JavaScript looks for a name starting in the current scope, then moves outward one level at a time. If an inner variable has the same name as an outer one, the inner one shadows it:
const x = "outer";
function shadow() {
const x = "inner";
return x;
}
console.log(shadow(), x); // "inner" "outer"
Why does this matter? Small scopes protect you. A variable that lives only inside one function cannot be changed by mistake from somewhere else. So: declare variables as close to where you use them as possible, and avoid global variables.
One old keyword, var, ignores block scope and leaks out of if and
for braces. You will see it in old code. Do not write it; use const
and let.
7. Hoisting, Briefly
Before running your code, JavaScript scans it and registers declarations. This is called hoisting, and it has one practical effect: function declarations can be called before the line where they appear.
console.log(hoisted()); // "works"
function hoisted() {
return "works";
}
But const, let, and function expressions are not usable before
their line. Trying gives an error:
console.log(later); // ReferenceError: Cannot access 'later' before initialization
const later = 1;
You do not need to memorize the rules. The advice is simple: define things before you use them, and when you see "Cannot access ... before initialization", move the definition up.
8. Closures
A function remembers the variables around the place where it was created, even after the outer function has finished. This is called a closure.
function makeCounter() {
let count = 0;
return () => {
count++;
return count;
};
}
const counterA = makeCounter();
const counterB = makeCounter();
console.log(counterA(), counterA(), counterA()); // 1 2 3
console.log(counterB()); // 1
makeCounter has finished running, yet the returned function still
reaches count. Each call to makeCounter creates a separate
count, so counterB starts from 1. Nothing outside can touch count
directly; it is private to those functions.
A closure is also a handy way to "pre-fill" a function:
function makeGreeter(greeting) {
return (name) => `${greeting}, ${name}!`;
}
const sayHello = makeGreeter("Hello");
console.log(sayHello("Dara")); // "Hello, Dara!"
You will meet closures again in chapter 8, when an event handler "remembers" which button it belongs to.
9. Callbacks: Passing a Function In
In JavaScript, a function is a value, just like a number or a string. You can store it in a variable, and you can pass it to another function. A function passed as an argument is called a callback.
function repeat(times, action) {
for (let i = 1; i <= times; i++) {
action(i);
}
}
repeat(3, (n) => console.log("Round", n));
This prints Round 1, Round 2, Round 3. The function repeat does
not know what the work is; the caller decides.
You have already used the idea. Array methods take a callback:
const titles = ["Recipe Finder", "Weather Dashboard"];
const upper = titles.map((title) => title.toUpperCase());
console.log(upper); // [ 'RECIPE FINDER', 'WEATHER DASHBOARD' ]
Callbacks are also how JavaScript waits. You say, "run this function later":
setTimeout(() => console.log("later"), 1000);
console.log("first");
first appears immediately and later after one second. This
"call me back when it is ready" pattern is the foundation of events
(chapter 8) and asynchronous code (chapter 9).
10. Try It Yourself
-
Three forms. Write a function
isEven(n)as a declaration, then as an expression, then as a one-line arrow. Test all three with4and7. -
Defaults. Write
welcome(name, greeting = "Hello")that returns a string. Call it with one argument and with two. -
Rest. Write
average(...numbers)that returns the average. What should it return for no arguments? Handle that case with an early return. -
Return, not print. Write
formatTitle(title)that returns the title in uppercase with" - Dara Sok"added. Use the result in aconsole.logoutside the function. -
Find the scope error. Predict what each line prints, then run it:
const a = 1; function test() { const b = 2; if (b > 1) { const c = 3; } console.log(a, b); console.log(c); } test(); -
Closure. Write
makeMultiplier(factor)that returns a function multiplying its input byfactor. Createdoubleandtriplefrom it and test them.
Next, we'll look closely at the two structures JavaScript programs use most, arrays and objects, and model Dara's projects as real data.
Buy Me a Coffee
Found this tutorial helpful? Consider buying me a coffee to support more free content!