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
thisfrom 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
argumentsobject
// 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:
- Convert an array of names to uppercase
- Filter names that start with "A"
- 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
- Arrow syntax:
(params) => expressionor(params) => { statements } - Concise return: Single expressions return automatically without
return - Object returns need parentheses:
() => ({ key: value }) - Arrow functions capture
this: They do not have their ownthisbinding - Perfect for callbacks: Array methods like
map,filter,reduce - 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.