Home Module 13 Arrow Functions

1. Introduction

Arrow functions were introduced in ES6 (2015) and are now the dominant way to write short functions and callbacks in JavaScript. They have two key differences from regular functions: a concise syntax and no own this binding. You have used them throughout earlier modules — this lesson goes deeper on every syntax form and the crucial this distinction.

2. Theory

2.1 Syntax forms

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

// Arrow function — all equivalent
const add1 = (a, b) => { return a + b; }; // block body, explicit return
const add2 = (a, b) => a + b;             // concise body, implicit return
const add3 = (a, b) => (a + b);           // same — parens are optional

// Single parameter — can omit the outer parentheses
const double = n => n * 2;
const double2 = (n) => n * 2; // also fine

// No parameters — must keep empty parentheses
const greet = () => 'Hello!';

// Returning an object literal — must wrap in parens
//   (otherwise {} is parsed as a block, not an object)
const makeUser = (name, age) => ({ name, age });
console.log(makeUser('Alice', 28)); // { name: 'Alice', age: 28 }

2.2 Implicit vs explicit return

// Concise body — single expression, result is returned automatically
const square = n => n * n;
const isEven = n => n % 2 === 0;
const greet  = name => `Hello, ${name}!`;

// Block body — must use return keyword
const clamp = (n, min, max) => {
  if (n < min) return min;
  if (n > max) return max;
  return n;
};

// Common mistake: implicit return of undefined
const broken = n => {
  n * 2; // no return! returns undefined
};
console.log(broken(5)); // undefined

2.3 Arrow functions in array methods

const numbers = [1, 2, 3, 4, 5, 6];

// Verbose — unnecessary for simple callbacks
const doubled = numbers.map(function(n) { return n * 2; });

// Arrow — much cleaner
const doubled2 = numbers.map(n => n * 2);
const evens    = numbers.filter(n => n % 2 === 0);
const sum      = numbers.reduce((acc, n) => acc + n, 0);

// Chained pipeline
const result = numbers
  .filter(n => n > 2)
  .map(n => n * 10)
  .reduce((acc, n) => acc + n, 0);
console.log(result); // 3+4+5+6 = 18 → *10 = 30+40+50+60 = 180

2.4 this in arrow functions — the key difference

Arrow functions have NO own this. They capture this from the surrounding lexical scope — wherever the arrow function is defined, not called.

const timer = {
  seconds: 0,

  // Regular function — loses 'this' in callbacks
  startBroken() {
    setInterval(function() {
      this.seconds++; // 'this' is undefined here!
    }, 1000);
  },

  // Arrow function — inherits 'this' from startGood's context
  startGood() {
    setInterval(() => {
      this.seconds++; // 'this' = timer object — correct!
    }, 1000);
  }
};

// Another common context: event listeners
const btn = document.querySelector('#btn');
const widget = {
  count: 0,
  init() {
    btn.addEventListener('click', () => {
      this.count++; // 'this' = widget — inherits from init()
      console.log(this.count);
    });
  }
};
widget.init();

2.5 When NOT to use arrow functions

// 1. Object methods — 'this' will be wrong
const obj = {
  name: 'Widget',
  getName: () => this.name // 'this' is NOT obj — undefined or window
};
// Fix: use method shorthand
const obj2 = { name: 'Widget', getName() { return this.name; } };

// 2. Constructors — arrow functions cannot be called with 'new'
const Person = (name) => { this.name = name; }; // Error if called with new
// Fix: use class or regular function

// 3. addEventListener when you need 'this' = the element
btn.addEventListener('click', () => {
  this.style.color = 'red'; // 'this' is NOT the button
});
// Fix: use regular function or e.currentTarget
btn.addEventListener('click', function() {
  this.style.color = 'red'; // 'this' = button
});
btn.addEventListener('click', e => {
  e.currentTarget.style.color = 'red'; // always works
});

2.6 Arrow functions are always anonymous expressions

// Arrow functions cannot be declared — they must be assigned
// They are NOT hoisted like function declarations

// This works (function declaration — hoisted)
greet('Alice'); // can call before declaration
function greet(name) { return `Hi ${name}`; }

// This fails (arrow function expression — NOT hoisted)
greetArrow('Alice'); // ReferenceError!
const greetArrow = name => `Hi ${name}`;

// Named for debugging — you can still name the variable
const calculateTax = price => price * 0.2; // "calculateTax" appears in stack traces

2.7 Immediately invoked arrow functions (IIFE)

// IIFE — runs immediately, useful for one-off scoped code
const result = (() => {
  const x = 10;
  const y = 20;
  return x + y;
})();
console.log(result); // 30
// x and y are not accessible outside

3. Real World Example

// Data pipeline using arrow functions throughout
const employees = [
  { name: 'Alice', dept: 'Engineering', salary: 85000, active: true  },
  { name: 'Bob',   dept: 'Design',      salary: 72000, active: true  },
  { name: 'Carol', dept: 'Engineering', salary: 92000, active: false },
  { name: 'Dave',  dept: 'Design',      salary: 68000, active: true  },
  { name: 'Eve',   dept: 'Engineering', salary: 78000, active: true  }
];

const getEngineeringReport = employees => {
  const active  = employees.filter(e => e.active && e.dept === 'Engineering');
  const total   = active.reduce((sum, e) => sum + e.salary, 0);
  const average = total / active.length;
  const names   = active.map(e => e.name).join(', ');

  return {
    count:   active.length,
    names,
    total,
    average: Math.round(average)
  };
};

const report = getEngineeringReport(employees);
console.log(`Engineers: ${report.names}`); // Alice, Eve
console.log(`Avg salary: $${report.average}`); // $81500

4. Code Example

<input id="number-in" type="number" placeholder="Enter number">
<div id="results"></div>

<script>
  // A collection of transformations using arrow functions
  const transformations = [
    { label: 'Double',    fn: n => n * 2 },
    { label: 'Square',    fn: n => n ** 2 },
    { label: 'Negate',    fn: n => -n },
    { label: 'Is even?',  fn: n => n % 2 === 0 },
    { label: 'Factorial', fn: n => {
        if (n < 0) return 'N/A';
        if (n === 0 || n === 1) return 1;
        let result = 1;
        for (let i = 2; i <= n; i++) result *= i;
        return result;
      }
    }
  ];

  const numberInput = document.querySelector('#number-in');
  const results     = document.querySelector('#results');

  numberInput.addEventListener('input', () => {
    const n = Number(numberInput.value);
    if (isNaN(n)) { results.textContent = 'Enter a valid number'; return; }

    results.innerHTML = '';
    transformations.forEach(({ label, fn }) => {
      const p = document.createElement('p');
      p.textContent = `${label}: ${fn(n)}`;
      results.appendChild(p);
    });
  });
</script>

5. Code Breakdown

Arrow functions in the transformations array

Each object stores a label and an arrow function. Most use concise body (single expression, implicit return). The factorial uses a block body with explicit return because it needs multiple statements.

Destructuring in forEach callback

forEach(({ label, fn }) => { ... }) destructures each transformation object directly in the parameter — extracting label and fn without an intermediate variable.

Arrow in event listener — this not needed

The input listener uses an arrow function because this is not needed — numberInput is available via closure. This is the preferred pattern for event listeners that don't need element context.

6. Common Mistakes

Mistake 1 — Forgetting to wrap object literal in parentheses

// Bug — {} is parsed as a block, not an object
const makeUser = name => { name: name }; // returns undefined!

// Fix — wrap in parentheses
const makeUser = name => ({ name: name });
const makeUser2 = name => ({ name }); // with shorthand

Mistake 2 — Using arrow function as an object method

const counter = {
  n: 0,
  inc: () => { this.n++; } // this is NOT counter
};
counter.inc();
console.log(counter.n); // 0 — broken

// Fix: method shorthand
const counter2 = { n: 0, inc() { this.n++; } };

Mistake 3 — Adding unnecessary parentheses for single params

// All valid — just pick a consistent style
const fn1 = x => x * 2;   // no parens — preferred by many
const fn2 = (x) => x * 2; // with parens — also fine
// Be consistent within a codebase

Mistake 4 — Missing return in block body

// Bug — block body requires explicit return
const double = n => {
  n * 2; // silently returns undefined
};

// Fix
const double2 = n => {
  return n * 2;
};
// Or use concise body:
const double3 = n => n * 2;

7. Best Practices

  1. Use concise body for single-expression functions — no braces, no return.
  2. Use block body when the function needs multiple statements or conditional logic.
  3. Use arrow functions for callbacks (map/filter/setTimeout/addEventListener) — they're shorter and usually don't need their own this.
  4. Use method shorthand for object methods, not arrow functions.
  5. Wrap object literals in parens when returning from a concise body arrow function.
  6. Be consistent — don't mix unnecessary styles within the same codebase.

8. Practice Exercise

  1. Convert these regular functions to arrow functions (concise where possible): function add(a, b) { return a + b; }, function isPositive(n) { return n > 0; }, function greet(name) { return 'Hi ' + name; }.
  2. Write a pipeline(...fns) function that takes any number of functions and returns a new function — when called with a value, it passes it through each function in order. Use arrow functions throughout.
  3. Create an object calculator with add, sub, mul methods. Write a version with arrow functions and observe the broken this. Then fix it with method shorthand.

9. Assignment

Build a "Function Playground" page.

  1. Create a library of at least 8 pure arrow functions: math operations, string transforms, boolean checks.
  2. Build a UI where the user selects a function from a dropdown and enters arguments. Clicking "Run" displays the return value.
  3. Add a "Pipeline" section: user picks 3 functions in order, enters a starting value, and sees it flow through each step with intermediate results displayed.
  4. All function definitions must use arrow syntax (concise where possible, block body only when needed).

Deliverable: One HTML file.

10. Interview Questions

  1. What is the difference between a regular function and an arrow function?
    Arrow functions have a shorter syntax and no own this, arguments, or prototype. They inherit this from the surrounding lexical scope. Regular functions get their own this determined at call time.
  2. What is an implicit return?
    When an arrow function has a concise body (no braces), the single expression is automatically returned — no return keyword needed. Adding braces requires an explicit return statement.
  3. Can you use new with an arrow function?
    No. Arrow functions cannot be constructors and will throw a TypeError if called with new. They have no prototype property.
  4. When should you NOT use arrow functions?
    As object methods (this won't refer to the object), as constructors (can't use new), and as event listeners where you need this to be the element. Also, they are not hoisted, so they can't be called before their definition.

11. Additional Resources

  • MDN — Arrow function expressions
  • javascript.info — Arrow functions revisited
  • MDN — this keyword