JavaScript Functions Advanced

In JavaScript, functions are first-class citizens. Think of them like any other value: you can store a function in a variable, pass it to another function, or return it from one. They're as flexible as numbers or strings.

javascript

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

A higher-order function either takes a function as an argument, returns a function, or both. You'll run into them everywhere in JavaScript, especially with array 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 hand off to another function, saying "run this when you're ready." You'll use callbacks a lot with API calls, timers, and event listeners.

javascript

function greetUser(name, callback) {
  const message = "Hello, " + name + "!";
  callback(message);
}
greetUser("Alice", msg => console.log(msg)); // Hello, Alice!

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. It runs your function over and over at a fixed interval (in milliseconds). When you want it to stop, pass the interval 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 introduce(city, country) {
  console.log(this.name + " from " + city + ", " + country);
}
const person = { name: "Alice" };
introduce.call(person, "Paris", "France");
introduce.apply(person, ["Paris", "France"]);

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 greet() {
  console.log("Hi, " + this.name);
}
const user = { name: "Bob" };
const boundGreet = greet.bind(user);
boundGreet(); // Hi, Bob

An IIFE runs the moment you define it. Think of it like 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 happens when a function "remembers" variables from where it was created, even after that outer function is done. It's like a backpack: the inner function carries its surrounding variables wherever it goes.

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(name); // ReferenceError: Cannot access 'name' before initialization
let name = "Alice";

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. Think of it like 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 break a big problem into smaller pieces. Think of Russian nesting dolls: each one opens to reveal a smaller one until you reach the last. That last doll is your 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.