Design Patterns

By the end of this lesson you'll be able to recognise five classic design patterns — Singleton, Factory, Strategy, Observer, and RAII — and write each one idiomatically in modern C++ using smart pointers, lambdas, and std::function instead of leak-prone raw pointers.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Design patterns are like standard fittings in plumbing . A plumber doesn't invent a new joint for every house — they reach for a known fitting (a T-junction, a valve, a trap) that solves a recurring problem in a way the next plumber will instantly recognise. A Factory is the valve that decides what flows out; a Strategy is the swappable nozzle; an Observer is the alarm that triggers when pressure changes; RAII is the automatic shut-off that closes the supply the moment you walk away. Learning the patterns means you stop reinventing fittings and start speaking a shared language with every C++ developer who reads your code.

1. Singleton — Exactly One, Reachable Anywhere

The Singleton guarantees a class has exactly one instance and gives global access to it. In C++11 and later the cleanest version is Meyer's Singleton : a static local variable inside a function. The compiler guarantees it is created once, on first use, and that creation is thread-safe. You make the constructor private and = delete the copy operations so nobody can clone it. Read this worked example, run it, and watch the constructor fire only once.

Use sparingly: a Singleton is global state in disguise. It makes code harder to test and creates hidden coupling. Reach for it only for truly single resources like a logger — otherwise prefer passing the object in as a parameter.

2. Factory — Create Without Naming the Type

The Factory pattern decouples creating an object from using it. Callers ask for something by name and get back a base-class pointer — here unique_ptr Shape — without ever mentioning the concrete class. Returning a unique_ptr is the key modern detail: the object owns itself and frees automatically, so there's no delete to forget. Read the worked example first.

Your turn. The factory below is almost complete — fill in the two blanks marked ___ so each branch returns the right animal on the heap, using the // 👉 hints.

3. Strategy — Swap the Algorithm at Runtime

The Strategy pattern wraps an algorithm behind a common interface so you can change it on the fly. Classic C++ used an abstract base class with one virtual method; modern C++ usually skips that and stores a std::function instead — then any callable with the right signature (a free function, a lambda, even a captured object) is a valid strategy. Read the worked example, then write your own strategies.

Now you try. A Discount is a strategy that takes a price and returns a new price. Fill in the two blanks with lambdas using the hints:

4. Observer — Tell Everyone Who Signed Up

The Observer pattern lets objects subscribe to changes in another object without the two being tightly coupled. The subject (here a PriceFeed ) keeps a list of subscribers and notifies each one when something changes. Storing subscribers as std::function means each observer is just a lambda — no observer base class required.

Pro Tip: for thread-safe observers, guard the listener list with a std::mutex , and if observers can outlive or die before the subject, store std::weak_ptr so you never call into a destroyed object.

5. RAII — The Pattern That Powers the Rest

RAII (Resource Acquisition Is Initialisation) is the most important pattern in C++, and it's the one beginners overlook because it has no Gang-of-Four name. The idea: tie a resource's lifetime to an object . Acquire it in the constructor, release it in the destructor, and the language guarantees the destructor runs when the object leaves scope — even if an exception is thrown. Smart pointers are RAII for memory; the same pattern handles files, locks, and sockets.

No blanks this time — just a brief and an outline. Combine the Factory pattern with a polymorphic interface to build it from scratch, then run it and check your output against the example in the comments.

Practice quiz

What does the Singleton pattern guarantee?

  • A class can never be instantiated
  • Every object is automatically copied
  • A class has exactly one instance, reachable from anywhere
  • A class always returns a new object on each call

Answer: A class has exactly one instance, reachable from anywhere. Singleton ensures exactly one instance with global access — useful for a logger or config, but use it sparingly.

How does Meyer's Singleton create its single, thread-safe instance in modern C++?

  • A static local variable inside a function (instance())
  • A global variable at file scope
  • A new call in the constructor
  • A shared_ptr passed everywhere

Answer: A static local variable inside a function (instance()). A static local inside instance() is created once on first use, and C++11 guarantees that initialisation is thread-safe.

Why does a modern Factory return unique_ptr<Shape> instead of a raw pointer?

  • Raw pointers cannot point to derived classes
  • unique_ptr is faster to construct
  • It is required by the Shape base class
  • unique_ptr owns the object and frees it automatically, so there is no delete to forget (no leaks)

Answer: unique_ptr owns the object and frees it automatically, so there is no delete to forget (no leaks). Returning unique_ptr makes ownership explicit and cleanup automatic, so a forgotten delete can never leak.

In modern C++, the Strategy pattern is usually implemented by storing what?

  • A raw function pointer array
  • A std::function that holds any matching callable
  • A global flag
  • A vector of integers

Answer: A std::function that holds any matching callable. Strategy stores a std::function, so any matching callable — a free function, lambda, or functor — is a valid strategy.

What does the Observer pattern let you do?

  • Notify all subscribers when a subject changes, without the subject knowing who they are
  • Guarantee one instance of a class
  • Swap an algorithm at runtime
  • Free memory automatically

Answer: Notify all subscribers when a subject changes, without the subject knowing who they are. Observer keeps a list of subscribers (often std::function) and notifies each one on a change, keeping them decoupled.

What is the core idea of RAII?

  • Allocate all memory at program start
  • Never use destructors
  • Tie a resource's lifetime to an object: acquire in the constructor, release in the destructor
  • Always call close() manually

Answer: Tie a resource's lifetime to an object: acquire in the constructor, release in the destructor. RAII acquires a resource in the constructor and releases it in the destructor, so cleanup is automatic and exception-safe.

Why must a polymorphic base class like Shape have a virtual destructor?

  • To make the class abstract
  • Deleting a derived object through a base pointer without it is undefined behaviour and leaks the derived part
  • To allow copying
  • Virtual destructors are never needed

Answer: Deleting a derived object through a base pointer without it is undefined behaviour and leaks the derived part. Without virtual ~Shape(), deleting a derived object via Shape* is undefined behaviour and the derived part leaks.

How do you stop a Singleton from being duplicated?

  • Make the constructor public
  • Mark the class final
  • Use a global variable
  • = delete the copy constructor and copy assignment operator

Answer: = delete the copy constructor and copy assignment operator. Deleting the copy constructor and assignment (and making the constructor private) ensures nobody can clone the instance.

When should you prefer std::function over a virtual interface for a behaviour?

  • When the behaviour has many related methods and its own state
  • When the strategy or callback is small and you want maximum flexibility with no class to write
  • Never; interfaces are always better
  • Only for memory management

Answer: When the strategy or callback is small and you want maximum flexibility with no class to write. std::function shines for small one-method strategies and callbacks; a virtual interface fits richer, multi-method contracts.

What is a 'fat interface' and how do you fix it?

  • An interface with one method; merge it with others
  • A class that uses too much memory; shrink its fields
  • A base class with many unrelated pure-virtual methods; split it into small focused interfaces (Interface Segregation)
  • An interface with a virtual destructor; remove it

Answer: A base class with many unrelated pure-virtual methods; split it into small focused interfaces (Interface Segregation). A fat interface forces empty or throw-only overrides; the fix is to split it into several small, focused interfaces.