intermediate Step 9 of 20

Functions

JavaScript Programming

Functions

Functions are the fundamental building blocks of JavaScript applications. They encapsulate reusable logic, accept inputs (parameters), and return outputs. JavaScript supports multiple ways to define functions, including function declarations, function expressions, and arrow functions (introduced in ES6). Functions in JavaScript are first-class objects — they can be assigned to variables, passed as arguments, returned from other functions, and stored in data structures. This makes JavaScript extremely powerful for functional programming patterns.

Function Declarations vs Expressions

// Function declaration — hoisted (can be called before it's defined)
function greet(name) {
    return `Hello, ${name}!`;
}
console.log(greet("Alice"));  // "Hello, Alice!"

// Function expression — NOT hoisted
const add = function(a, b) {
    return a + b;
};
console.log(add(3, 5));  // 8

// Arrow function (ES6) — concise syntax
const multiply = (a, b) => a * b;
console.log(multiply(4, 5));  // 20

// Arrow function variations
const square = x => x * x;              // Single param: no parens needed
const getUser = () => ({ name: "Alice" }); // Return object: wrap in parens
const log = msg => {                     // Multi-line body
    console.log(`[LOG] ${msg}`);
    return msg;
};

Parameters and Arguments

// Default parameters
function createUser(name, role = "user", active = true) {
    return { name, role, active };
}
console.log(createUser("Alice"));
// { name: "Alice", role: "user", active: true }
console.log(createUser("Bob", "admin"));
// { name: "Bob", role: "admin", active: true }

// Rest parameters (...args)
function sum(...numbers) {
    return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4, 5));  // 15

// Spread in function calls
const nums = [3, 1, 4, 1, 5];
console.log(Math.max(...nums));  // 5

// Destructuring parameters
function displayUser({ name, age, role = "user" }) {
    console.log(`${name} (${age}) - ${role}`);
}
displayUser({ name: "Alice", age: 30, role: "admin" });

// Arguments object (legacy — use rest params instead)
function oldStyle() {
    console.log(arguments.length);
    console.log(arguments[0]);
}

Arrow Functions and 'this'

// Arrow functions do NOT have their own 'this'
// They inherit 'this' from the enclosing scope

const user = {
    name: "Alice",
    hobbies: ["reading", "coding", "gaming"],

    // Regular function — 'this' refers to the object
    showHobbies() {
        // Arrow function inside — inherits 'this' from showHobbies
        this.hobbies.forEach(hobby => {
            console.log(`${this.name} likes ${hobby}`);
        });
    }
};
user.showHobbies();
// "Alice likes reading"
// "Alice likes coding"
// "Alice likes gaming"

// With a regular function inside, 'this' would be lost:
// this.hobbies.forEach(function(hobby) {
//     console.log(this.name);  // undefined! 'this' is not the object
// });

// When NOT to use arrow functions:
// 1. Object methods (if you need 'this')
// 2. Event handlers in some cases
// 3. Constructors (cannot use 'new' with arrows)

Higher-Order Functions

// Functions that take functions as arguments
function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}
repeat(3, i => console.log(`Iteration ${i}`));

// Functions that return functions
function multiplier(factor) {
    return (number) => number * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5));   // 10
console.log(triple(5));   // 15

// Function composition
const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);

const addOne = x => x + 1;
const doubleIt = x => x * 2;
const squareIt = x => x * x;

const transform = compose(squareIt, doubleIt, addOne);
console.log(transform(3));  // ((3+1)*2)^2 = 64

// Immediately Invoked Function Expression (IIFE)
const result = (() => {
    const secret = "hidden";
    return secret.toUpperCase();
})();
console.log(result);  // "HIDDEN"
Pro tip: Use arrow functions for callbacks and short functions, but use regular function declarations for methods that need their own this binding. A good rule: if the function is a method on an object or class, use regular syntax. If it is a callback or standalone function, use arrow syntax.

Key Takeaways

  • Use function declarations for named, hoisted functions; arrow functions for callbacks and concise expressions.
  • Arrow functions inherit this from their enclosing scope; regular functions have their own this.
  • Default parameters, rest parameters (...args), and destructuring make function signatures flexible and readable.
  • Higher-order functions — functions that accept or return functions — enable powerful patterns like composition and currying.
  • Prefer arrow functions for callbacks (map, filter, forEach) and regular functions for object methods and constructors.