Skip to main content

Behavioral Design Patterns in JavaScript

Created: May 8, 2026 Larry Qu 8 min read

Behavioral patterns focus on object collaboration and responsibility distribution. This article covers Observer, Strategy, Command, State, Template Method, Iterator, and Chain of Responsibility patterns.

Introduction

Behavioral patterns provide:

  • Object communication
  • Responsibility distribution
  • Flexible behavior
  • Event handling
  • Algorithm encapsulation

Understanding these patterns helps you:

  • Manage object interactions
  • Implement flexible algorithms
  • Handle events effectively
  • Distribute responsibilities
  • Improve code organization

Observer Pattern

Basic Observer

// ✅ Good: Observer pattern
class Subject {
  constructor() {
    this.observers = [];
  }

  attach(observer) {
    this.observers.push(observer);
  }

  detach(observer) {
    this.observers = this.observers.filter(o => o !== observer);
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    throw new Error('Must implement update');
  }
}

class ConcreteObserver extends Observer {
  constructor(name) {
    super();
    this.name = name;
  }

  update(data) {
    console.log(`${this.name} received: ${data}`);
  }
}

// Usage
const subject = new Subject();
const observer1 = new ConcreteObserver('Observer 1');
const observer2 = new ConcreteObserver('Observer 2');

subject.attach(observer1);
subject.attach(observer2);

subject.notify('Hello!');
// Observer 1 received: Hello!
// Observer 2 received: Hello!

Event Emitter

// ✅ Good: Event emitter pattern
class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  off(event, listener) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
  }

  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(listener => listener(data));
    }
  }

  once(event, listener) {
    const onceWrapper = (data) => {
      listener(data);
      this.off(event, onceWrapper);
    };
    this.on(event, onceWrapper);
  }
}

// Usage
const emitter = new EventEmitter();

emitter.on('user:login', (user) => {
  console.log(`${user} logged in`);
});

emitter.on('user:login', (user) => {
  console.log(`Welcome ${user}!`);
});

emitter.emit('user:login', 'John');
// John logged in
// Welcome John!

Strategy Pattern

Basic Strategy

// ✅ Good: Strategy pattern
class PaymentStrategy {
  pay(amount) {
    throw new Error('Must implement pay');
  }
}

class CreditCardPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paying $${amount} with credit card`);
    return true;
  }
}

class PayPalPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paying $${amount} with PayPal`);
    return true;
  }
}

class BitcoinPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paying ${amount} BTC with Bitcoin`);
    return true;
  }
}

class ShoppingCart {
  constructor(paymentStrategy) {
    this.paymentStrategy = paymentStrategy;
    this.total = 0;
  }

  addItem(price) {
    this.total += price;
  }

  checkout() {
    return this.paymentStrategy.pay(this.total);
  }
}

// Usage
const cart = new ShoppingCart(new CreditCardPayment());
cart.addItem(50);
cart.addItem(30);
cart.checkout(); // Paying $80 with credit card

const cart2 = new ShoppingCart(new PayPalPayment());
cart2.addItem(100);
cart2.checkout(); // Paying $100 with PayPal

Sorting Strategy

// ✅ Good: Sorting strategy
class Sorter {
  constructor(strategy) {
    this.strategy = strategy;
  }

  sort(array) {
    return this.strategy.sort(array);
  }
}

class BubbleSort {
  sort(array) {
    console.log('Sorting with bubble sort');
    return [...array].sort((a, b) => a - b);
  }
}

class QuickSort {
  sort(array) {
    console.log('Sorting with quick sort');
    return [...array].sort((a, b) => a - b);
  }
}

// Usage
const data = [5, 2, 8, 1, 9];

const sorter1 = new Sorter(new BubbleSort());
console.log(sorter1.sort(data)); // [1, 2, 5, 8, 9]

const sorter2 = new Sorter(new QuickSort());
console.log(sorter2.sort(data)); // [1, 2, 5, 8, 9]

Command Pattern

// ✅ Good: Command pattern
class Command {
  execute() {
    throw new Error('Must implement execute');
  }

  undo() {
    throw new Error('Must implement undo');
  }
}

class Light {
  constructor() {
    this.isOn = false;
  }

  turnOn() {
    this.isOn = true;
    console.log('Light is on');
  }

  turnOff() {
    this.isOn = false;
    console.log('Light is off');
  }
}

class TurnOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

  execute() {
    this.light.turnOn();
  }

  undo() {
    this.light.turnOff();
  }
}

class TurnOffCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

  execute() {
    this.light.turnOff();
  }

  undo() {
    this.light.turnOn();
  }
}

class RemoteControl {
  constructor() {
    this.commands = [];
    this.history = [];
  }

  execute(command) {
    command.execute();
    this.history.push(command);
  }

  undo() {
    const command = this.history.pop();
    if (command) {
      command.undo();
    }
  }
}

// Usage
const light = new Light();
const remote = new RemoteControl();

remote.execute(new TurnOnCommand(light)); // Light is on
remote.execute(new TurnOffCommand(light)); // Light is off
remote.undo(); // Light is on
remote.undo(); // Light is off

State Pattern

// ✅ Good: State pattern
class State {
  handle(context) {
    throw new Error('Must implement handle');
  }
}

class SolidState extends State {
  handle(context) {
    console.log('Melting solid to liquid');
    context.setState(new LiquidState());
  }
}

class LiquidState extends State {
  handle(context) {
    console.log('Evaporating liquid to gas');
    context.setState(new GasState());
  }
}

class GasState extends State {
  handle(context) {
    console.log('Condensing gas to liquid');
    context.setState(new LiquidState());
  }
}

class Matter {
  constructor() {
    this.state = new SolidState();
  }

  setState(state) {
    this.state = state;
  }

  changeState() {
    this.state.handle(this);
  }
}

// Usage
const matter = new Matter();
matter.changeState(); // Melting solid to liquid
matter.changeState(); // Evaporating liquid to gas
matter.changeState(); // Condensing gas to liquid

Template Method Pattern

// ✅ Good: Template method pattern
class DataProcessor {
  process(data) {
    const validated = this.validate(data);
    const transformed = this.transform(validated);
    const result = this.save(transformed);
    return result;
  }

  validate(data) {
    throw new Error('Must implement validate');
  }

  transform(data) {
    throw new Error('Must implement transform');
  }

  save(data) {
    throw new Error('Must implement save');
  }
}

class CSVProcessor extends DataProcessor {
  validate(data) {
    console.log('Validating CSV data');
    return data;
  }

  transform(data) {
    console.log('Transforming CSV to objects');
    return data.split('\n').map(line => line.split(','));
  }

  save(data) {
    console.log('Saving to database');
    return data;
  }
}

class JSONProcessor extends DataProcessor {
  validate(data) {
    console.log('Validating JSON data');
    return JSON.parse(data);
  }

  transform(data) {
    console.log('Transforming JSON');
    return data;
  }

  save(data) {
    console.log('Saving to database');
    return data;
  }
}

// Usage
const csvProcessor = new CSVProcessor();
csvProcessor.process('name,age\nJohn,30');

const jsonProcessor = new JSONProcessor();
jsonProcessor.process('{"name":"John","age":30}');

Iterator Pattern

// ✅ Good: Iterator pattern
class Iterator {
  hasNext() {
    throw new Error('Must implement hasNext');
  }

  next() {
    throw new Error('Must implement next');
  }
}

class ArrayIterator extends Iterator {
  constructor(array) {
    super();
    this.array = array;
    this.index = 0;
  }

  hasNext() {
    return this.index < this.array.length;
  }

  next() {
    return this.array[this.index++];
  }
}

class Collection {
  constructor(items) {
    this.items = items;
  }

  createIterator() {
    return new ArrayIterator(this.items);
  }
}

// Usage
const collection = new Collection([1, 2, 3, 4, 5]);
const iterator = collection.createIterator();

while (iterator.hasNext()) {
  console.log(iterator.next());
}
// 1, 2, 3, 4, 5

Chain of Responsibility Pattern

// ✅ Good: Chain of responsibility
class Handler {
  constructor(successor = null) {
    this.successor = successor;
  }

  handle(request) {
    throw new Error('Must implement handle');
  }
}

class AuthHandler extends Handler {
  handle(request) {
    if (!request.user) {
      console.log('Auth failed');
      return false;
    }
    console.log('Auth passed');
    return this.successor ? this.successor.handle(request) : true;
  }
}

class ValidationHandler extends Handler {
  handle(request) {
    if (!request.data) {
      console.log('Validation failed');
      return false;
    }
    console.log('Validation passed');
    return this.successor ? this.successor.handle(request) : true;
  }
}

class LoggingHandler extends Handler {
  handle(request) {
    console.log('Logging request');
    return this.successor ? this.successor.handle(request) : true;
  }
}

// Usage
const chain = new AuthHandler(
  new ValidationHandler(
    new LoggingHandler()
  )
);

const request = { user: 'John', data: { name: 'John' } };
chain.handle(request);
// Auth passed
// Validation passed
// Logging request

Practical Examples

Form Validation

// ✅ Good: Form validation with chain of responsibility
class ValidationRule {
  constructor(successor = null) {
    this.successor = successor;
  }

  validate(data) {
    throw new Error('Must implement validate');
  }
}

class RequiredRule extends ValidationRule {
  validate(data) {
    if (!data.value) {
      return { valid: false, error: 'Field is required' };
    }
    return this.successor ? this.successor.validate(data) : { valid: true };
  }
}

class EmailRule extends ValidationRule {
  validate(data) {
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.value)) {
      return { valid: false, error: 'Invalid email format' };
    }
    return this.successor ? this.successor.validate(data) : { valid: true };
  }
}

class LengthRule extends ValidationRule {
  constructor(minLength, successor = null) {
    super(successor);
    this.minLength = minLength;
  }

  validate(data) {
    if (data.value.length < this.minLength) {
      return { valid: false, error: `Minimum length is ${this.minLength}` };
    }
    return this.successor ? this.successor.validate(data) : { valid: true };
  }
}

// Usage
const emailValidator = new RequiredRule(
  new EmailRule(
    new LengthRule(5)
  )
);

console.log(emailValidator.validate({ value: '' }));
// { valid: false, error: 'Field is required' }

console.log(emailValidator.validate({ value: 'invalid' }));
// { valid: false, error: 'Invalid email format' }

console.log(emailValidator.validate({ value: '[email protected]' }));
// { valid: true }

Notification System

// ✅ Good: Notification system with observer
class NotificationCenter {
  constructor() {
    this.subscribers = {};
  }

  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }
    this.subscribers[event].push(callback);
  }

  unsubscribe(event, callback) {
    if (this.subscribers[event]) {
      this.subscribers[event] = this.subscribers[event].filter(
        cb => cb !== callback
      );
    }
  }

  publish(event, data) {
    if (this.subscribers[event]) {
      this.subscribers[event].forEach(callback => callback(data));
    }
  }
}

// Usage
const notificationCenter = new NotificationCenter();

notificationCenter.subscribe('user:login', (user) => {
  console.log(`${user} logged in`);
});

notificationCenter.subscribe('user:login', (user) => {
  console.log(`Send welcome email to ${user}`);
});

notificationCenter.publish('user:login', 'John');
// John logged in
// Send welcome email to John

Best Practices

  1. Use Observer for event handling:
    // ✅ Good
    emitter.on('event', callback);
    
    // ❌ Bad
    if (condition) callback();
    ```javascript
    
  2. Use Strategy for algorithm selection:
    // ✅ Good
    const processor = new Processor(strategy);
    
    // ❌ Bad
    if (type === 'a') { } else if (type === 'b') { }
    ```javascript
    
  3. Use State for state management:
    // ✅ Good
    context.setState(newState);
    
    // ❌ Bad
    if (state === 'a') { } else if (state === 'b') { }
    ```javascript
    

Common Mistakes

  1. Overusing Observer:
    // ❌ Bad - too many observers
    emitter.on('event', cb1);
    emitter.on('event', cb2);
    emitter.on('event', cb3);
    
    // ✅ Good - group related observers
    emitter.on('event', aggregatedCallback);
    ```javascript
    
  2. Command without undo:
    // ❌ Bad
    class Command {
      execute() { }
    }
    
    // ✅ Good
    class Command {
      execute() { }
      undo() { }
    }
    

Summary

Behavioral patterns manage object interactions. Key takeaways:

  • Observer: Event handling
  • Strategy: Algorithm selection
  • Command: Action encapsulation
  • State: State management
  • Template Method: Algorithm structure
  • Iterator: Sequential access
  • Chain of Responsibility: Request handling
  • Improves flexibility
  • Reduces coupling
  • Enhances maintainability

Next Steps

Resources

Comments

Share this article

Scan to read on mobile