⚙️ Functions

Reusable Code Blocks

Functions in JavaScript

Functions are reusable blocks of code that perform specific tasks. They are fundamental to organizing, structuring, and reusing code effectively.

📝 Function Declaration

// Basic function declaration
function greet() {
    console.log('Hello, World!');
}

greet();  // Call the function

// Function with parameters
function greetPerson(name) {
    console.log(`Hello, ${name}!`);
}

greetPerson('John');  // 'Hello, John!'
greetPerson('Jane');  // 'Hello, Jane!'

// Multiple parameters
function add(a, b) {
    return a + b;
}

let sum = add(5, 3);
console.log(sum);  // 8

// Return value
function multiply(x, y) {
    return x * y;
    console.log('This never runs');  // Code after return is unreachable
}

let product = multiply(4, 5);
console.log(product);  // 20

// Function without return
function sayHello() {
    console.log('Hello!');
    // Implicit return undefined
}

let result = sayHello();
console.log(result);  // undefined

🎭 Function Expressions

// Function expression
const subtract = function(a, b) {
    return a - b;
};

console.log(subtract(10, 3));  // 7

// Named function expression
const factorial = function fact(n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);  // Can use name inside
};

console.log(factorial(5));  // 120

// Difference from declaration
greetDeclaration();  // Works! (hoisted)

function greetDeclaration() {
    console.log('I am hoisted');
}

// greetExpression();  // Error! Not hoisted
const greetExpression = function() {
    console.log('I am not hoisted');
};

greetExpression();  // Works here

➡️ Arrow Functions (ES6)

// Arrow function syntax
const square = (x) => {
    return x * x;
};

// Concise syntax (implicit return)
const square2 = x => x * x;

// No parameters
const greet = () => console.log('Hello!');

// Multiple parameters
const add = (a, b) => a + b;

// Returning object literal
const makePerson = (name, age) => ({
    name: name,
    age: age
});

// Or with shorthand
const makePerson2 = (name, age) => ({ name, age });

// Multi-line function body
const isEven = num => {
    if (num % 2 === 0) {
        return true;
    }
    return false;
};

// Examples
console.log(square2(5));          // 25
console.log(add(3, 4));           // 7
console.log(makePerson('John', 30));  // { name: 'John', age: 30 }

// Array methods with arrow functions
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);

console.log(doubled);  // [2, 4, 6, 8, 10]
console.log(evens);    // [2, 4]
console.log(sum);      // 15

📦 Parameters and Arguments

Default Parameters

// ES6 default parameters
function greet(name = 'Guest', greeting = 'Hello') {
    return `${greeting}, ${name}!`;
}

console.log(greet());                    // 'Hello, Guest!'
console.log(greet('John'));              // 'Hello, John!'
console.log(greet('Jane', 'Hi'));        // 'Hi, Jane!'

// Default with expressions
function createUser(name, role = 'user', id = Date.now()) {
    return { name, role, id };
}

// Default can reference other parameters
function calculatePrice(price, tax = price * 0.1) {
    return price + tax;
}

console.log(calculatePrice(100));  // 110 (100 + 10)

Rest Parameters

// Collect remaining arguments into array
function sum(...numbers) {
    return numbers.reduce((acc, n) => acc + n, 0);
}

console.log(sum(1, 2, 3));         // 6
console.log(sum(1, 2, 3, 4, 5));   // 15

// Rest must be last parameter
function greetAll(greeting, ...names) {
    return names.map(name => `${greeting}, ${name}!`);
}

console.log(greetAll('Hello', 'John', 'Jane', 'Bob'));
// ['Hello, John!', 'Hello, Jane!', 'Hello, Bob!']

// Practical example
function max(...nums) {
    if (nums.length === 0) return -Infinity;
    return Math.max(...nums);
}

console.log(max(3, 7, 2, 9, 1));  // 9

Arguments Object (Legacy)

// arguments object (array-like, not available in arrow functions)
function oldSum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

console.log(oldSum(1, 2, 3, 4));  // 10

// Convert to real array
function toArray() {
    return Array.from(arguments);
    // Or: return [...arguments];
}

console.log(toArray(1, 2, 3));  // [1, 2, 3]

// Note: Use rest parameters instead in modern code

🔄 Return Values

// Single return value
function divide(a, b) {
    if (b === 0) {
        return 'Cannot divide by zero';
    }
    return a / b;
}

// Multiple returns (early return pattern)
function getGrade(score) {
    if (score < 0 || score > 100) {
        return 'Invalid score';
    }
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    if (score >= 70) return 'C';
    if (score >= 60) return 'D';
    return 'F';
}

// Return object
function getUser(id) {
    return {
        id: id,
        name: 'John',
        email: 'john@example.com'
    };
}

// Return array
function getCoordinates() {
    return [40.7128, -74.0060];
}

// Destructure return values
const [lat, lng] = getCoordinates();

// Return function (higher-order function)
function multiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

🎯 Scope and Closures

Function Scope

// Variables declared in function are local
function myFunction() {
    let localVar = 'I am local';
    console.log(localVar);  // Works
}

myFunction();
// console.log(localVar);  // Error: localVar is not defined

// Nested scope
function outer() {
    let outerVar = 'outer';
    
    function inner() {
        let innerVar = 'inner';
        console.log(outerVar);  // Can access outer
        console.log(innerVar);  // Can access inner
    }
    
    inner();
    // console.log(innerVar);  // Error: can't access inner
}

outer();

Closures

// Function remembers outer scope
function createCounter() {
    let count = 0;  // Private variable
    
    return {
        increment() {
            count++;
            return count;
        },
        decrement() {
            count--;
            return count;
        },
        getCount() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.getCount());   // 2
console.log(counter.decrement());  // 1

// Practical: Private data
function createBankAccount(initialBalance) {
    let balance = initialBalance;
    
    return {
        deposit(amount) {
            if (amount > 0) {
                balance += amount;
                return balance;
            }
            return 'Invalid amount';
        },
        withdraw(amount) {
            if (amount > 0 && amount <= balance) {
                balance -= amount;
                return balance;
            }
            return 'Invalid amount or insufficient funds';
        },
        getBalance() {
            return balance;
        }
    };
}

const account = createBankAccount(1000);
console.log(account.deposit(500));   // 1500
console.log(account.withdraw(200));  // 1300
console.log(account.getBalance());   // 1300
// console.log(balance);  // Error: balance is private

🔝 Higher-Order Functions

// Functions that take or return functions

// Take function as argument
function operate(a, b, operation) {
    return operation(a, b);
}

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

console.log(operate(5, 3, add));       // 8
console.log(operate(5, 3, multiply));  // 15

// Return function
function greeter(greeting) {
    return function(name) {
        return `${greeting}, ${name}!`;
    };
}

const sayHello = greeter('Hello');
const sayHi = greeter('Hi');

console.log(sayHello('John'));  // 'Hello, John!'
console.log(sayHi('Jane'));     // 'Hi, Jane!'

// Common higher-order functions
const numbers = [1, 2, 3, 4, 5];

// map - transform each element
const doubled = numbers.map(n => n * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

// filter - keep elements that pass test
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens);  // [2, 4]

// reduce - combine elements into single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum);  // 15

// forEach - execute function for each element
numbers.forEach(n => console.log(n * n));

// find - get first element that passes test
const firstEven = numbers.find(n => n % 2 === 0);
console.log(firstEven);  // 2

// some - test if any element passes
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven);  // true

// every - test if all elements pass
const allPositive = numbers.every(n => n > 0);
console.log(allPositive);  // true

🎨 Function Methods

call, apply, bind

// call - invoke function with specific this
function greet() {
    return `Hello, ${this.name}!`;
}

const person1 = { name: 'John' };
const person2 = { name: 'Jane' };

console.log(greet.call(person1));  // 'Hello, John!'
console.log(greet.call(person2));  // 'Hello, Jane!'

// call with arguments
function introduce(city, country) {
    return `${this.name} from ${city}, ${country}`;
}

console.log(introduce.call(person1, 'New York', 'USA'));
// 'John from New York, USA'

// apply - same as call but array of arguments
console.log(introduce.apply(person1, ['New York', 'USA']));

// bind - create new function with bound this
const greetJohn = greet.bind(person1);
console.log(greetJohn());  // 'Hello, John!'

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

const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

🔁 Recursion

// Function that calls itself

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

console.log(factorial(5));  // 120 (5 * 4 * 3 * 2 * 1)

// Fibonacci
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(7));  // 13

// Countdown
function countdown(num) {
    if (num <= 0) {
        console.log('Done!');
        return;
    }
    console.log(num);
    countdown(num - 1);
}

countdown(5);  // 5, 4, 3, 2, 1, Done!

// Sum array recursively
function sumArray(arr) {
    if (arr.length === 0) return 0;
    return arr[0] + sumArray(arr.slice(1));
}

console.log(sumArray([1, 2, 3, 4, 5]));  // 15

// Flatten nested array
function flatten(arr) {
    let result = [];
    for (let item of arr) {
        if (Array.isArray(item)) {
            result = result.concat(flatten(item));
        } else {
            result.push(item);
        }
    }
    return result;
}

console.log(flatten([1, [2, [3, 4], 5], 6]));
// [1, 2, 3, 4, 5, 6]

💡 Practical Examples

Utility Functions

// Debounce - delay execution until user stops action
function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func(...args), delay);
    };
}

// Usage: search as user types
const search = debounce((query) => {
    console.log(`Searching for: ${query}`);
}, 500);

// Throttle - limit execution frequency
function throttle(func, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            func(...args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// Memoization - cache results
function memoize(fn) {
    const cache = {};
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            return cache[key];
        }
        const result = fn(...args);
        cache[key] = result;
        return result;
    };
}

const slowFib = memoize(function fib(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
});

console.log(slowFib(40));  // Fast due to caching

Function Composition

// Compose multiple functions
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);

const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const transform = compose(square, double, addOne);
console.log(transform(5));  // 144 ((5+1)*2)^2

// Pipe (left to right)
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

const transform2 = pipe(addOne, double, square);
console.log(transform2(5));  // 144

// Practical: Data transformation
const users = [
    { name: 'john doe', age: 25, active: true },
    { name: 'jane smith', age: 30, active: false }
];

const capitalize = str => str.split(' ').map(w => 
    w.charAt(0).toUpperCase() + w.slice(1)
).join(' ');

const processUser = pipe(
    user => ({ ...user, name: capitalize(user.name) }),
    user => ({ ...user, status: user.active ? 'Active' : 'Inactive' })
);

console.log(users.map(processUser));

Currying

// Transform function with multiple args into sequence of functions
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn(...args);
        }
        return function(...nextArgs) {
            return curried(...args, ...nextArgs);
        };
    };
}

// Example function
function sum(a, b, c) {
    return a + b + c;
}

const curriedSum = curry(sum);

console.log(curriedSum(1)(2)(3));     // 6
console.log(curriedSum(1, 2)(3));     // 6
console.log(curriedSum(1)(2, 3));     // 6

// Practical: Reusable configurations
const multiply = curry((a, b, c) => a * b * c);

const multiplyByTwo = multiply(2);
const multiplyByTwoAndThree = multiplyByTwo(3);

console.log(multiplyByTwoAndThree(4));  // 24 (2*3*4)

🎯 Key Takeaways