Module 5 · Functions: Declarations, Arrows, Defaults and Rest
A Function Is a Named Recipe: The Picture
In this lesson
- Write a function once, call it from many places, and tell its definition from its call.
- Pass values in as arguments and get one value back with
return. - Predict what a call gives when the function has no
return, and read the starter'snextandnextIntas functions.
Amara's shop gives members 10 percent off, and her receipt program works that price out in five places. One day the owner raises the discount to 12 percent. Bob finds the formula, changes it four times, and misses the fifth. Every receipt is now right except the one printed for gift cards. This lesson writes the formula once, gives it a name, and runs it from all five places.
One job, written once
Here is the shape of Amara's problem, cut down to three places. The same formula appears in each line, with a different price in it.
console.log("Rice: " + (500 - Math.floor(500 / 10)));
console.log("Oil: " + (1200 - Math.floor(1200 / 10)));
console.log("Salt: " + (85 - Math.floor(85 / 10)));
Rice: 450
Oil: 1080
Salt: 77
Math.floor rounds down, so a member saves whole taka only. The program works. The trouble is that the rule "10 percent off" lives in three places, and a change must find all three.
A function is a named piece of code you write once and run whenever you need it. Here is the same program with the rule moved into a function called memberPrice.
function memberPrice(amount) {
return amount - Math.floor(amount / 10);
}
console.log("Rice: " + memberPrice(500));
console.log("Oil: " + memberPrice(1200));
console.log("Salt: " + memberPrice(85));
Rice: 450
Oil: 1080
Salt: 77
The output is the same, line for line. The rule now lives in one place, so a new discount is a one-line change. So a function turns a formula you would copy into a name you can use.
Defining a function, and calling it
The function memberPrice(amount) { ... } part is the definition. It gives the code a name and stores it. It runs nothing: a program with only a definition prints nothing at all.
The memberPrice(500) part is the call. The parentheses after the name mean "run it now". Module 1 met calls already: console.log("hi") calls a function that Node wrote for you.
function greet(name) {
return "Hello, " + name;
}
console.log(greet("Zara"));
console.log(greet);
console.log(typeof greet);
Hello, Zara
[Function: greet]
function
Line 5 calls greet and prints what it gives back. Line 6 has no parentheses, so nothing runs. Node prints the function itself as [Function: greet]. A function is a value, and typeof calls its type "function". So the name is the recipe, and the name with parentheses is the cooking.
A function declaration, and a call
function name(parameter1, parameter2) {
body: the lines that run on every call
return value;
}
name(argument1, argument2) runs the body, and is replaced by the value returned
functionstarts a function declaration, the plainest way to make one.- The name follows Module 1's naming habit: camelCase, and it says what you get or what it does.
- Parameters are names for the values the function receives. There may be none:
function hello(). - The body sits between the braces and runs only when the function is called.
returnends the call and hands one value back to the place that called.- The arguments are the values written in the call's parentheses.
Parameters and arguments
A parameter is a name in the definition: amount in memberPrice(amount). An argument is the value in the call: the 500 in memberPrice(500). When the call starts, each parameter gets the argument in the same position.
function lineTotal(qty, price) {
return qty * price;
}
console.log(lineTotal(3, 40));
console.log(lineTotal(40, 3));
console.log(lineTotal(2));
console.log(lineTotal(2, 5, 99));
120
120
NaN
10
Matching is by position only, not by name. Line 6 swapped the two values, and only the order of the multiplication changed. Line 7 gave one argument, so price is undefined, the "no value" of Module 1, and 2 * undefined is NaN.
Line 8 gave three. The extra 99 has no parameter to land in, so the function never sees it. JavaScript gives no error in either case. So count your arguments: too few become undefined, and too many are ignored.
return: one value back, and the call is over
return does two things at once. It hands one value back to the call, and it ends the call on the spot. A line after a return that ran never runs.
function shippingFee(total) {
if (total >= 1000) {
return 0;
}
return 60;
}
function sayHi(name) {
console.log("Hi, " + name);
}
console.log(shippingFee(1500));
console.log(shippingFee(400));
const result = sayHi("Kenji");
console.log(result);
0
60
Hi, Kenji
undefined
For 1500, the first return runs, and the function stops before return 60. This is an early return: answer the special case first, and leave. It is the "small function with return" that Module 4 said can replace a labelled break.
sayHi prints a line, but it has no return. A call to a function with no return gives undefined, and so does a bare return; with no value. Printing and returning are different jobs. So a function that should give an answer must return it, not console.log it.
One rule from Module 1 now matters. A line break straight after return ends the statement there, so return alone on a line returns undefined. Keep the value on the same line as return. Common mistake 3 shows the bug.
Watching a call happen
When a call starts, the engine needs a place to keep that call's parameters and to remember where to come back to. It keeps them in a frame, and the frames of calls that have not finished stack up like plates: the call stack. A call puts a frame on top; a return takes it off.
This program prints one receipt line. printLine calls lineTotal in the middle of its own work.
function lineTotal(qty, price) {
return qty * price;
}
function printLine(item, qty, price) {
console.log(item + ": " + lineTotal(qty, price));
}
printLine("tea", 3, 40);
console.log("done");
tea: 120
done
Step through the calls. Watch each frame arrive with its own parameters, and leave with its return value.
The last frame in is the first one out. printLine cannot finish its line until lineTotal has returned. So a call waits for every call it makes, and the call stack is the list of calls still waiting.
A function's names belong to it
Each call gets fresh parameters, and a let or const made inside the body belongs to that call too. Outside the function, those names do not exist.
function average(a, b) {
const sum = a + b;
return sum / 2;
}
console.log(average(4, 10));
console.log(typeof sum);
7
undefined
sum lived inside the call, and it was gone when the call ended. typeof on a name that does not exist gives "undefined" instead of an error. So a function is a closed box: values come in through parameters, and one value leaves through return. Where a name can be seen is called its scope, and Module 6 teaches it.
Three spellings, and the two in your starter
JavaScript has three ways to write a function. All three make the same kind of value, and you call them the same way.
function halfA(n) {
return n / 2;
}
const halfB = function (n) {
return n / 2;
};
const halfC = (n) => n / 2;
console.log(halfA(9), halfB(9), halfC(9));
4.5 4.5 4.5
The first is a function declaration. The second is a function expression: a function made as a value and stored in a const. The third is an arrow function: the parameters, then =>, then the value to give back. With no braces, an arrow returns that value without the word return.
Now read the two lines of the starter that Module 1 asked you to use as given.
const tokens = ["7", "3"];
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
console.log(nextInt() + nextInt());
console.log(at);
10
2
next is an arrow function with no parameters, so its parentheses are empty. Each call returns the token at position at, and at++ moves the position on by one. nextInt calls next and turns its token into a number. So the starter's helpers are two small functions, one calling the other.
The three spellings differ in two places that matter later: where in a file you may call them, and a word called this (Module 9). Lesson 02 takes every form apart, and lesson 04 says when to choose which.
The smallest useful function: one parameter in, one string out.
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet("Zara"));
console.log(greet("Kenji"));
const line = greet("Amara");
console.log(line.length);
Hello, Zara!
Hello, Kenji!
13
A call is an expression, so its value can be printed, stored in a name, or used on the spot. line holds the string the third call returned, and line.length counts its 13 boxes.
David's delivery fee depends on two things: whether the customer is a member, and the order total. Each special case answers first and leaves.
function deliveryFee(total, isMember) {
if (isMember) {
return 0;
}
if (total >= 1000) {
return 0;
}
if (total >= 500) {
return 30;
}
return 60;
}
console.log(deliveryFee(300, true));
console.log(deliveryFee(1200, false));
console.log(deliveryFee(700, false));
console.log(deliveryFee(150, false));
0
0
30
60
No else is needed: once a return runs, the lines below it never do. Reading top to bottom, each check only sees the cases the checks above did not answer.
Amara's real program reads the items from the input with the fixed starter. Two small functions do the sums, and the loop calls them once per item. The member discount now lives in one place.
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
function lineTotal(qty, price) {
return qty * price;
}
function memberPrice(amount) {
return amount - Math.floor(amount / 10);
}
const n = nextInt();
let total = 0;
for (let i = 0; i < n; i++) {
const item = next();
const qty = nextInt();
const price = nextInt();
const cost = memberPrice(lineTotal(qty, price));
out.push(`${item} x${qty}: ${cost}`);
total += cost;
}
out.push(`total: ${total}`);
console.log(out.join("\n"));
rice x2: 900
oil x1: 1080
salt x3: 230
total: 2210
That output is for the input 3, then rice 2 500, oil 1 1200 and salt 3 85 on three lines. memberPrice(lineTotal(qty, price)) calls one function and hands its answer straight to the other: 3 times 85 is 255, and 255 minus 25 is 230.
Where this is used
- The starter's first line.
require("fs").readFileSync(0, "utf8")calls a function from Node's own file module. Node's team wrote it once, and every JavaScript program on the judge calls it. - Express, a web server for Node. You hand a function to
app.get("/", ...), and Express calls it once for every request that arrives at that address. - AWS Lambda. Amazon's service runs one exported JavaScript function, called the handler, each time an event arrives. You never write the loop that waits for events.
- Sorting.
Array.prototype.sorttakes a function that compares two items and calls it as often as it needs. Module 3 used it withlocaleCompare, and Module 7 teaches it.
Common mistakes
1. Forgetting the parentheses.
function today() {
return "Monday";
}
console.log("Today is " + today);
console.log("Today is " + today());
Today is function today() {
return "Monday";
}
Today is Monday
Without parentheses, nothing is called. Joined to a string, the function turns into its own source code. Write today() to run it. You will forget them because the name alone already looks like the thing you want.
2. Printing the answer instead of returning it.
function addTax(price) {
console.log(price + 15);
}
const total = addTax(100);
console.log(total * 2);
115
NaN
The function shows 115 on the screen, and gives back undefined. undefined * 2 is NaN. Write return price + 15; and let the caller print. You will do this because the printed number looks like an answer while you test.
3. return alone on its line.
function welcome(name) {
return
"Welcome back, " + name;
}
console.log(welcome("Maria"));
undefined
No error, just undefined. The line break after return ends the statement, so the string below is never reached. Start the value on the same line: return "Welcome back, " + name;. You will write it when a long value tempts you to start it on a fresh line.
4. Calling with too few arguments.
function area(width, height) {
return width * height;
}
console.log(area(5));
NaN
height got no argument, so it is undefined, and 5 * undefined is NaN. Pass both: area(5, 5). Lesson 02 shows a default value for a parameter the caller may leave out. You will hit this when you add a parameter to a function and forget an old call.
Alice prices floor tiles by area. Each room is a rectangle, a square or a right triangle. Write one small function per shape, and call the right one for each room.
Input. n, then n shapes, each a word and its lengths: rect w h, square s or triangle b h (base and height).
Output. One line per shape: its area, as JavaScript prints the number. A triangle's area may end in .5.
Constraints. 1 <= n <= 100000. Every length is a whole number from 1 to 10000.
Sample. Input 4, rect 3 4, square 5, triangle 3 5 and rect 1 1 on five lines gives 12, 25, 7.5 and 1.
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Graded as shape-areas. The hidden tests include a single room, every shape at the largest lengths, and triangles whose area is not a whole number.
Bob's friends split restaurant bills. Each person pays the same whole number of taka, rounded up so the bill is always covered. Write a function share(total, people) and call it for each bill.
Input. n, then n bills, each a total and the number of people.
Output. One line per bill: what each person pays, then a space and how much is left over for the tip.
Constraints. 1 <= n <= 1000. The total is a whole number from 1 to 1000000, and people from 1 to 50.
Sample. Input 2, 1000 3 and 900 3 on three lines gives 334 2 and 300 0.
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Not graded on its own. Math.ceil rounds up. The left-over part needs the share, so call share once and keep its answer in a const.
Zara checks passwords before her app accepts them. A strong password has at least 8 characters, at least one digit and at least one capital letter. Write one function per rule, each returning true or false, and a fourth that uses the three.
Input. n, then n passwords of ASCII letters and digits.
Output. One line per password: strong, or weak: followed by the missing rules from length, digit and capital, in that order, separated by a comma and a space.
Constraints. 1 <= n <= 1000. Each password has 1 to 40 characters.
Sample. Input 3, then Kenji2026 abc Password gives strong, weak: length, digit, capital and weak: digit.
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Not graded on its own. Each rule function can loop over the characters and return true the moment it finds what it needs. Collect the missing names in an array and join them.
Common doubts
Why not just copy the lines? It works.
It works until the rule changes, and rules always change. A copied formula has to be found and fixed in every place, and the one you miss is a bug nobody notices for weeks. A function also gives the idea a name, so
memberPrice(500)says what it means while500 - Math.floor(500 / 10)makes you work it out.Is a parameter the same as an argument?
They are two ends of one hand-over. The parameter is the name written in the definition, and the argument is the value written in the call. People mix the words up in speech all the time, and nobody is confused for long. In this track, the definition has parameters and the call has arguments.
Does every function need a return?
No. A function that only does something, such as printing a line, needs none, and its call gives
undefined. A function that works out an answer needs one, or the answer is lost. Name the first kind with a verb, likeprintLine, so nobody uses its call as a value.Module 1 said a function name starts with a verb. What about
lineTotalandarea?The rule behind the habit is that the name says what you get. A function that does something is named for the action:
printReceipt. One that only answers a question is often named for its answer, as JavaScript's ownMath.maxis.getLineTotalis fine too; pick one style and keep it.Can a function call another function, or itself?
Yes to both.
printLinecalledlineTotalabove, and the call stack kept both frames. A function that calls itself is called recursive, and lesson 03 builds one and watches its frames pile up.
Key takeaways
- A function is a named piece of code: the definition stores it, and a call with parentheses runs it.
- Arguments land in parameters by position; a missing one is
undefined, and an extra one is ignored. returngives one value back and ends the call; with noreturn, the call givesundefined.- Each call gets a frame on the call stack, with its own parameters, and the frame is removed when the call returns.
- The declaration, the function expression and the arrow are three spellings of a function; the starter's
nextandnextIntare arrows. - Go deeper: Under the Hood, the call stack, frames and what a call costs (Pro).
Next, lesson 02 takes every form apart one by one: the arrow in full, default values, rest parameters, and functions passed around as values.
End of lesson 1
Mark it done, and your progress moves with you.
Next: Every Form, One by One: Declarations, Arrows, Defaults and Rest