Advanced Functions Β· Astro Tech Blog

Advanced Working with Functions

Recursion and Stack

Recursion is when a function calls itself.

function countDown(n) {
  if (n <= 0) {
    console.log("Done!");
    return;
  }
  console.log(n);
  countDown(n - 1);
}

countDown(3);
// β†’ 3, 2, 1, "Done!"

Recursive Factorial

function factorial(n) {
  if (n <= 1) return 1; // Base case
  return n * factorial(n - 1); // Recursive call
}

console.log(factorial(5)); // β†’ 120 (5 Γ— 4 Γ— 3 Γ— 2 Γ— 1)

Recursive vs Iterative

// Iterative
function powIter(x, n) {
  let result = 1;
  for (let i = 0; i < n; i++) result *= x;
  return result;
}

// Recursive
function powRecur(x, n) {
  if (n === 0) return 1;
  return x * powRecur(x, n - 1);
}

console.log(powIter(2, 3));  // β†’ 8
console.log(powRecur(2, 3)); // β†’ 8

Execution Stack

Each recursive call is pushed onto the call stack:

pow(2, 3)
  β†’ 2 * pow(2, 2)
    β†’ 2 * 2 * pow(2, 1)
      β†’ 2 * 2 * 2 * pow(2, 0)
        β†’ 2 * 2 * 2 * 1
      β†’ 2 * 2 * 2
    β†’ 2 * 4
  β†’ 8

Deep Recursion

// Recursive tree traversal
let company = {
  name: "Big Corp",
  departments: [
    { name: "Engineering", employees: 50 },
    {
      name: "Sales",
      employees: 30,
      sub: [
        { name: "Domestic", employees: 20 },
        { name: "International", employees: 10 }
      ]
    }
  ]
};

function countEmployees(dept) {
  let total = dept.employees || 0;
  if (dept.sub) {
    for (let sub of dept.sub) {
      total += countEmployees(sub);
    }
  }
  return total;
}

console.log(countEmployees(company)); // β†’ 110

Rest Parameters and Spread Syntax

Rest Parameters ...

Collect remaining arguments into an array:

function sum(...nums) {
  let total = 0;
  for (let n of nums) total += n;
  return total;
}

console.log(sum(1, 2, 3, 4, 5)); // β†’ 15

Rest parameters must be the last parameter:

function show(name, age, ...hobbies) {
  console.log(name);     // β†’ "Alice"
  console.log(age);      // β†’ 30
  console.log(hobbies);  // β†’ ["reading", "coding", "gaming"]
}

show("Alice", 30, "reading", "coding", "gaming");

Spread Syntax ...

Spread expands an iterable into individual elements:

// Arrays
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2]; // β†’ [1, 2, 3, 4, 5, 6]

// Copy array
let copy = [...arr1];

// String to characters
let chars = [..."hello"]; // β†’ ["h", "e", "l", "l", "o"]

// Function arguments
let numbers = [3, 8, 1, 6, 2];
console.log(Math.max(...numbers)); // β†’ 8

// Objects (ES2018)
let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3, ...obj1 }; // β†’ { c: 3, a: 1, b: 2 }

// Merge objects (later properties overwrite earlier)
let merged = { ...obj1, ...obj2 }; // β†’ { a: 1, b: 2, c: 3 }

Variable Scope, Closure

Lexical Environment

Every function, block, and script has an associated Lexical Environment that stores local variables.

let x = 10; // Global

function outer() {
  let y = 20; // Outer

  function inner() {
    let z = 30; // Inner
    console.log(x + y + z); // Can access all three!
  }

  inner();
}

outer(); // β†’ 60

Closure

A closure is a function that β€œremembers” its lexical scope even when called outside of it.

function createCounter() {
  let count = 0; // Private variable

  return function() {
    count++;
    return count;
  };
}

let counter = createCounter();

console.log(counter()); // β†’ 1
console.log(counter()); // β†’ 2
console.log(counter()); // β†’ 3

// count is not accessible from outside
console.log(counter.count); // β†’ undefined

Practical Closures

// Closure for private data
function createBankAccount(initialBalance) {
  let balance = initialBalance;

  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) return "Insufficient funds";
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    }
  };
}

let account = createBankAccount(1000);
console.log(account.deposit(500));  // β†’ 1500
console.log(account.withdraw(200)); // β†’ 1300
console.log(account.getBalance());  // β†’ 1300
// console.log(account.balance);    // β†’ undefined β€” private!

Closure in Loops (the classic problem)

// ❌ Classic problem
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// β†’ 3, 3, 3 (all reference the same `var i`)

// βœ… Fix with let (block scope)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// β†’ 0, 1, 2

// βœ… Fix with closure (pre-ES6)
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 1000);
  })(i);
}
// β†’ 0, 1, 2

The Old β€œvar”

Before ES6, var was the only way to declare variables. It’s still used in older code.

var has no block scope

if (true) {
  var x = 10;
  let y = 20;
}

console.log(x); // β†’ 10 (var ignores blocks)
// console.log(y); // ReferenceError!

var hoisting

console.log(z); // β†’ undefined (not an error!)
var z = 5;

// Same as:
// var z;
// console.log(z);
// z = 5;

var in loops

for (var i = 0; i < 5; i++) {
  // i is visible outside the loop!
}
console.log(i); // β†’ 5

IIFE pattern with var

Before modules, IIFEs were used to create private scopes:

(function() {
  var privateVar = "secret";
  // Can't access from outside
})();

Modern rule: Use let and const. Never use var in new code.

Global Object

In browsers, the global object is window. In Node.js, it’s global. The modern standard is globalThis.

// Browser
console.log(window); // Global object
console.log(globalThis); // Works everywhere

// Variables declared with var become global properties
var x = 10;
console.log(window.x); // β†’ 10 (var only)

// let/const do NOT become global properties
let y = 20;
console.log(window.y); // β†’ undefined

// Global functions
window.alert("Hello!");
console.log(globalThis.parseInt === parseInt); // β†’ true

Function Object, NFE

Functions are objects β€” they have properties and methods:

function greet(name) {
  return "Hello, " + name;
}

// Functions have properties
console.log(greet.name); // β†’ "greet"
console.log(greet.length); // β†’ 1 (number of parameters)

// You can add custom properties
greet.counter = 0;
greet.counter++;
console.log(greet.counter); // β†’ 1

Named Function Expression (NFE)

// Anonymous function expression
let sayHi = function(name) {
  if (name) return "Hi, " + name;
};

// Named function expression β€” has a name for internal reference
let sayHi = function greeting(name) {
  if (name) return "Hi, " + name;
  // return greeting("Guest"); // Can reference itself
};

console.log(sayHi.name); // β†’ "greeting"

The β€œnew Function” Syntax

Create a function from a string at runtime:

let sum = new Function("a", "b", "return a + b");
console.log(sum(2, 3)); // β†’ 5

// No parameters
let sayHi = new Function('console.log("Hello")');
sayHi(); // β†’ "Hello"

Security note: new Function is rarely used and can be dangerous if the string comes from user input (code injection risk).

Scheduling: setTimeout and setInterval

setTimeout

Run a function once after a delay:

function greet() {
  console.log("Hello!");
}

// Run after 2 seconds
setTimeout(greet, 2000);

// With arguments
setTimeout((name) => console.log("Hello, " + name), 1000, "Alice");

// Cancel a timeout
let timeoutId = setTimeout(() => console.log("Never runs"), 3000);
clearTimeout(timeoutId);

setInterval

Run a function repeatedly at intervals:

let count = 0;
let intervalId = setInterval(() => {
  count++;
  console.log("Tick " + count);
  if (count >= 5) clearInterval(intervalId);
}, 1000);
// β†’ "Tick 1", "Tick 2", ... "Tick 5"

Recursive setTimeout vs setInterval

// setInterval β€” fixed interval, doesn't wait for execution
let id = setInterval(() => {
  console.log("Running...");
}, 1000);

// Recursive setTimeout β€” waits for execution to finish
function recursiveTimeout() {
  console.log("Running...");
  setTimeout(recursiveTimeout, 1000);
}

// Recursive setTimeout guarantees the delay between completions

Zero Delay setTimeout

setTimeout(() => console.log("World"), 0);
console.log("Hello");
// β†’ "Hello" then "World"

// Even with 0ms delay, it runs after current code finishes
// (scheduled on the macrotask queue)
Demo: Timers Demo
HTML
<div id='timer'></div>
JavaScript
let count = 0;
let el = document.getElementById('timer');

let intervalId = setInterval(() => {
count++;
el.textContent = 'Count: ' + count;
if (count >= 10) {
clearInterval(intervalId);
el.textContent = 'Done!';
}
}, 500);
Live Output Window

Decorators and Forwarding, call/apply

Decorator β€” a function that wraps another function

function slow(x) {
  // Heavy computation
  let result = 0;
  for (let i = 0; i < x * 1e7; i++) result += i;
  return result;
}

// Caching decorator
function caching(fn) {
  let cache = new Map();

  return function(x) {
    if (cache.has(x)) {
      console.log("From cache: " + x);
      return cache.get(x);
    }

    let result = fn(x);
    cache.set(x, result);
    return result;
  };
}

let cachedSlow = caching(slow);
console.log(cachedSlow(5)); // Computed
console.log(cachedSlow(5)); // From cache

Function.call

Call a function with a specific this:

function greet() {
  console.log("Hello, " + this.name);
}

let user1 = { name: "Alice" };
let user2 = { name: "Bob" };

greet.call(user1); // β†’ "Hello, Alice"
greet.call(user2); // β†’ "Hello, Bob"

// With arguments
function introduce(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}

introduce.call(user1, "Hi", "!"); // β†’ "Hi, Alice!"

Function.apply

Same as call but arguments are passed as an array:

introduce.apply(user1, ["Hello", "."]); // β†’ "Hello, Alice."

// Useful for spreading arrays
let numbers = [3, 5, 1, 8, 2];
console.log(Math.max.apply(null, numbers)); // β†’ 8 (Math.max doesn't use this)

Borrowing methods

let obj = {
  0: "hello",
  1: "world",
  length: 2
};

// Borrow Array.prototype.join
let result = Array.prototype.join.call(obj, ", ");
console.log(result); // β†’ "hello, world"

Function Binding

When passing an object method as a callback, this is lost:

let user = {
  name: "Alice",
  greet() {
    console.log("Hello, " + this.name);
  }
};

// Direct call works
user.greet(); // β†’ "Hello, Alice"

// Passed as callback β€” this is lost
setTimeout(user.greet, 1000); // β†’ "Hello, undefined"

Solution 1: Wrapper

setTimeout(() => user.greet(), 1000); // β†’ "Hello, Alice"

Solution 2: Function.bind

let boundGreet = user.greet.bind(user);
boundGreet(); // β†’ "Hello, Alice"
setTimeout(boundGreet, 1000); // β†’ "Hello, Alice"

// bind with arguments (partial application)
function multiply(a, b) {
  return a * b;
}

let double = multiply.bind(null, 2); // Pre-fill first argument
console.log(double(5)); // β†’ 10

Partial application

function log(level, message) {
  console.log(`[${level}] ${message}`);
}

let info = log.bind(null, "INFO");
let error = log.bind(null, "ERROR");

info("System started"); // β†’ "[INFO] System started"
error("Something broke"); // β†’ "[ERROR] Something broke"

Arrow Functions Revisited

Arrow functions are not just shorter syntax β€” they have fundamental differences:

No β€œthis”

let user = {
  name: "Alice",
  greet() {
    // Regular function β€” has own this
    setTimeout(function() {
      console.log("Hello, " + this.name); // this is window/undefined
    }, 100);
  },

  greetArrow() {
    // Arrow β€” inherits this from surrounding scope
    setTimeout(() => {
      console.log("Hello, " + this.name); // this is user
    }, 100);
  }
};

user.greet();       // β†’ "Hello, undefined"
user.greetArrow();  // β†’ "Hello, Alice"

No β€œarguments”

function regular() {
  console.log(arguments); // β†’ [1, 2, 3]
}

let arrow = () => {
  console.log(arguments); // ReferenceError (or inherits from parent)
};

regular(1, 2, 3);

Can’t be used with β€œnew”

let Foo = () => {};
// new Foo(); // TypeError: Foo is not a constructor

When to use arrows vs regular

ScenarioUse
Short callbacks, array methodsArrow
Object methods that need thisRegular
Constructor functionsRegular
Need arguments objectRegular
Event handlers (need this = element)Regular
Demo: Arrow vs Regular Demo
JavaScript
let numbers = [1, 2, 3, 4, 5, 6];

// Arrow functions shine in callbacks
let evens = numbers.filter(n => n % 2 === 0);
let squares = numbers.map(n => n * n);
let sum = numbers.reduce((acc, n) => acc + n, 0);

document.write('Evens: ' + evens.join(', ') + '<br>');
document.write('Squares: ' + squares.join(', ') + '<br>');
document.write('Sum: ' + sum + '<br>');

// Arrow with this inheritance (demonstration)
let timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++;
}, 1000);
},
getTime() {
return this.seconds;
}
};

timer.start();
Live Output Window