JS - 03.06 - Return, Recursion, Call Stack
returning values, callback functions, recursion, and closures.
Returning a Value
Functions and Side Effects
Functions can serve two purposes: they can produce side effects (such as modifying a variable, printing to the console, or changing the DOM) or they can return values.
Functions that return values are more useful because their output can be reused in other parts of the program.
function sum(a, b) {
return a + b;
}
let result = sum(1, 2);
alert(result); // 3
Here, sum
returns a value (3
) that is then stored in the variable result
. This value can be used or passed around later.
Returning Early
The return
statement immediately stops the function’s execution and optionally returns a value. If no value is returned, the function returns undefined
by default.
function showMovie(age) {
if (!checkAge(age)) {
return; // Early exit if age is not valid
}
alert("Showing movie");
}
Pure Functions
A pure function is one that:
- Returns a value based only on its input arguments.
- Has no side effects (does not modify global variables or interact with external state).
Pure functions are easy to test and reason about, as their output depends only on the input.
Functions as Values
In JavaScript, functions are first-class citizens. This means they can be:
- Assigned to variables.
- Passed as arguments to other functions.
- Returned from other functions.
Functions as Values
A function can be treated as a value (like a string or a number), meaning you can assign it to a variable and execute it later.
function sayHi() {
alert("Hello");
}
let func = sayHi; // Assign function to variable
func(); // Executes sayHi() function
sayHi();
Regular values like strings or numbers represent the data. A function can be perceived as an action. We can pass it between variables and run when we want.
Passing Functions as Arguments
You can pass functions as arguments to other functions. These are often called callback functions.
function ask(question, yes, no) {
if (confirm(question)) yes(); // If yes is clicked, call the `yes` callback
else no(); // If no is clicked, call the `no` callback
}
function showOk() {
alert("You agreed.");
}
function showCancel() {
alert("You canceled.");
}
ask("Do you agree?", showOk, showCancel);
Here:
- The
ask
function takes two callback functions (showOk
andshowCancel
). - The appropriate callback is executed based on the user’s response.
Anonymous Functions as Callbacks
You can also pass anonymous functions directly as arguments to another function.
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled."); }
);
These anonymous functions are called immediately when the ask
function executes. They are not assigned to any variables, so they exist only within the scope of the ask
call.
The Call Stack
When a function is called, the program must “remember” where it left off in order to return to that point after the function finishes executing. This is handled by the call stack.
- Every time a function is called, its execution context is pushed onto the stack.
- When a function returns, its context is popped from the stack, and execution continues at the point where the function was called.
function chicken() {
return egg();
}
function egg() {
return chicken();
}
console.log(chicken() + " came first.");
This code causes infinite recursion. The functions chicken
and egg
keep calling each other, filling up the call stack until it overflows.
Stack Overflow
If the call stack grows too large (due to excessive function calls like the example above), a stack overflow error occurs. This happens when the computer runs out of space to store execution contexts.
Closures
A closure is a function that retains access to variables from its lexical scope, even after the function that created those variables has finished executing.
function wrapValue(n) {
let local = n;
return () => local; // Returned function remembers the `local` variable
}
let wrap1 = wrapValue(1);
let wrap2 = wrapValue(2);
console.log(wrap1()); // 1
console.log(wrap2()); // 2
Here, the wrapValue
function creates a closure. The returned function remembers the value of local
from the scope in which it was created.
Closures are powerful because they allow a function to “remember” the environment in which it was created, even if that function is called later.
function multiplier(factor) {
return number => number * factor; // Closure capturing `factor`
}
let twice = multiplier(2);
console.log(twice(5)); // 10
- The
multiplier
function creates a closure by returning a function that uses thefactor
parameter. - The
twice
function retains the environment wherefactor
was set to2
.
Recursion
A function is recursive if it calls itself in order to solve a problem. Recursive functions are commonly used to solve problems that involve repetitive sub-problems (e.g., calculating factorials, traversing trees, etc.).
function power(base, exponent) {
if (exponent === 0) {
return 1; // Base case: anything raised to the power of 0 is 1
} else {
return base * power(base, exponent - 1); // Recursive case
}
}
console.log(power(2, 3)); // 8
Solving a Problem with Recursion The problem asks for a sequence of operations (adding 5 or multiplying by 3) to reach a target number. Here’s how you could implement it recursively:
function findSolution(target) {
function find(current, history) {
if (current === target) {
return history; // Base case: return the history if we reach the target
} else if (current > target) {
return null; // Stop if we exceed the target
} else {
// Recursively try adding 5 or multiplying by 3
return find(current + 5, `${history} + 5`) || find(current * 3, `${history} * 3`);
}
}
return find(1, "1");
}
console.log(findSolution(24)); // "(((1 * 3) + 5) * 3)"
Here, the function findSolution
uses recursion to explore two possible operations at each step: adding 5 or multiplying by 3. It keeps track of the operations in the history
variable.
Growing Functions
When working with larger functions, it’s best to break them down into smaller, reusable pieces.
Formatting Output To print the number of cows, chickens, and pigs on a farm, formatted with leading zeros.
function zeroPad(number, width) {
let string = String(number);
while (string.length < width) {
string = "0" + string;
}
return string;
}
function printFarmInventory(cows, chickens, pigs) {
console.log(`${zeroPad(cows, 3)} Cows`);
console.log(`${zeroPad(chickens, 3)} Chickens`);
console.log(`${zeroPad(pigs, 3)} Pigs`);
}
printFarmInventory(7, 16, 3);
Here:
zeroPad
ensures each number is padded with zeros to be exactly three digits long.printFarmInventory
useszeroPad
to format and print each animal’s count.
- function
min
that returns the minimum of two numbers.
function min(a, b) {
return a < b ? a : b;
}
- function
countB
that counts the number ofB
characters in a string.
function countB(str) {
let count = 0;
for (let char of str) {
if (char === 'B' || char === 'b') count++;
}
return count;
}
- function
countChar
that counts occurrences of a specific character in a string.
function countChar(str, char) {
let count = 0;
for (let c of str) {
if (c === char) count++;
}
return count;
}