From Zero to AI

Lesson 4.3: Arrow Functions

Duration: 50 minutes

Learning Objectives

By the end of this lesson, you will be able to:

  • Write arrow functions with proper TypeScript syntax
  • Use concise body and block body syntax
  • Understand the difference between arrow functions and regular functions
  • Type arrow functions correctly in various contexts
  • Use arrow functions with array methods effectively

Introduction to Arrow Functions

Arrow functions are a modern, concise way to write functions in JavaScript and TypeScript:

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

// Arrow function
const add = (a: number, b: number): number => {
  return a + b;
};

// Concise arrow function (implicit return)
const add = (a: number, b: number): number => a + b;

Arrow Function Syntax

Basic Syntax

const functionName = (parameters): ReturnType => {
  // function body
  return value;
};

With Type Annotations

// Full syntax with types
const greet = (name: string): string => {
  return `Hello, ${name}!`;
};

// Concise syntax (single expression)
const greet = (name: string): string => `Hello, ${name}!`;

console.log(greet("Alice")); // "Hello, Alice!"

No Parameters

Use empty parentheses when there are no parameters:

const getTimestamp = (): number => Date.now();

const sayHello = (): void => {
  console.log('Hello!');
};

Single Parameter

Parentheses are optional with a single parameter (but required when adding types):

// Without type - parentheses optional
const double = x => x * 2;

// With type - parentheses required
const double = (x: number): number => x * 2;

Concise vs Block Body

Concise Body (Implicit Return)

When the function is a single expression, skip the braces and return:

// These are equivalent
const square = (x: number): number => x * x;

const square = (x: number): number => {
  return x * x;
};

// More examples
const isEven = (n: number): boolean => n % 2 === 0;
const toUpper = (s: string): string => s.toUpperCase();
const getLength = (arr: unknown[]): number => arr.length;

Block Body (Explicit Return)

Use braces when you need multiple statements:

const processNumber = (n: number): string => {
  const doubled = n * 2;
  const squared = n * n;
  return `Doubled: ${doubled}, Squared: ${squared}`;
};

const validateAge = (age: number): boolean => {
  if (age < 0) {
    console.log('Age cannot be negative');
    return false;
  }
  if (age > 150) {
    console.log('Age seems unrealistic');
    return false;
  }
  return true;
};

Returning Objects

When returning an object with concise syntax, wrap it in parentheses:

// Wrong - TypeScript thinks {} is a block body
const createUser = (name: string) => { name: name }; // Error!

// Correct - wrap object in parentheses
const createUser = (name: string) => ({ name: name });

// Or use the shorthand
const createUser = (name: string) => ({ name });

// Full example
interface Point {
  x: number;
  y: number;
}

const createPoint = (x: number, y: number): Point => ({ x, y });

console.log(createPoint(10, 20)); // { x: 10, y: 20 }

Arrow Functions vs Regular Functions

The this Keyword

The main difference is how this behaves:

// Regular function - 'this' depends on how it's called
const obj1 = {
  name: 'Alice',
  greet: function () {
    console.log(`Hello, ${this.name}`);
  },
};

obj1.greet(); // "Hello, Alice" - 'this' is obj1

// Arrow function - 'this' is captured from surrounding scope
const obj2 = {
  name: 'Bob',
  greet: () => {
    console.log(`Hello, ${this.name}`); // 'this' is NOT obj2!
  },
};

obj2.greet(); // "Hello, undefined" - 'this' is the outer scope

When to Use Each

Use arrow functions for:

  • Callbacks and array methods
  • Short, single-expression functions
  • When you need to preserve this from outer scope
// Array methods - arrow functions are perfect
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
const evens = numbers.filter((n) => n % 2 === 0);

Use regular functions for:

  • Methods on objects (when you need this)
  • Constructors (arrow functions cannot be constructors)
  • When you need arguments object
// Object method - regular function for 'this'
const counter = {
  count: 0,
  increment() {
    this.count++;
  },
  decrement() {
    this.count--;
  },
};

Typing Arrow Functions

Inline Type Annotations

const add = (a: number, b: number): number => a + b;

Type Alias for Function Types

// Define the function type
type MathOperation = (a: number, b: number) => number;

// Use it
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;

Interface with Call Signature

interface StringTransformer {
  (input: string): string;
}

const toUpperCase: StringTransformer = (s) => s.toUpperCase();
const toLowerCase: StringTransformer = (s) => s.toLowerCase();
const reverse: StringTransformer = (s) => s.split('').reverse().join('');

Function Type in Object

interface Calculator {
  add: (a: number, b: number) => number;
  subtract: (a: number, b: number) => number;
}

const calculator: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
};

Arrow Functions with Array Methods

Arrow functions shine with array methods:

map - Transform Each Element

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

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

// Convert to strings
const strings = numbers.map((n) => `Number: ${n}`);
console.log(strings); // ["Number: 1", "Number: 2", ...]

// Extract property from objects
interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];

const names = users.map((user) => user.name);
console.log(names); // ["Alice", "Bob"]

filter - Select Elements

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Get even numbers
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// Get numbers greater than 5
const big = numbers.filter((n) => n > 5);
console.log(big); // [6, 7, 8, 9, 10]

// Filter objects
interface Product {
  name: string;
  price: number;
  inStock: boolean;
}

const products: Product[] = [
  { name: 'Laptop', price: 999, inStock: true },
  { name: 'Phone', price: 699, inStock: false },
  { name: 'Tablet', price: 499, inStock: true },
];

const available = products.filter((p) => p.inStock);
const affordable = products.filter((p) => p.price < 700);

reduce - Accumulate Values

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

// Sum all numbers
const sum = numbers.reduce((total, n) => total + n, 0);
console.log(sum); // 15

// Find maximum
const max = numbers.reduce((max, n) => (n > max ? n : max), numbers[0]);
console.log(max); // 5

// Group by property
interface Item {
  category: string;
  name: string;
}

const items: Item[] = [
  { category: 'fruit', name: 'apple' },
  { category: 'fruit', name: 'banana' },
  { category: 'vegetable', name: 'carrot' },
];

const grouped = items.reduce(
  (acc, item) => {
    if (!acc[item.category]) {
      acc[item.category] = [];
    }
    acc[item.category].push(item.name);
    return acc;
  },
  {} as Record<string, string[]>
);

console.log(grouped);
// { fruit: ["apple", "banana"], vegetable: ["carrot"] }

find and findIndex

const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'user' },
];

// Find first admin
const admin = users.find((u) => u.role === 'admin');
console.log(admin); // { id: 1, name: "Alice", role: "admin" }

// Find index of Bob
const bobIndex = users.findIndex((u) => u.name === 'Bob');
console.log(bobIndex); // 1

some and every

const numbers = [2, 4, 6, 8, 10];

// Check if any number is greater than 5
const hasLarge = numbers.some((n) => n > 5);
console.log(hasLarge); // true

// Check if all numbers are even
const allEven = numbers.every((n) => n % 2 === 0);
console.log(allEven); // true

Practical Examples

Example 1: Data Pipeline

interface Order {
  id: number;
  customer: string;
  items: { name: string; price: number; quantity: number }[];
  status: 'pending' | 'shipped' | 'delivered';
}

const orders: Order[] = [
  {
    id: 1,
    customer: 'Alice',
    items: [
      { name: 'Book', price: 20, quantity: 2 },
      { name: 'Pen', price: 5, quantity: 10 },
    ],
    status: 'delivered',
  },
  {
    id: 2,
    customer: 'Bob',
    items: [{ name: 'Laptop', price: 999, quantity: 1 }],
    status: 'shipped',
  },
];

// Calculate order total
const getOrderTotal = (order: Order): number =>
  order.items.reduce((sum, item) => sum + item.price * item.quantity, 0);

// Get delivered orders with totals
const deliveredOrderTotals = orders
  .filter((order) => order.status === 'delivered')
  .map((order) => ({
    customer: order.customer,
    total: getOrderTotal(order),
  }));

console.log(deliveredOrderTotals);
// [{ customer: "Alice", total: 90 }]

Example 2: Event Handlers

interface ButtonConfig {
  label: string;
  onClick: () => void;
}

const createButton = (config: ButtonConfig) => {
  console.log(`Button "${config.label}" created`);
  // In a real app, this would create a DOM element
};

createButton({
  label: 'Submit',
  onClick: () => console.log('Form submitted!'),
});

createButton({
  label: 'Cancel',
  onClick: () => console.log('Operation cancelled'),
});

Example 3: Sorting Functions

interface Person {
  name: string;
  age: number;
}

const people: Person[] = [
  { name: 'Charlie', age: 30 },
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 35 },
];

// Sort by name
const byName = [...people].sort((a, b) => a.name.localeCompare(b.name));
console.log(byName.map((p) => p.name)); // ["Alice", "Bob", "Charlie"]

// Sort by age
const byAge = [...people].sort((a, b) => a.age - b.age);
console.log(byAge.map((p) => p.name)); // ["Alice", "Charlie", "Bob"]

// Sort by age descending
const byAgeDesc = [...people].sort((a, b) => b.age - a.age);
console.log(byAgeDesc.map((p) => p.name)); // ["Bob", "Charlie", "Alice"]

Exercises

Exercise 1: Array Transformations

Use arrow functions to:

  1. Convert an array of names to uppercase
  2. Filter names that start with "A"
  3. Create objects with name and length properties
const names = ['Alice', 'Bob', 'Anna', 'Charlie', 'Amy'];
Solution
const names = ['Alice', 'Bob', 'Anna', 'Charlie', 'Amy'];

// 1. Convert to uppercase
const upperNames = names.map((name) => name.toUpperCase());
console.log(upperNames); // ["ALICE", "BOB", "ANNA", "CHARLIE", "AMY"]

// 2. Filter names starting with "A"
const aNames = names.filter((name) => name.startsWith('A'));
console.log(aNames); // ["Alice", "Anna", "Amy"]

// 3. Create objects with name and length
const nameObjects = names.map((name) => ({
  name,
  length: name.length,
}));
console.log(nameObjects);
// [{ name: "Alice", length: 5 }, { name: "Bob", length: 3 }, ...]

Exercise 2: Calculator Factory

Create a function that returns calculator functions:

type Operation = (a: number, b: number) => number;

// Create a function createOperation that takes a symbol (+, -, *, /)
// and returns the corresponding operation function
Solution
type Operation = (a: number, b: number) => number;

const createOperation = (symbol: '+' | '-' | '*' | '/'): Operation => {
  switch (symbol) {
    case '+':
      return (a, b) => a + b;
    case '-':
      return (a, b) => a - b;
    case '*':
      return (a, b) => a * b;
    case '/':
      return (a, b) => a / b;
  }
};

const add = createOperation('+');
const subtract = createOperation('-');
const multiply = createOperation('*');
const divide = createOperation('/');

console.log(add(10, 5)); // 15
console.log(subtract(10, 5)); // 5
console.log(multiply(10, 5)); // 50
console.log(divide(10, 5)); // 2

Exercise 3: Data Processing

Process this product data using arrow functions:

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}

const products: Product[] = [
  { id: 1, name: 'Laptop', price: 999, category: 'Electronics' },
  { id: 2, name: 'Book', price: 20, category: 'Books' },
  { id: 3, name: 'Phone', price: 699, category: 'Electronics' },
  { id: 4, name: 'Notebook', price: 5, category: 'Books' },
];

// 1. Get all electronics
// 2. Get total price of all products
// 3. Get product names sorted by price (ascending)
Solution
interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}

const products: Product[] = [
  { id: 1, name: 'Laptop', price: 999, category: 'Electronics' },
  { id: 2, name: 'Book', price: 20, category: 'Books' },
  { id: 3, name: 'Phone', price: 699, category: 'Electronics' },
  { id: 4, name: 'Notebook', price: 5, category: 'Books' },
];

// 1. Get all electronics
const electronics = products.filter((p) => p.category === 'Electronics');
console.log(electronics);
// [{ id: 1, name: "Laptop", ... }, { id: 3, name: "Phone", ... }]

// 2. Get total price of all products
const totalPrice = products.reduce((sum, p) => sum + p.price, 0);
console.log(totalPrice); // 1723

// 3. Get product names sorted by price (ascending)
const sortedNames = [...products].sort((a, b) => a.price - b.price).map((p) => p.name);
console.log(sortedNames); // ["Notebook", "Book", "Phone", "Laptop"]

Exercise 4: Typed Callback

Create a function that accepts a callback with proper typing:

// Create processArray function that:
// - Takes an array of numbers
// - Takes a callback that transforms each number
// - Returns the transformed array
Solution
type Transformer = (value: number) => number;

const processArray = (numbers: number[], transformer: Transformer): number[] => {
  return numbers.map(transformer);
};

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

const doubled = processArray(numbers, (n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

const squared = processArray(numbers, (n) => n * n);
console.log(squared); // [1, 4, 9, 16, 25]

const incremented = processArray(numbers, (n) => n + 1);
console.log(incremented); // [2, 3, 4, 5, 6]

Key Takeaways

  1. Arrow syntax: (params) => expression or (params) => { statements }
  2. Concise return: Single expressions return automatically without return
  3. Object returns need parentheses: () => ({ key: value })
  4. Arrow functions capture this: They do not have their own this binding
  5. Perfect for callbacks: Array methods like map, filter, reduce
  6. Type aliases for reuse: Define function types once, use many times

Resources

Resource Type Description
MDN: Arrow Functions Documentation Complete guide to arrow functions
TypeScript Handbook: Functions Documentation TypeScript function types
MDN: Array Methods Documentation All array methods reference

Next Lesson

Now that you have mastered functions, let us learn how to organize code with classes.

Continue to Lesson 4.4: Classes in TypeScript