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.