2.12 Module 2 · Variables, Data Types & Functions

Advanced Functions

Level up your function writing with reusability, composition, and clean design principles — the habits that separate maintainable code from tangled spaghetti.

1

Definition

Advanced functions in JavaScript refer to techniques and practices that enhance function usability — including reusability, function composition, and clean coding principles — allowing developers to write modular, maintainable, and scalable code.

The core idea is simple: write each function to do one thing well, then combine those small focused functions to build complex behaviour. This approach makes code easier to read, test, and reuse across projects.

2

Simple Way

Advanced functions are about three habits that make every codebase better:

Reuse Write logic once — call it as many times as needed, anywhere
Compose Pass the output of one function as the input of another — chain small steps
Single task Each function does exactly one thing — easy to name, test, and debug
3

Code Examples

Example 1 — Reusability
function calculateArea(length, width) {
  return length * width;
}

// Use the same function with different data — no code duplication
console.log(calculateArea(5, 4));   // 20
console.log(calculateArea(10, 2));  // 20
console.log(calculateArea(7, 3));   // 21
Output
Click ▶ Run to execute
Explanation
  • One function definition — unlimited uses with different inputs
  • If the logic needs to change (e.g. add rounding), you update one place and all callers benefit
  • This is the DRY principle: Don't Repeat Yourself
Example 2 — Function Composition
function double(x) {
  return x * 2;
}

function square(x) {
  return x * x;
}

// Compose: output of double() becomes input of square()
let result = square(double(3));
// double(3) → 6
// square(6) → 36

console.log(result); // 36

// You can also store intermediate results
let doubled  = double(4);  // 8
let squaredD = square(doubled); // 64
console.log(squaredD);
Output
Click ▶ Run to execute
Explanation
  • Each function is small and focused — double only doubles, square only squares
  • Composing them builds complex behaviour without rewriting logic
  • JavaScript evaluates from the inside out — the innermost call runs first
Example 3 — Single Responsibility (Clean Design)
// Each function does exactly one thing
function isEven(num) {
  return num % 2 === 0;
}

function isPositive(num) {
  return num > 0;
}

function isEvenAndPositive(num) {
  return isEven(num) && isPositive(num);
}

console.log(isEven(4));                // true
console.log(isEven(7));                // false
console.log(isEvenAndPositive(6));     // true
console.log(isEvenAndPositive(-4));    // false — negative
Output
Click ▶ Run to execute
Explanation
  • Each function is independently testable — you can verify isEven without caring about positivity
  • Combining them is clean and readable — the composed function reads like plain English
  • Single-responsibility functions are far easier to debug when something goes wrong
Example 4 — Bad Code vs Good Code
// ✗ Bad — repeated magic number, no reuse
let total1 = 5 * 10;
let total2 = 8 * 10;
let total3 = 3 * 10;
console.log(total1, total2, total3);

// ✓ Good — single function, zero repetition
function calculateTotal(price, quantity) {
  return price * quantity;
}

console.log(calculateTotal(5, 10));
console.log(calculateTotal(8, 10));
console.log(calculateTotal(3, 10));
Output
Click ▶ Run — both produce the same result
Explanation
  • The bad version repeats the same pattern — if the formula changes you must update every line
  • The good version centralises the logic — one change fixes everything
  • This makes testing trivial and bugs easier to isolate
Example 5 — Functions as Arguments (Higher-Order)
// A function that accepts another function as input
function applyTwice(fn, value) {
  return fn(fn(value));
}

const addFive  = x => x + 5;
const triple   = x => x * 3;

console.log(applyTwice(addFive, 10));  // addFive(addFive(10)) = 20
console.log(applyTwice(triple, 2));    // triple(triple(2))    = 18
Output
Click ▶ Run to execute
Explanation
  • In JavaScript, functions are first-class values — they can be passed as arguments
  • A function that receives or returns another function is called a higher-order function
  • This pattern powers array.map(), array.filter(), and many modern APIs
4

Real-Life Example

Think of a factory assembly line — each machine does one job, and the output of each becomes the input of the next:

Reuse
The same cutting machine shapes every piece — not a new machine per piece
Compose
Cut → Paint → Assemble — each station feeds the next
Single task
The paint station only paints — it doesn't also cut or assemble
Higher-order
A manager who tells different machines what process to run — flexible control
5

HTML + JavaScript Example

Click ▶ Preview, enter a price, and watch two small composed functions — calculateTax() and applyDiscount() — each do their single job and combine to produce the final result.

Live in Browser — Composed Price Calculator
<!DOCTYPE html>
<html>
<head><title>Advanced Functions</title></head>
<body style="font-family:sans-serif;padding:24px;background:#f8f9fa">

  <h2>Discount Calculator</h2>
  <input id="priceInput" type="number" placeholder="Enter price (₹)"
    style="padding:8px 12px;border:1px solid #ccc;border-radius:6px;
           font-size:1rem;width:200px">
  <button onclick="calculate()"
    style="margin-left:8px;padding:8px 18px;background:#f7df1e;border:none;
           border-radius:6px;cursor:pointer;font-weight:700;font-size:1rem">
    Apply 10% Discount
  </button>
  <div id="output" style="margin-top:16px;line-height:2.4;font-size:0.95rem"></div>

  <script>
    // Each function does ONE thing
    function applyDiscount(price) {
      return price * 0.9; // 10% off
    }

    function calculateTax(price) {
      return price * 1.18; // 18% GST
    }

    function formatPrice(amount) {
      return "₹" + amount.toFixed(2);
    }

    function calculate() {
      let raw = Number(document.getElementById("priceInput").value);

      if (!raw || raw <= 0) {
        document.getElementById("output").innerHTML =
          "<b style='color:red'>Please enter a valid price.</b>";
        return;
      }

      // Compose: discount first, then tax on discounted price
      let discounted = applyDiscount(raw);
      let withTax    = calculateTax(discounted);

      document.getElementById("output").innerHTML =
        "<b>Original price:</b> " + formatPrice(raw) + "<br>" +
        "<b>After 10% discount:</b> " + formatPrice(discounted) + "<br>" +
        "<b>After 18% GST:</b> " + formatPrice(withTax);
    }
  </script>

</body>
</html>
Live Preview
6

Tasks (Practice)

Easy Task 1 — Square function
Starter code
function square(n) {
  // return n squared
}

console.log(square(5));   // 25
console.log(square(12));  // 144
  • Complete the function so it returns n * n
  • Call it with three different numbers and verify the outputs
  • Rewrite it as a one-line arrow function: const square = n => n * n
Medium Task 2 — Composed tax calculator
Starter code
function calculateTax(price) {
  return price * 0.10; // 10% tax
}

function addTax(price) {
  return price + calculateTax(price);
}

console.log(addTax(500));
  • Run the code — confirm the output is 550
  • Create a third function formatBill(price) that returns a string like "Total: ₹550.00"
  • Compose all three: formatBill(addTax(500))
7

MCQs

Q1
What is function composition?
  • A Writing multiple unrelated functions
  • B Passing the output of one function as the input of another
  • C Deleting functions after use
  • D Looping through functions
💡 B is correct. Function composition means chaining functions so that the output of one becomes the input of the next — e.g. square(double(3)) where double(3) produces the input for square.
Q2
Which is the better function design practice?
  • A One large function that does everything
  • B Small, focused, reusable functions
  • C No functions — write everything inline
  • D Random naming so it's harder to copy
💡 B is correct. Small, single-purpose functions are easier to read, test, and reuse. A large function that does everything becomes a maintenance nightmare — hard to debug and impossible to reuse parts of.
Q3
What is the output?
function double(x) {
  return x * 2;
}
console.log(double(5));
Output
Run it to verify
  • A 5
  • B 10
  • C 25
  • D Error
💡 B is correct. double(5) returns 5 * 2 = 10. The parameter x receives the argument 5, and the function returns the doubled value.
8

Pro Tips & Extra Knowledge

  • 01

    Name functions by what they do, not what they are — a clear name eliminates the need for a comment:

    Naming that communicates intent
    // ✗ Vague names — what do these do?
    function doStuff(x)   { return x * 1.18; }
    function fn2(a, b)    { return a + b; }
    
    // ✓ Clear names — self-documenting
    function applyGST(price)       { return price * 1.18; }
    function calculateTotal(a, b)  { return a + b; }
    
    console.log(applyGST(500));
    console.log(calculateTotal(300, 200));
    Output
    Click ▶ Run to execute
  • 02

    Prefer return over console.log inside functions — returning a value makes the function reusable in expressions; printing locks it to console output only:

    return makes functions composable
    // ✗ Printing inside — cannot reuse the result
    function badDouble(x) { console.log(x * 2); }
    
    // ✓ Returning — result can be used anywhere
    const goodDouble = x => x * 2;
    
    let result = goodDouble(5) + goodDouble(10); // composable!
    console.log(result); // 30
    Output
    Click ▶ Run to execute
  • 03

    Avoid side effects — a pure function always returns the same output for the same input and changes nothing outside itself:

    Pure vs impure functions
    let total = 0;
    
    // ✗ Impure — modifies external state
    function addToTotal(n) { total += n; }
    
    // ✓ Pure — no side effects, predictable
    function add(a, b) { return a + b; }
    
    console.log(add(3, 4)); // always 7
    console.log(add(3, 4)); // still 7 — no surprises
    Output
    Click ▶ Run to execute
  • 04

    Use Array.map() and Array.filter() — these are the most common higher-order functions and apply a function to every element of an array:

    map() and filter() — higher-order in action
    const prices  = [100, 250, 80, 400, 60];
    
    // map() — apply a function to every item
    const discounted = prices.map(p => p * 0.9);
    console.log(discounted);
    
    // filter() — keep only items that pass a test
    const expensive = prices.filter(p => p > 100);
    console.log(expensive);
    Output
    Click ▶ Run to execute
Mini Challenge

Two small functions are composed together — trace each step from the inside out before running.

What does this print?
function add(x) {
  return x + 2;
}

function multiply(x) {
  return x * 3;
}

let result = multiply(add(2));

console.log(result);
Output
Think first, then run!
Step-by-Step Explanation
// JavaScript evaluates composed calls from the INSIDE OUT

// Step 1: Innermost call — add(2)
//         x = 2 → return 2 + 2 = 4
//         add(2) resolves to 4

// Step 2: Outer call — multiply(4)
//         x = 4 → return 4 * 3 = 12
//         multiply(4) resolves to 12

// Step 3: result = 12

// Output → 12

// Key takeaway:
//   multiply(add(2))
//   = multiply(4)     ← add(2) runs first, returns 4
//   = 12              ← multiply receives 4

// This is function composition — small, focused functions
// chained together to build more complex behaviour.
// Neither add() nor multiply() needed to know about the other.