Functions - Reusable Code Blocks
TL;DR — Quick Summary
- Arrow functions use concise syntax and don't have their own
this— prefer them for callbacks and short functions. - Functions should do one thing, have a descriptive verb-based name, and return a value rather than mutate external state.
- Default parameters (
function fn(x = 0)) handle missing arguments cleanly — better thanx = x || 0inside the body. - Pure functions — same input always gives same output, no side effects — are easier to test, debug, and reuse.
Lesson Overview
Functions are the fundamental building block of reusable code. A function packages a piece of logic under a name, so you can call it multiple times without repeating the code. Well-designed functions are the cornerstone of maintainable software.
JavaScript has four ways to create functions: function declarations (hoisted), function expressions (assigned to a variable), arrow functions (concise syntax, no own this), and methods (functions as object properties). Each has different behaviour — particularly around this binding and hoisting.
Key principles: functions should do one thing, take minimal parameters, return a value rather than cause side effects where possible, and have descriptive names starting with a verb.
Conceptual Deep Dive
Parameters and arguments: Parameters are the named variables in the function definition. Arguments are the actual values passed when calling. Default parameters (function greet(name = 'World')) provide fallbacks for missing arguments.
Return values: Functions should return a value when they compute something. Without a return statement, functions return undefined. Arrow functions with a single expression can use an implicit return (no braces, no return keyword).
Scope: Variables declared inside a function are not accessible outside (function scope). Functions can read variables from their outer scope (closure).
Pure functions: Given the same inputs, always return the same output, with no side effects. These are the easiest to test and reason about.
Higher-order functions: Functions that take other functions as parameters or return functions. Array methods like map, filter, sort are higher-order functions.
Implementation Lab
// 1. Function declaration — hoisted (can call before definition)Function declaration — hoisted (can call before definition)
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
// 2. Function expression — NOT hoistedFunction expression — NOT hoisted
const multiply = function(a, b) {
return a * b;
};
// 3. Arrow function — concise, no own 'this'Arrow function — concise, no own 'this'
const divide = (a, b) => a / b; // implicit return
const square = n => n * n; // single param, no parens needed
const greet = () => 'Hello!'; // no params
// 4. Arrow with body (multiple statements)Arrow with body (multiple statements)
const clamp = (value, min, max) => {
if (value < min) return min;
if (value > max) return max;
return value;
};
// Default parameters
function createUser(name, role = 'viewer', active = true) {
return { name, role, active };
}
console.log(createUser('Alice')); // { name: 'Alice', role: 'viewer', active: true }'Alice', role: 'viewer', active: true }
console.log(createUser('Bob', 'admin')); // { name: 'Bob', role: 'admin', active: true }'Bob', role: 'admin', active: true }
// Rest parameters — collect remaining args into an array
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15// ✅ Pure function: same input → same output, no side effectsfunction: same input → same output, no side effects
const calculateTax = (price, rate) => price * rate;
const applyDiscount = (price, percent) => price * (1 - percent / 100);
const formatCurrency = (amount) => `$${amount.toFixed(2)}`(2)}`;
// Composing pure functions
const getDisplayPrice = (price) => {
const withTax = calculateTax(price, 0.08);
const total = price + withTax;
return formatCurrency(total);
};
console.log(getDisplayPrice(100)); // '$108.00'
// ❌ Impure: modifies external state (side effect)(side effect)
let taxTotal = 0;
function addTax_impure(price, rate) {
taxTotal += price * rate; // side effect: modifies external variable
return price * (1 + rate);
}
// Higher-order functions: functions as arguments
const numbers = [1, 2, 3, 4, 5];
const isEven = n => n % 2 === 0; // predicate function
const double = n => n * 2; // transform function
console.log(numbers.filter(isEven)); // [2, 4]2, 4]
console.log(numbers.map(double)); // [2, 4, 6, 8, 10]2, 4, 6, 8, 10]
// Function returning a function (closure)function (closure)
const createMultiplier = (factor) => {
return (number) => number * factor; // closes over 'factor'
};
const triple = createMultiplier(3);
const tenX = createMultiplier(10);
console.log(triple(5)); // 15
console.log(tenX(5)); // 50Pro Tips — Senior Dev Insights
Name functions starting with a verb that describes what they do: getUserById, calculateTotal, isValidEmail, formatDate. Functions that return booleans should start with is/has/can.
Use destructuring in parameters for objects: function display({ name, email }) instead of function display(user) — it documents exactly which properties are needed.
An immediately invoked function expression (IIFE) (function() { ... })() creates a private scope — useful in legacy code and module patterns.
Common Developer Pitfalls
return a value — a function without a return returns undefined, silently producing NaN or 'undefined' bugs downstream.this — arrow functions inherit this from their definition scope, not from the calling object. Use regular function syntax for methods.Creating functions that do too many things — if you need an 'and' in a function's description ('validate AND save AND redirect'), split it into separate functions.
function createUser({ name, role, active }).Interview Mastery
Function declarations are hoisted — you can call them before their definition in the file. Arrow functions are not hoisted (they're assigned to variables, which are not initialised before the declaration line). The key difference is 'this' binding: regular functions have their own 'this' that depends on how they're called (the object that calls them). Arrow functions inherit 'this' from their surrounding lexical scope. This makes arrow functions ideal for callbacks and array methods, but inappropriate for object methods that need to reference 'this'.
A closure is a function that remembers and accesses variables from its outer scope even after the outer function has finished executing. Practical example: a counter factory — function createCounter() { let count = 0; return { increment: () => ++count, get: () => count }; }. The returned object's methods close over the count variable. Real-world uses: data privacy (variables not accessible from outside), memoisation (caching results), and callbacks that need to remember context (setTimeout, event handlers).
A pure function: (1) given the same inputs, always returns the same output, (2) produces no side effects (doesn't modify external state, make API calls, or log). Why it matters: predictability (easy to reason about), testability (no mocking needed), reusability (no hidden dependencies), and composability (can be combined safely). Example: Math.max, calculateTax. Impure: functions that modify global state, make API calls, or depend on random/time. Favour pure functions for business logic; confine side effects to specific layers.
Higher-order functions either take functions as arguments or return functions. Built-in examples: Array.map, filter, reduce, sort, forEach — they all accept a callback function. Custom example: a middleware pattern (function withLogging(fn) { return (...args) => { console.log('calling'); return fn(...args); }; }). React hooks like useState return values and setters. Event listeners (addEventListener) take callbacks. They're fundamental to functional programming and enable abstraction, composition, and code reuse.
Hands-on Lab Exercises
Write three versions of the same function (add two numbers): as a declaration, expression, and arrow function — call each and verify they work identically.
formatDate, truncateString, capitalize, isValidEmail. Test each with multiple inputs.increment, decrement, and reset methods — verify the count is private.createValidator(rules) that returns a validation function — each rule is a function that returns an error string or null.Real-World Practice Scenarios
addItem, removeItem, applyDiscount, calculateTotal — each takes the cart as input and returns a new cart.memoize(fn) function that caches expensive function results using a closure and Map.Deepen Your Knowledge
Functions - Reusable Code Blocks
TL;DR — Quick Summary
- Arrow functions use concise syntax and don't have their own
this— prefer them for callbacks and short functions. - Functions should do one thing, have a descriptive verb-based name, and return a value rather than mutate external state.
- Default parameters (
function fn(x = 0)) handle missing arguments cleanly — better thanx = x || 0inside the body. - Pure functions — same input always gives same output, no side effects — are easier to test, debug, and reuse.
Overview
Functions are the fundamental building block of reusable code. A function packages a piece of logic under a name, so you can call it multiple times without repeating the code. Well-designed functions are the cornerstone of maintainable software.
JavaScript has four ways to create functions: function declarations (hoisted), function expressions (assigned to a variable), arrow functions (concise syntax, no own this), and methods (functions as object properties). Each has different behaviour — particularly around this binding and hoisting.
Key principles: functions should do one thing, take minimal parameters, return a value rather than cause side effects where possible, and have descriptive names starting with a verb.
Deep Dive Analysis
<p><strong>Parameters and arguments:</strong> Parameters are the named variables in the function definition. Arguments are the actual values passed when calling. Default parameters (<code>function greet(name = 'World')</code>) provide fallbacks for missing arguments.</p><p><strong>Return values:</strong> Functions should return a value when they compute something. Without a <code>return</code> statement, functions return <code>undefined</code>. Arrow functions with a single expression can use an implicit return (no braces, no return keyword).</p><p><strong>Scope:</strong> Variables declared inside a function are not accessible outside (function scope). Functions can read variables from their outer scope (closure).</p><p><strong>Pure functions:</strong> Given the same inputs, always return the same output, with no side effects. These are the easiest to test and reason about.</p><p><strong>Higher-order functions:</strong> Functions that take other functions as parameters or return functions. Array methods like <code>map</code>, <code>filter</code>, <code>sort</code> are higher-order functions.</p>
Implementation Reference
// 1. Function declaration — hoisted (can call before definition)
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
// 2. Function expression — NOT hoisted
const multiply = function(a, b) {
return a * b;
};
// 3. Arrow function — concise, no own 'this'
const divide = (a, b) => a / b; // implicit return
const square = n => n * n; // single param, no parens needed
const greet = () => 'Hello!'; // no params
// 4. Arrow with body (multiple statements)
const clamp = (value, min, max) => {
if (value < min) return min;
if (value > max) return max;
return value;
};
// Default parameters
function createUser(name, role = 'viewer', active = true) {
return { name, role, active };
}
console.log(createUser('Alice')); // { name: 'Alice', role: 'viewer', active: true }
console.log(createUser('Bob', 'admin')); // { name: 'Bob', role: 'admin', active: true }
// Rest parameters — collect remaining args into an array
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15// ✅ Pure function: same input → same output, no side effects
const calculateTax = (price, rate) => price * rate;
const applyDiscount = (price, percent) => price * (1 - percent / 100);
const formatCurrency = (amount) => `$${amount.toFixed(2)}`;
// Composing pure functions
const getDisplayPrice = (price) => {
const withTax = calculateTax(price, 0.08);
const total = price + withTax;
return formatCurrency(total);
};
console.log(getDisplayPrice(100)); // '$108.00'
// ❌ Impure: modifies external state (side effect)
let taxTotal = 0;
function addTax_impure(price, rate) {
taxTotal += price * rate; // side effect: modifies external variable
return price * (1 + rate);
}
// Higher-order functions: functions as arguments
const numbers = [1, 2, 3, 4, 5];
const isEven = n => n % 2 === 0; // predicate function
const double = n => n * 2; // transform function
console.log(numbers.filter(isEven)); // [2, 4]
console.log(numbers.map(double)); // [2, 4, 6, 8, 10]
// Function returning a function (closure)
const createMultiplier = (factor) => {
return (number) => number * factor; // closes over 'factor'
};
const triple = createMultiplier(3);
const tenX = createMultiplier(10);
console.log(triple(5)); // 15
console.log(tenX(5)); // 50Common Pitfalls
- •Forgetting to <code>return</code> a value — a function without a <code>return</code> returns <code>undefined</code>, silently producing <code>NaN</code> or <code>'undefined'</code> bugs downstream.
- •Using arrow functions as object methods when you need <code>this</code> — arrow functions inherit <code>this</code> from their definition scope, not from the calling object. Use regular function syntax for methods.
- •Creating functions that do too many things — if you need an 'and' in a function's description ('validate AND save AND redirect'), split it into separate functions.
- •Using too many parameters — more than 3 parameters is a sign the function should accept an options object: <code>function createUser({ name, role, active })</code>.
Hands-on Practice
- ✓Write three versions of the same function (add two numbers): as a declaration, expression, and arrow function — call each and verify they work identically.
- ✓Build a set of pure utility functions: <code>formatDate</code>, <code>truncateString</code>, <code>capitalize</code>, <code>isValidEmail</code>. Test each with multiple inputs.
- ✓Create a closure-based counter with <code>increment</code>, <code>decrement</code>, and <code>reset</code> methods — verify the count is private.
- ✓Write a higher-order function <code>createValidator(rules)</code> that returns a validation function — each rule is a function that returns an error string or null.
Expert Pro Tips
Interview Preparation
Q: What's the difference between function declarations and arrow functions?
Master Answer:
Function declarations are hoisted — you can call them before their definition in the file. Arrow functions are not hoisted (they're assigned to variables, which are not initialised before the declaration line). The key difference is 'this' binding: regular functions have their own 'this' that depends on how they're called (the object that calls them). Arrow functions inherit 'this' from their surrounding lexical scope. This makes arrow functions ideal for callbacks and array methods, but inappropriate for object methods that need to reference 'this'.
Q: What is a closure? Give a practical example.
Master Answer:
A closure is a function that remembers and accesses variables from its outer scope even after the outer function has finished executing. Practical example: a counter factory — function createCounter() { let count = 0; return { increment: () => ++count, get: () => count }; }. The returned object's methods close over the count variable. Real-world uses: data privacy (variables not accessible from outside), memoisation (caching results), and callbacks that need to remember context (setTimeout, event handlers).
Q: What is a pure function and why does it matter?
Master Answer:
A pure function: (1) given the same inputs, always returns the same output, (2) produces no side effects (doesn't modify external state, make API calls, or log). Why it matters: predictability (easy to reason about), testability (no mocking needed), reusability (no hidden dependencies), and composability (can be combined safely). Example: Math.max, calculateTax. Impure: functions that modify global state, make API calls, or depend on random/time. Favour pure functions for business logic; confine side effects to specific layers.
Q: What are higher-order functions? Give examples.
Master Answer:
Higher-order functions either take functions as arguments or return functions. Built-in examples: Array.map, filter, reduce, sort, forEach — they all accept a callback function. Custom example: a middleware pattern (function withLogging(fn) { return (...args) => { console.log('calling'); return fn(...args); }; }). React hooks like useState return values and setters. Event listeners (addEventListener) take callbacks. They're fundamental to functional programming and enable abstraction, composition, and code reuse.
Simulated Scenarios
Extended Reading
MDN: Functions
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
JavaScript.info: Functions
https://javascript.info/function-basics
JavaScript.info: Closures
https://javascript.info/closure
© 2026 DevHub Engineering • All Proprietary Rights Reserved
Generated on March 7, 2026 • Ver: 4.0.2
Document Class: Master Education
Confidential Information • Licensed to User