JavaScript Functions Advanced

In JavaScript, functions are first-class citizens. That means you can store them in variables, pass them into other functions, and return them from functions, just like any other value.

javascript

const greet = function() { return "Hello!"; };
console.log(greet()); // Hello!

A higher-order function takes another function as input, returns one as output, or does both. You will see this pattern everywhere, especially with methods like map, filter, and reduce.

javascript

function applyTwice(fn, value) {
  return fn(fn(value));
}
const double = x => x * 2;
console.log(applyTwice(double, 3)); // 12

A callback function is a function you pass to another function with the instruction: "run this later at the right time." Callbacks are common in timers, event listeners, and async code.

javascript

function processOrder(orderId, callback) {
  const message = "Order " + orderId + " confirmed!";
  callback(message);
}
processOrder("ORD-1234", msg => console.log(msg)); // Order ORD-1234 confirmed!

setTimeout schedules a function to run once after a given delay in milliseconds. It returns a timer ID that you can pass to clearTimeout to cancel the scheduled call before it runs.

javascript

const timer = setTimeout(() => {
  console.log("Runs after 2 seconds");
}, 2000);

// Cancel before it runs
clearTimeout(timer);

setInterval works like a repeating alarm clock. It runs your function again and again at a fixed interval. When you want it to stop, pass its ID to clearInterval.

javascript

let count = 0;
const interval = setInterval(() => {
  count++;
  console.log("Count:", count);
  if (count === 3) clearInterval(interval);
}, 1000);

call() and apply() let you invoke a function with a specific this value. The difference is how you pass arguments: call() takes them one by one, while apply() takes them as an array.

javascript

function showDetails(region, timezone) {
  console.log(this.label + " - " + region + ", " + timezone);
}
const server = { label: "API-East" };
showDetails.call(server, "US-East", "UTC-5");
showDetails.apply(server, ["US-East", "UTC-5"]);

bind() creates a new function with a fixed this value. Unlike call() and apply(), it does not call the function right away. You can store the bound function and call it later.

javascript

function logStatus() {
  console.log("Status: " + this.status);
}
const service = { status: "running" };
const boundLog = logStatus.bind(service);
boundLog(); // Status: running

An IIFE runs the moment you define it. It's essentially a one-time setup: it creates its own private scope, so nothing inside leaks out to the global scope. It's handy for initialization code and avoiding naming conflicts.

javascript

(function() {
  const secret = "hidden";
  console.log("IIFE runs immediately!");
})();
// console.log(secret); // ReferenceError

A closure means a function remembers variables from the scope where it was created, even after that outer scope has finished running.

javascript

function counter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2

Before your code runs, JavaScript moves declarations to the top of their scope. This is called hoisting. Function declarations are fully hoisted, so you can call them before they appear. var is hoisted but not its value (you'll get undefined early), while let and const are hoisted but stay uninitialized until their line runs.

javascript

console.log(sayHi()); // "Hi!" - works because declaration is hoisted
function sayHi() { return "Hi!"; }

console.log(x); // undefined - var is hoisted but not its value
var x = 5;

The Temporal Dead Zone (TDZ) is the gap between entering a scope and the line where your let or const is declared. If you try to use the variable in that gap, you'll get a ReferenceError. With var, you'd just get undefined instead of an error.

javascript

console.log(endpoint); // ReferenceError: Cannot access 'endpoint' before initialization
let endpoint = "/api/users";

DRY stands for "Don't Repeat Yourself." It means you should extract repeated logic into a function so it lives in one place. This makes your code easier to maintain and reduces the chance of bugs when something needs to change.

javascript

// Without DRY
console.log("Tax for 100:", 100 * 0.18);
console.log("Tax for 200:", 200 * 0.18);

// With DRY
function calculateTax(amount) { return amount * 0.18; }
console.log("Tax for 100:", calculateTax(100));
console.log("Tax for 200:", calculateTax(200));

Currying breaks a function with multiple arguments into a chain of functions, each taking one argument. Imagine an assembly line where each step adds one piece. This lets you create reusable, pre-filled versions of functions.

javascript

function multiply(a) {
  return function(b) {
    return a * b;
  };
}
const triple = multiply(3);
console.log(triple(4));       // 12
console.log(multiply(2)(5));  // 10

eval() takes a string and runs it as JavaScript code. You should avoid it in most cases because it's slow, hard to debug, and opens the door to security risks with untrusted input.

javascript

const code = "2 + 2";
console.log(eval(code)); // 4

// Avoid eval - use safer alternatives instead

Recursion is when a function calls itself to solve smaller versions of the same problem. The most important part is the base case, which tells the function when to stop.

javascript

function factorial(n) {
  if (n <= 1) return 1;        // base case
  return n * factorial(n - 1); // recursive call
}
console.log(factorial(5)); // 120
  • First-class functions can be stored in variables, passed as arguments, and returned from other functions.
  • Higher-order functions take a function as an argument or return one.
  • Callback functions are passed into another function and called inside it.
  • setTimeout runs a function once after a delay; clearTimeout cancels it.
  • setInterval runs a function repeatedly; clearInterval stops it.
  • call() and apply() invoke a function with a specific this; apply uses an array for arguments.
  • bind() returns a new function with a fixed this, without calling it immediately.
  • IIFE is a function that runs immediately and creates a private scope.
  • Closures let inner functions remember variables from their outer scope after the outer function has returned.
  • Hoisting moves declarations to the top of their scope. Function declarations are fully hoisted; var is hoisted but not its value.
  • TDZ is the zone where let/const variables exist but cannot be accessed yet.
  • DRY means "Don't Repeat Yourself" - extract repeated logic into reusable functions.
  • Currying converts a multi-argument function into a chain of single-argument functions.
  • eval() runs a string as code - avoid it due to performance and security concerns.
  • Recursion is when a function calls itself, always with a base case to stop the loop.

What's next? Now that you've seen more powerful function concepts, let's learn how to spot and fix problems in the next tutorial.

Videos for this topic will be added soon.