+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 19 of 354

โžก ๏ธ Arrow Functions in TypeScript: Modern Function Syntax

Master arrow functions in TypeScript with concise syntax, lexical this binding, and type annotations for modern development ๐Ÿš€

๐ŸŒฑBeginner
20 min read

Prerequisites

  • Basic JavaScript function knowledge ๐Ÿ“
  • Understanding of TypeScript basics โšก
  • Familiarity with ES6 features ๐Ÿ’ป

What you'll learn

  • Write concise arrow functions with types ๐ŸŽฏ
  • Understand lexical this binding ๐Ÿ—๏ธ
  • Apply arrow functions in real scenarios ๐Ÿ”
  • Choose between arrow and regular functions โœจ

๐ŸŽฏ Introduction

Welcome to the sleek world of arrow functions in TypeScript! ๐ŸŽ‰ In this guide, weโ€™ll explore how these modern function expressions make your code cleaner, more concise, and more predictable.

Think of arrow functions as the sports car ๐ŸŽ๏ธ of the function world - streamlined, efficient, and designed for the modern era. Theyโ€™re not just about shorter syntax; they fundamentally change how this works and make functional programming patterns a breeze!

By the end of this tutorial, youโ€™ll be writing arrow functions like a TypeScript ninja, knowing exactly when to use them and when to stick with traditional functions! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Arrow Functions

๐Ÿค” What Are Arrow Functions?

Arrow functions are like shortcuts on your keyboard โŒจ๏ธ - they help you write the same thing with less effort! Introduced in ES6, they provide a concise way to write function expressions.

The syntax looks like this: () => {} - hence the name โ€œarrowโ€ functions! โžก๏ธ

Key characteristics:

  • โœจ Concise syntax for function expressions
  • ๐ŸŽฏ Lexical this binding (inherits from surrounding scope)
  • ๐Ÿ›ก๏ธ Implicit returns for single expressions
  • ๐Ÿš€ Perfect for callbacks and functional programming

๐Ÿ’ก Why Use Arrow Functions?

Hereโ€™s why arrow functions are game-changers:

  1. Cleaner Code ๐Ÿงฝ: Less boilerplate, more readable
  2. Predictable this ๐ŸŽฏ: No more that = this hacks!
  3. Functional Programming ๐ŸŒˆ: Perfect for map, filter, reduce
  4. Type Inference ๐Ÿค–: TypeScript often infers types automatically
  5. Modern Standards ๐Ÿš€: Industry standard in modern JavaScript/TypeScript

Real-world example: In React components โš›๏ธ, arrow functions eliminate the need to bind methods in constructors!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Arrow Function Syntax

Letโ€™s compare traditional functions with arrow functions:

// ๐ŸŽญ Traditional function
function add(a: number, b: number): number {
  return a + b;
}

// โžก๏ธ Arrow function - same thing!
const addArrow = (a: number, b: number): number => {
  return a + b;
};

// ๐ŸŽฏ Even shorter - implicit return!
const addShort = (a: number, b: number): number => a + b;

// โœจ Type inference - TypeScript knows the return type
const addInferred = (a: number, b: number) => a + b; // Return type inferred as number

// ๐ŸŽจ Single parameter - parentheses optional
const double = (n: number) => n * 2;
const doubleNoParens = n => n * 2; // Works in JS, but TS needs type!
const doubleTyped = (n: number) => n * 2; // Better for TypeScript

// ๐Ÿš€ No parameters
const getRandom = (): number => Math.random();
const sayHello = () => console.log("Hello! ๐Ÿ‘‹");

๐ŸŽฏ Common Patterns

Here are arrow function patterns youโ€™ll use daily:

// ๐Ÿ—๏ธ Pattern 1: Array methods
const numbers = [1, 2, 3, 4, 5];

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

// Filter
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]

// Reduce
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(`Sum: ${sum} ๐Ÿงฎ`);

// ๐ŸŽจ Pattern 2: Object methods with arrow functions
interface Calculator {
  value: number;
  add: (n: number) => void;
  multiply: (n: number) => void;
  getValue: () => number;
}

const calculator: Calculator = {
  value: 0,
  add: function(n: number) { // Regular function to access this
    this.value += n;
  },
  multiply: function(n: number) {
    this.value *= n;
  },
  getValue: function() {
    return this.value;
  }
};

// ๐Ÿ”„ Pattern 3: Callbacks and event handlers
const button = document.querySelector('button');
button?.addEventListener('click', (event) => {
  console.log('๐Ÿ”˜ Button clicked!', event);
});

// ๐ŸŽ† Pattern 4: Promises and async
const fetchData = async (): Promise<string> => {
  const response = await fetch('/api/data');
  return response.text();
};

// Chain with arrow functions
fetchData()
  .then(data => console.log('๐Ÿ“ฆ Data:', data))
  .catch(error => console.error('โŒ Error:', error));

๐Ÿงฉ Type Annotations in Arrow Functions

// ๐ŸŽฏ Full type annotation
const greet: (name: string) => string = (name) => {
  return `Hello, ${name}! ๐Ÿ‘‹`;
};

// ๐ŸŽจ Return type annotation
const calculate = (x: number, y: number): { sum: number; product: number } => ({
  sum: x + y,
  product: x * y
});

// ๐Ÿ›ก๏ธ Generic arrow functions
const identity = <T>(value: T): T => value;

const first = <T>(array: T[]): T | undefined => array[0];

// ๐ŸŽ† Complex types
type MathOperation = (a: number, b: number) => number;

const operations: Record<string, MathOperation> = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => b !== 0 ? a / b : 0
};

console.log(operations.add(5, 3)); // 8
console.log(operations.multiply(4, 7)); // 28

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Functional Shopping Cart

Letโ€™s build a shopping cart using arrow functions:

// ๐Ÿ›๏ธ Product type
interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  emoji: string;
}

// ๐Ÿ›’ Functional shopping cart using arrow functions
class ShoppingCart {
  private items: Product[] = [];
  
  // โžก๏ธ Arrow functions for all methods
  addItem = (product: Product): void => {
    this.items.push(product);
    console.log(`${product.emoji} ${product.name} added to cart!`);
  };
  
  removeItem = (productId: string): void => {
    this.items = this.items.filter(item => item.id !== productId);
    console.log(`๐Ÿ—‘๏ธ Item removed from cart`);
  };
  
  // ๐Ÿ’ฐ Calculate with arrow functions
  getTotal = (): number => 
    this.items.reduce((sum, item) => sum + item.price, 0);
  
  getTax = (rate: number = 0.08): number => 
    this.getTotal() * rate;
  
  getFinalPrice = (): number => 
    this.getTotal() + this.getTax();
  
  // ๐ŸŽฏ Filter and map operations
  getItemsByCategory = (category: string): Product[] =>
    this.items.filter(item => item.category === category);
  
  getItemNames = (): string[] =>
    this.items.map(item => `${item.emoji} ${item.name}`);
  
  // ๐ŸŽจ Summary with template literals
  getSummary = (): string => {
    const total = this.getTotal();
    const tax = this.getTax();
    const final = this.getFinalPrice();
    
    return `
๐Ÿ›’ Cart Summary:
  Items: ${this.items.length}
  Subtotal: $${total.toFixed(2)}
  Tax: $${tax.toFixed(2)}
  Total: $${final.toFixed(2)} ๐Ÿ’ณ
    `.trim();
  };
  
  // ๐Ÿš€ Async operations
  checkout = async (): Promise<{ success: boolean; orderId: string }> => {
    console.log('๐Ÿ“ฆ Processing order...');
    
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    const orderId = `ORD-${Date.now()}`;
    console.log(`โœ… Order ${orderId} placed successfully!`);
    
    return { success: true, orderId };
  };
}

// ๐ŸŽฎ Usage
const cart = new ShoppingCart();

cart.addItem({ 
  id: "1", 
  name: "TypeScript Handbook", 
  price: 39.99, 
  category: "books", 
  emoji: "๐Ÿ“˜" 
});

cart.addItem({ 
  id: "2", 
  name: "Mechanical Keyboard", 
  price: 149.99, 
  category: "electronics", 
  emoji: "โŒจ๏ธ" 
});

console.log(cart.getSummary());
console.log("๐Ÿท๏ธ Electronics:", cart.getItemsByCategory("electronics"));

// Async checkout
cart.checkout().then(result => {
  console.log("๐ŸŽ† Order complete:", result);
});

๐ŸŽจ Example 2: Event System with Arrow Functions

Letโ€™s create an event system showcasing this binding:

// ๐ŸŽ† Event emitter using arrow functions
type EventCallback<T = any> = (data: T) => void;

class EventEmitter {
  private events = new Map<string, EventCallback[]>();
  
  // โžก๏ธ Arrow function preserves 'this'
  on = <T>(event: string, callback: EventCallback<T>): void => {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event)!.push(callback);
    console.log(`๐ŸŽ‰ Subscribed to "${event}"`);
  };
  
  off = <T>(event: string, callback: EventCallback<T>): void => {
    const callbacks = this.events.get(event);
    if (callbacks) {
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
        console.log(`๐Ÿ”‡ Unsubscribed from "${event}"`);
      }
    }
  };
  
  emit = <T>(event: string, data: T): void => {
    const callbacks = this.events.get(event) || [];
    console.log(`๐Ÿ“ก Emitting "${event}" to ${callbacks.length} listeners`);
    
    // Arrow function in forEach preserves 'this'
    callbacks.forEach(callback => {
      callback(data);
    });
  };
  
  // ๐Ÿš€ Async emit
  emitAsync = async <T>(event: string, data: T): Promise<void> => {
    const callbacks = this.events.get(event) || [];
    
    await Promise.all(
      callbacks.map(callback => 
        Promise.resolve(callback(data))
      )
    );
  };
}

// ๐ŸŽฎ Game using event system
class Game {
  private events = new EventEmitter();
  private score = 0;
  private level = 1;
  
  constructor() {
    // Arrow functions automatically bind 'this'
    this.setupEventHandlers();
  }
  
  private setupEventHandlers = () => {
    // No need for .bind(this)!
    this.events.on('score', this.handleScore);
    this.events.on('powerup', this.handlePowerup);
    this.events.on('gameover', this.handleGameOver);
  };
  
  // โžก๏ธ All handlers use arrow functions
  private handleScore = (points: number) => {
    this.score += points;
    console.log(`๐ŸŽฏ Score: ${this.score}`);
    
    if (this.score >= this.level * 100) {
      this.levelUp();
    }
  };
  
  private handlePowerup = (type: string) => {
    console.log(`โšก Power-up collected: ${type}`);
    
    const bonuses: Record<string, () => void> = {
      'double': () => this.score *= 2,
      'shield': () => console.log('๐Ÿ›ก๏ธ Shield activated!'),
      'speed': () => console.log('๐Ÿš€ Speed boost!')
    };
    
    bonuses[type]?.();
  };
  
  private handleGameOver = () => {
    console.log(`๐ŸŽฎ Game Over! Final score: ${this.score}`);
  };
  
  private levelUp = () => {
    this.level++;
    console.log(`๐ŸŽ† Level ${this.level}!`);
  };
  
  // Public methods
  play = () => {
    console.log('๐ŸŽฎ Game started!');
    
    // Simulate game events
    setTimeout(() => this.events.emit('score', 10), 1000);
    setTimeout(() => this.events.emit('powerup', 'double'), 2000);
    setTimeout(() => this.events.emit('score', 50), 3000);
    setTimeout(() => this.events.emit('gameover', null), 5000);
  };
}

// ๐Ÿš€ Start the game
const game = new Game();
game.play();

๐ŸŽฎ Example 3: Functional Data Pipeline

// ๐Ÿ”— Data transformation pipeline using arrow functions
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  isActive: boolean;
  score: number;
}

const users: User[] = [
  { id: 1, name: "Alice", age: 28, email: "[email protected]", isActive: true, score: 850 },
  { id: 2, name: "Bob", age: 32, email: "[email protected]", isActive: false, score: 920 },
  { id: 3, name: "Charlie", age: 25, email: "[email protected]", isActive: true, score: 780 },
  { id: 4, name: "Diana", age: 29, email: "[email protected]", isActive: true, score: 950 }
];

// ๐ŸŽฏ Composable data operations
const pipeline = {
  // Filter operations
  filterActive: (users: User[]) => 
    users.filter(user => user.isActive),
  
  filterByAge: (minAge: number) => (users: User[]) =>
    users.filter(user => user.age >= minAge),
  
  filterHighScorers: (minScore: number) => (users: User[]) =>
    users.filter(user => user.score >= minScore),
  
  // Transform operations
  mapToNames: (users: User[]) =>
    users.map(user => user.name),
  
  mapToEmails: (users: User[]) =>
    users.map(user => user.email),
  
  mapToSummary: (users: User[]) =>
    users.map(user => ({
      name: user.name,
      status: user.isActive ? '๐Ÿ”ต Active' : '๐Ÿ”ด Inactive',
      level: user.score > 900 ? '๐Ÿ† Expert' : user.score > 800 ? '๐Ÿฅˆ Advanced' : '๐Ÿฅ‰ Intermediate'
    })),
  
  // Aggregate operations
  getAverageScore: (users: User[]) =>
    users.reduce((sum, user) => sum + user.score, 0) / users.length,
  
  getAverageAge: (users: User[]) =>
    users.reduce((sum, user) => sum + user.age, 0) / users.length,
  
  groupByStatus: (users: User[]) =>
    users.reduce((groups, user) => {
      const key = user.isActive ? 'active' : 'inactive';
      return {
        ...groups,
        [key]: [...(groups[key] || []), user]
      };
    }, {} as Record<string, User[]>)
};

// ๐Ÿš€ Chain operations
const activeHighScorers = pipeline.filterHighScorers(800)(
  pipeline.filterActive(users)
);

console.log('๐Ÿ† High scorers:', pipeline.mapToNames(activeHighScorers));
console.log('๐Ÿ“Š Average score:', pipeline.getAverageScore(users));
console.log('๐Ÿ‘ฅ User summaries:', pipeline.mapToSummary(users));

// ๐ŸŽจ Compose functions
const compose = <T>(...fns: Array<(arg: T) => T>) => (value: T): T =>
  fns.reduceRight((acc, fn) => fn(acc), value);

const getEliteUsers = compose(
  pipeline.mapToNames,
  pipeline.filterHighScorers(900),
  pipeline.filterActive
);

console.log('๐ŸŒŸ Elite users:', getEliteUsers(users));

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Lexical this Binding

The most powerful feature of arrow functions is lexical this binding:

// ๐ŸŽฏ Traditional function - 'this' changes based on caller
class Timer {
  seconds = 0;
  
  // โŒ Problem with regular functions
  startBad() {
    setInterval(function() {
      this.seconds++; // 'this' is undefined or window!
      console.log(this.seconds); // Error!
    }, 1000);
  }
  
  // ๐ŸŽจ Old solution - store 'this'
  startOldWay() {
    const that = this;
    setInterval(function() {
      that.seconds++;
      console.log(that.seconds);
    }, 1000);
  }
  
  // โœ… Arrow function solution - 'this' is preserved!
  start = () => {
    setInterval(() => {
      this.seconds++; // 'this' refers to Timer instance
      console.log(`โฑ๏ธ ${this.seconds} seconds`);
    }, 1000);
  };
}

// ๐Ÿš€ React-style component example
class Component {
  state = { count: 0 };
  
  // โžก๏ธ Arrow function auto-binds 'this'
  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
    console.log(`๐Ÿ”ข Count: ${this.state.count}`);
  };
  
  // Simulate setState
  setState = (newState: any) => {
    this.state = { ...this.state, ...newState };
  };
  
  render = () => {
    // In real React, this would return JSX
    return {
      onClick: this.handleClick // No .bind(this) needed!
    };
  };
}

๐Ÿ—๏ธ Arrow Functions in Classes

Modern TypeScript patterns with arrow functions:

// ๐ŸŽจ Modern class with arrow function properties
class DataService {
  private baseUrl = 'https://api.example.com';
  private cache = new Map<string, any>();
  
  // โžก๏ธ All methods as arrow functions
  get = async <T>(endpoint: string): Promise<T> => {
    const cacheKey = `${this.baseUrl}${endpoint}`;
    
    if (this.cache.has(cacheKey)) {
      console.log('๐Ÿ“ฆ Cache hit!');
      return this.cache.get(cacheKey);
    }
    
    console.log('๐ŸŒ Fetching...');
    const response = await fetch(cacheKey);
    const data = await response.json();
    
    this.cache.set(cacheKey, data);
    return data;
  };
  
  post = async <T, R>(endpoint: string, data: T): Promise<R> => {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    return response.json();
  };
  
  // ๐ŸŽฏ Higher-order functions
  withRetry = <T extends any[], R>(
    fn: (...args: T) => Promise<R>,
    retries = 3
  ) => async (...args: T): Promise<R> => {
    for (let i = 0; i < retries; i++) {
      try {
        return await fn(...args);
      } catch (error) {
        console.log(`โš ๏ธ Retry ${i + 1}/${retries}`);
        if (i === retries - 1) throw error;
      }
    }
    throw new Error('Unreachable');
  };
  
  // Usage
  getWithRetry = this.withRetry(this.get);
  postWithRetry = this.withRetry(this.post);
}

// ๐ŸŽฎ Functional programming patterns
const createCounter = (initial = 0) => {
  let count = initial;
  
  return {
    increment: () => ++count,
    decrement: () => --count,
    getValue: () => count,
    reset: () => count = initial
  };
};

const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.getValue()); // 12

๐ŸŽจ Type-Safe Function Composition

// ๐Ÿงฌ Advanced function composition with arrow functions
type Fn<T, R> = (value: T) => R;

// Compose two functions
const compose2 = <A, B, C>(
  f: Fn<B, C>,
  g: Fn<A, B>
): Fn<A, C> => (x: A) => f(g(x));

// Pipe functions (left to right)
const pipe = <T>(...fns: Array<(arg: any) => any>) => 
  (value: T) => fns.reduce((acc, fn) => fn(acc), value);

// ๐ŸŽฏ Example transformations
const addTax = (price: number) => price * 1.2;
const formatPrice = (price: number) => `$${price.toFixed(2)}`;
const addEmoji = (price: string) => `๐Ÿ’ฐ ${price}`;

// Compose functions
const processPrice = compose2(
  compose2(addEmoji, formatPrice),
  addTax
);

console.log(processPrice(100)); // "๐Ÿ’ฐ $120.00"

// ๐Ÿš€ Curry functions with arrow syntax
const curry = <T extends any[], R>(
  fn: (...args: T) => R
) => {
  const curried = (...args: any[]): any => {
    if (args.length >= fn.length) {
      return fn(...args as T);
    }
    return (...nextArgs: any[]) => curried(...args, ...nextArgs);
  };
  return curried;
};

// Example curry usage
const multiply = (a: number, b: number, c: number) => a * b * c;
const curriedMultiply = curry(multiply);

console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Arrow Functions and this in Object Literals

// โŒ Wrong - arrow functions don't have their own 'this'
const person = {
  name: "Alice",
  greet: () => {
    console.log(`Hello, I'm ${this.name}`); // 'this' is not the object!
  }
};
person.greet(); // "Hello, I'm undefined" ๐Ÿ˜ญ

// โœ… Correct - use regular function for object methods
const personGood = {
  name: "Alice",
  greet: function() {
    console.log(`Hello, I'm ${this.name}! ๐Ÿ‘‹`);
  },
  // Or method shorthand
  greetShort() {
    console.log(`Hi, I'm ${this.name}! ๐Ÿ˜Š`);
  }
};

๐Ÿคฏ Pitfall 2: Arrow Functions Canโ€™t Be Constructors

// โŒ Wrong - arrow functions can't be used with 'new'
const Person = (name: string) => {
  this.name = name; // Error!
};
// const person = new Person("Bob"); // TypeError!

// โœ… Correct - use class or regular function
class PersonClass {
  constructor(public name: string) {}
}

// Or regular function constructor
function PersonFunction(this: any, name: string) {
  this.name = name;
}

const person1 = new PersonClass("Bob");
const person2 = new PersonFunction("Charlie");

๐Ÿ˜ต Pitfall 3: Implicit Returns with Objects

// โŒ Wrong - thinks it's a code block!
const makeUser = (name: string) => { name: name }; // undefined!

// JavaScript thinks the {} is a code block, not an object

// โœ… Correct - wrap in parentheses
const makeUserGood = (name: string) => ({ name: name });
// Or even shorter
const makeUserBest = (name: string) => ({ name });

console.log(makeUserGood("Alice")); // { name: "Alice" } โœ…

// ๐ŸŽจ More complex example
const createResponse = (status: number, data: any) => ({
  status,
  data,
  timestamp: new Date(),
  emoji: status === 200 ? "โœ…" : "โŒ"
});

๐Ÿค” Pitfall 4: Arguments Object

// โŒ Arrow functions don't have 'arguments'
const sum = () => {
  // return Array.from(arguments).reduce((a, b) => a + b); // Error!
};

// โœ… Use rest parameters instead
const sumGood = (...numbers: number[]): number => {
  return numbers.reduce((a, b) => a + b, 0);
};

console.log(sumGood(1, 2, 3, 4)); // 10 โœ…

// ๐ŸŽฏ Even better with type safety
const sumTyped = <T extends number[]>(...numbers: T): number => {
  return numbers.reduce((sum, n) => sum + n, 0);
};

๐Ÿ˜ฌ Pitfall 5: Method Extraction

// โš ๏ธ Careful when extracting methods
class Counter {
  count = 0;
  
  // Regular method - 'this' depends on caller
  incrementBad() {
    this.count++;
  }
  
  // Arrow function - 'this' always bound
  incrementGood = () => {
    this.count++;
  };
}

const counter = new Counter();

// โŒ Extracting regular method loses 'this'
const badInc = counter.incrementBad;
// badInc(); // Error! 'this' is undefined

// โœ… Arrow function works when extracted
const goodInc = counter.incrementGood;
goodInc(); // Works! 'this' is still the counter
console.log(counter.count); // 1 โœ…

// ๐ŸŽฏ Common in event handlers
button.addEventListener('click', counter.incrementGood); // Works!
// button.addEventListener('click', counter.incrementBad); // Breaks!

๐Ÿ› ๏ธ Best Practices

1. ๐ŸŽฏ Use Arrow Functions for Callbacks

// โœ… Perfect for array methods
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);

// โœ… Great for promises
fetchData()
  .then(data => processData(data))
  .then(result => console.log(result))
  .catch(error => console.error(error));

2. ๐Ÿ“ Keep Arrow Functions Concise

// โœ… Good - concise and clear
const isAdult = (age: number) => age >= 18;
const double = (n: number) => n * 2;
const greet = (name: string) => `Hello, ${name}!`;

// โŒ Avoid - too complex for one line
const complexCalc = (a: number, b: number) => { const x = a * 2; const y = b * 3; return x + y + Math.sqrt(x * y); };

// โœ… Better - use block body for complex logic
const complexCalcBetter = (a: number, b: number) => {
  const x = a * 2;
  const y = b * 3;
  return x + y + Math.sqrt(x * y);
};

3. ๐Ÿ›ก๏ธ Use Regular Functions for Object Methods

// โœ… Good - regular functions for methods that use 'this'
const calculator = {
  value: 0,
  add(n: number) {
    this.value += n;
    return this;
  },
  getValue() {
    return this.value;
  }
};

// โœ… Arrow functions for callbacks within methods
const dataProcessor = {
  items: [1, 2, 3],
  process() {
    return this.items.map(item => item * 2); // Arrow function here is fine
  }
};

4. ๐ŸŽจ Type Arrow Functions Properly

// โœ… Good - clear type annotations
type MathFn = (a: number, b: number) => number;

const operations: Record<string, MathFn> = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b
};

// โœ… Generic arrow functions
const identity = <T>(value: T): T => value;
const getProperty = <T, K extends keyof T>(obj: T, key: K) => obj[key];

5. โœจ Choose the Right Function Type

// โœ… Use arrow functions for:
// - Callbacks and event handlers
// - Array methods (map, filter, reduce)
// - Promise chains
// - When you need lexical 'this'

class Component {
  name = "MyComponent";
  
  // Arrow function for event handler
  handleClick = () => {
    console.log(`Clicked ${this.name}`);
  };
}

// โœ… Use regular functions for:
// - Object methods
// - Constructors
// - Generator functions
// - When you need dynamic 'this'

const api = {
  baseUrl: "https://api.example.com",
  
  // Regular function to access this.baseUrl
  async fetchData(endpoint: string) {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    return response.json();
  }
};

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Task Queue System

Create a task queue system using arrow functions that showcases their strengths:

๐Ÿ“‹ Requirements:

  • โœ… Async task queue with arrow functions
  • ๐Ÿท๏ธ Different task types and priorities
  • ๐Ÿ‘ค Progress tracking and callbacks
  • ๐Ÿ“… Retry logic for failed tasks
  • ๐ŸŽจ Event-driven architecture

๐Ÿš€ Bonus Points:

  • Add task cancellation
  • Implement concurrent execution limits
  • Create a task scheduler

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Task Queue System with Arrow Functions
type TaskStatus = 'pending' | 'running' | 'completed' | 'failed';
type TaskPriority = 'low' | 'normal' | 'high';

interface Task<T = any> {
  id: string;
  name: string;
  priority: TaskPriority;
  status: TaskStatus;
  execute: () => Promise<T>;
  onComplete?: (result: T) => void;
  onError?: (error: Error) => void;
  retries: number;
  maxRetries: number;
}

class TaskQueue {
  private queue: Task[] = [];
  private running = false;
  private concurrency = 2;
  private activeCount = 0;
  
  // โžก๏ธ Event handlers using arrow functions
  private listeners = {
    taskComplete: [] as Array<(task: Task) => void>,
    taskFailed: [] as Array<(task: Task, error: Error) => void>,
    queueEmpty: [] as Array<() => void>
  };
  
  // ๐ŸŽฏ Add task to queue
  addTask = <T>(
    name: string,
    execute: () => Promise<T>,
    options: {
      priority?: TaskPriority;
      onComplete?: (result: T) => void;
      onError?: (error: Error) => void;
      maxRetries?: number;
    } = {}
  ): string => {
    const task: Task<T> = {
      id: `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      name,
      execute,
      priority: options.priority || 'normal',
      status: 'pending',
      onComplete: options.onComplete,
      onError: options.onError,
      retries: 0,
      maxRetries: options.maxRetries || 3
    };
    
    this.queue.push(task);
    this.sortQueue();
    console.log(`๐Ÿ“ฅ Added task: ${name} (${task.priority} priority)`);
    
    if (!this.running) {
      this.start();
    }
    
    return task.id;
  };
  
  // ๐Ÿš€ Start processing queue
  start = async (): Promise<void> => {
    this.running = true;
    console.log('๐Ÿ Queue started');
    
    while (this.queue.length > 0 || this.activeCount > 0) {
      while (this.activeCount < this.concurrency && this.queue.length > 0) {
        const task = this.queue.shift()!;
        this.processTask(task);
      }
      
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    
    this.running = false;
    console.log('๐Ÿ† Queue completed');
    this.emit('queueEmpty');
  };
  
  // ๐ŸŽฏ Process individual task
  private processTask = async (task: Task): Promise<void> => {
    this.activeCount++;
    task.status = 'running';
    console.log(`๐Ÿƒ Running: ${task.name}`);
    
    try {
      const result = await task.execute();
      task.status = 'completed';
      console.log(`โœ… Completed: ${task.name}`);
      
      task.onComplete?.(result);
      this.emit('taskComplete', task);
    } catch (error) {
      task.retries++;
      console.error(`โŒ Failed: ${task.name} (attempt ${task.retries}/${task.maxRetries})`);
      
      if (task.retries < task.maxRetries) {
        task.status = 'pending';
        this.queue.push(task);
        this.sortQueue();
      } else {
        task.status = 'failed';
        task.onError?.(error as Error);
        this.emit('taskFailed', task, error as Error);
      }
    } finally {
      this.activeCount--;
    }
  };
  
  // ๐Ÿท๏ธ Sort queue by priority
  private sortQueue = (): void => {
    const priorityOrder = { high: 0, normal: 1, low: 2 };
    this.queue.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
  };
  
  // ๐Ÿ“ก Event system
  on = (event: keyof typeof this.listeners, callback: any): void => {
    this.listeners[event].push(callback);
  };
  
  private emit = (event: keyof typeof this.listeners, ...args: any[]): void => {
    this.listeners[event].forEach(callback => callback(...args));
  };
  
  // ๐Ÿ“Š Get queue statistics
  getStats = () => ({
    pending: this.queue.filter(t => t.status === 'pending').length,
    running: this.activeCount,
    completed: this.queue.filter(t => t.status === 'completed').length,
    failed: this.queue.filter(t => t.status === 'failed').length
  });
  
  // ๐Ÿ”„ Cancel task
  cancelTask = (taskId: string): boolean => {
    const index = this.queue.findIndex(t => t.id === taskId && t.status === 'pending');
    if (index > -1) {
      this.queue.splice(index, 1);
      console.log(`๐Ÿšซ Cancelled task: ${taskId}`);
      return true;
    }
    return false;
  };
}

// ๐ŸŽฎ Example usage
const queue = new TaskQueue();

// Add event listeners
queue.on('taskComplete', (task) => {
  console.log(`๐ŸŽ† Task completed: ${task.name}`);
});

queue.on('taskFailed', (task, error) => {
  console.log(`๐Ÿšจ Task failed permanently: ${task.name}`);
});

queue.on('queueEmpty', () => {
  console.log('๐ŸŽ‰ All tasks processed!');
  console.log('๐Ÿ“Š Final stats:', queue.getStats());
});

// Add various tasks
queue.addTask(
  'Fetch user data',
  async () => {
    await new Promise(resolve => setTimeout(resolve, 1000));
    return { id: 1, name: 'John Doe' };
  },
  {
    priority: 'high',
    onComplete: (user) => console.log('๐Ÿ‘ค User fetched:', user)
  }
);

queue.addTask(
  'Process image',
  async () => {
    await new Promise(resolve => setTimeout(resolve, 2000));
    if (Math.random() > 0.5) throw new Error('Processing failed');
    return 'image-processed.jpg';
  },
  {
    priority: 'normal',
    maxRetries: 2,
    onComplete: (result) => console.log('๐ŸŽจ Image processed:', result),
    onError: (error) => console.log('๐Ÿ—‘๏ธ Image processing failed:', error.message)
  }
);

queue.addTask(
  'Send notification',
  async () => {
    await new Promise(resolve => setTimeout(resolve, 500));
    return 'Notification sent';
  },
  {
    priority: 'low',
    onComplete: (result) => console.log('๐Ÿ””', result)
  }
);

// Add high priority task
setTimeout(() => {
  queue.addTask(
    'Urgent task',
    async () => {
      await new Promise(resolve => setTimeout(resolve, 800));
      return 'Urgent task completed';
    },
    {
      priority: 'high',
      onComplete: (result) => console.log('๐Ÿšจ', result)
    }
  );
}, 1500);

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered arrow functions in TypeScript! Hereโ€™s what you can now do:

  • โœ… Write concise functions with clean, modern syntax ๐Ÿ’ช
  • โœ… Leverage lexical this for predictable behavior ๐Ÿ›ก๏ธ
  • โœ… Use implicit returns for single expressions ๐ŸŽฏ
  • โœ… Choose wisely between arrow and regular functions ๐Ÿ›
  • โœ… Apply functional patterns with confidence! ๐Ÿš€

Remember: Arrow functions arenโ€™t always better - theyโ€™re different tools for different jobs. Use them wisely! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered arrow functions in TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Complete the task queue exercise above
  2. ๐Ÿ—๏ธ Refactor existing code to use arrow functions appropriately
  3. ๐Ÿ“š Move on to our next tutorial: Union and Intersection Types
  4. ๐ŸŒŸ Practice using arrow functions in React or Node.js projects!

Remember: The best way to learn is by doing. Start using arrow functions in your code today! ๐Ÿš€

Happy coding! ๐ŸŽ‰๐Ÿš€โœจ