Introduction to Functions
Functions are like recipes in programming - they're reusable sets of instructions that perform specific tasks. They take inputs (ingredients), process them (cooking steps), and produce outputs (the finished dish). Functions help us write DRY (Don't Repeat Yourself) code and organize our programs into logical, reusable pieces.
graph LR
A[Input] --> B[Function]
B --> C[Output]
subgraph Function
D[Parameters]
E[Processing]
F[Return Value]
end
style B fill:#f9f,stroke:#333,stroke-width:2px
Why Use Functions?
- Reusability: Write once, use many times
- Modularity: Break complex problems into smaller pieces
- Abstraction: Hide complex details behind simple interfaces
- Maintainability: Easier to update and debug
- Testing: Test individual functions in isolation
Function Declaration
// Function declaration
function greet(name) {
return "Hello, " + name + "!";
}
// Calling the function
console.log(greet("Alice")); // "Hello, Alice!"
// Function with multiple parameters
function add(a, b) {
return a + b;
}
console.log(add(5, 3)); // 8
// Function with no parameters
function getCurrentTime() {
return new Date().toLocaleTimeString();
}
console.log(getCurrentTime()); // "2:30:45 PM"
// Function with no return value
function logMessage(message) {
console.log(message);
// Implicitly returns undefined
}
// Function that returns early
function isPositive(number) {
if (number > 0) {
return true;
}
return false;
// Or simply: return number > 0;
}
Function Expressions
// Function expression
const greet = function(name) {
return "Hello, " + name + "!";
};
// Named function expression
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // Can reference itself
};
// Immediately Invoked Function Expression (IIFE)
(function() {
console.log("This runs immediately!");
})();
// IIFE with parameters
(function(name) {
console.log("Hello, " + name + "!");
})("Alice");
// Storing functions in objects
const math = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
};
console.log(math.add(5, 3)); // 8
Arrow Functions
// Basic arrow function
const greet = (name) => {
return "Hello, " + name + "!";
};
// Concise arrow function (implicit return)
const greetShort = name => "Hello, " + name + "!";
// Multiple parameters
const add = (a, b) => a + b;
// No parameters
const sayHello = () => "Hello!";
// Multiple statements
const calculateTotal = (price, tax) => {
const taxAmount = price * tax;
const total = price + taxAmount;
return total;
};
// Arrow functions with array methods
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
const evens = numbers.filter(num => num % 2 === 0);
const sum = numbers.reduce((acc, num) => acc + num, 0);
// Lexical 'this' binding
const person = {
name: "Alice",
hobbies: ["reading", "coding", "hiking"],
showHobbies: function() {
this.hobbies.forEach(hobby => {
console.log(this.name + " likes " + hobby);
});
}
};
Function Parameters
Default Parameters
// Default parameters
function greet(name = "Guest") {
return "Hello, " + name + "!";
}
console.log(greet()); // "Hello, Guest!"
console.log(greet("Alice")); // "Hello, Alice!"
// Default parameters with expressions
function createUser(name, role = "user", id = Date.now()) {
return { name, role, id };
}
// Default parameters can reference previous parameters
function createPoint(x = 0, y = x) {
return { x, y };
}
Rest Parameters
// Rest parameters
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// Rest parameters with other parameters
function introduce(greeting, ...names) {
return greeting + " " + names.join(", ") + "!";
}
console.log(introduce("Hello", "Alice", "Bob", "Charlie"));
// "Hello Alice, Bob, Charlie!"
// Rest parameters in arrow functions
const multiply = (...args) => args.reduce((a, b) => a * b, 1);
Destructuring Parameters
// Object destructuring in parameters
function createUser({ name, age, email }) {
return {
name,
age,
email,
created: new Date()
};
}
const userData = { name: "Alice", age: 30, email: "alice@example.com" };
console.log(createUser(userData));
// Array destructuring in parameters
function getCoordinates([x, y, z = 0]) {
return { x, y, z };
}
console.log(getCoordinates([10, 20])); // { x: 10, y: 20, z: 0 }
// Nested destructuring
function processUser({ name, address: { city, country } }) {
return `${name} from ${city}, ${country}`;
}
Scope in JavaScript
graph TD
A[Global Scope] --> B[Function Scope]
B --> C[Block Scope]
subgraph "Scope Chain"
C --> B
B --> A
end
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#9cf,stroke:#333,stroke-width:2px
style C fill:#fc9,stroke:#333,stroke-width:2px
Global Scope
// Global scope
var globalVar = "I'm global";
let globalLet = "I'm also global";
const globalConst = "Me too!";
function showGlobals() {
console.log(globalVar); // Accessible
console.log(globalLet); // Accessible
console.log(globalConst); // Accessible
}
// Implicit globals (bad practice!)
function createImplicitGlobal() {
implicitGlobal = "I'm global without declaration!";
}
Function Scope
function functionScope() {
var functionVar = "I'm function-scoped";
let functionLet = "Me too!";
if (true) {
var insideIf = "I'm still in function scope";
let blockLet = "I'm block-scoped";
}
console.log(functionVar); // Works
console.log(insideIf); // Works (var ignores blocks)
// console.log(blockLet); // Error: not defined
}
// console.log(functionVar); // Error: not defined
Block Scope
// Block scope with let and const
if (true) {
let blockScoped = "I only exist in this block";
const alsoBlockScoped = "Me too!";
console.log(blockScoped); // Works
}
// console.log(blockScoped); // Error: not defined
// Loop scope
for (let i = 0; i < 3; i++) {
// i is scoped to this block
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// Compare with var
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 3, 3, 3
}
Lexical Scope and Closures
Lexical Scope
// Lexical (static) scope
const globalValue = "global";
function outer() {
const outerValue = "outer";
function inner() {
const innerValue = "inner";
console.log(globalValue); // "global"
console.log(outerValue); // "outer"
console.log(innerValue); // "inner"
}
inner();
}
outer();
Closures
// Closure basics
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
const counter2 = createCounter();
console.log(counter2()); // 1 (independent counter)
// Practical closure example
function createGreeting(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = createGreeting("Hello");
const sayHi = createGreeting("Hi");
console.log(sayHello("Alice")); // "Hello, Alice!"
console.log(sayHi("Bob")); // "Hi, Bob!"
// Closure with private variables
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function(amount) {
if (amount > 0) {
balance += amount;
return balance;
}
},
withdraw: function(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
},
getBalance: function() {
return balance;
}
};
}
const account = createBankAccount(100);
console.log(account.deposit(50)); // 150
console.log(account.withdraw(30)); // 120
console.log(account.getBalance()); // 120
// console.log(account.balance); // undefined (private)
Function Methods: call, apply, bind
// call method
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: "Alice" };
console.log(greet.call(person, "Hello", "!")); // "Hello, Alice!"
// apply method
const args = ["Hi", "!!"];
console.log(greet.apply(person, args)); // "Hi, Alice!!"
// bind method
const greetAlice = greet.bind(person);
console.log(greetAlice("Hey", ".")); // "Hey, Alice."
// Partial application with bind
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
const triple = multiply.bind(null, 3);
console.log(triple(5)); // 15
// Common use case: event handlers
const button = {
name: "Submit Button",
handleClick: function() {
console.log(this.name + " was clicked");
}
};
// Without bind, 'this' would be the DOM element
// element.addEventListener('click', button.handleClick.bind(button));
Higher-Order Functions
// Function that returns a function
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Function that takes a function as argument
function repeat(fn, times) {
for (let i = 0; i < times; i++) {
fn(i);
}
}
repeat(console.log, 3); // 0, 1, 2
// Array methods as higher-order 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);
// Custom higher-order function
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
const addOne = x => x + 1;
const double = x => x * 2;
const doubleThenAddOne = compose(addOne, double);
console.log(doubleThenAddOne(5)); // 11
Recursion
// Basic recursion
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
// Fibonacci sequence
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Fibonacci with memoization
function fibonacciMemo() {
const cache = {};
return function fib(n) {
if (n in cache) return cache[n];
if (n <= 1) return n;
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
}
const efficientFib = fibonacciMemo();
// Tree traversal
function sumTree(node) {
if (!node) return 0;
return node.value + sumTree(node.left) + sumTree(node.right);
}
// Deep object comparison
function deepEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || obj1 === null ||
typeof obj2 !== 'object' || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
Function Patterns and Best Practices
mindmap
root((Function Best Practices))
Design
Single responsibility
Small and focused
Pure functions
Descriptive names
Parameters
Limit count(max 3)
Use object for options
Default values
Validate input
Returns
Consistent return types
Early returns for errors
Meaningful values
Structure
Avoid deep nesting
Extract complex logic
Use helper functions
Keep DRY principle
Pure Functions
// Pure function (no side effects)
function add(a, b) {
return a + b;
}
// Impure function (has side effects)
let total = 0;
function addToTotal(value) {
total += value; // Modifies external state
return total;
}
// Making it pure
function addToTotal(currentTotal, value) {
return currentTotal + value;
}
Function Composition
// Function composition
const compose = (...fns) => x =>
fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => x =>
fns.reduce((v, f) => f(v), x);
// Example usage
const addTwo = x => x + 2;
const double = x => x * 2;
const square = x => x * x;
const composedFn = compose(square, double, addTwo);
console.log(composedFn(3)); // ((3 + 2) * 2)² = 100
const pipedFn = pipe(addTwo, double, square);
console.log(pipedFn(3)); // ((3 + 2) * 2)² = 100
Common Function Patterns
Debounce
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage
const debouncedSearch = debounce(searchQuery => {
console.log('Searching for:', searchQuery);
}, 300);
Throttle
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage
const throttledScroll = throttle(() => {
console.log('Scroll event');
}, 100);
Memoization
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Usage
const expensiveOperation = memoize(num => {
console.log('Computing...');
return num * num;
});
console.log(expensiveOperation(5)); // Computing... 25
console.log(expensiveOperation(5)); // 25 (cached)
Currying
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
};
}
// Usage
const regularAdd = (a, b, c) => a + b + c;
const curriedAdd = curry(regularAdd);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
Assignment: Functions and Scope
- Create a function library with:
- Mathematical operations (factorial, fibonacci, isPrime)
- String utilities (capitalize, reverse, truncate)
- Array utilities (unique, flatten, chunk)
- All functions should be pure
- Build a closure-based module:
- Private variables and methods
- Public API with getters/setters
- State management functions
- Event emitter pattern
- Implement higher-order functions:
- Custom map, filter, reduce
- Function composition utilities
- Memoization decorator
- Debounce and throttle
- Create a recursive solution for:
- Deep object cloning
- Directory tree walker
- Nested array flattening
- JSON parser (simplified)
Bonus: Create a functional programming utility library with compose, pipe, curry, and partial application!