Embedded Development · Design Patterns
The 10 most important design patterns for embedded development
Design patterns in embedded are not an academic exercise. They are the difference between software that runs in production for 10 years and software that collapses after the first field update.
Gang-of-Four books sit on every shelf. In embedded practice the same mistakes are still repeated: global state, spaghetti interrupts, monolithic main loops. The following 10 patterns are not theory — they run in devices that have been in the field for years.
1. State machine
Problem: Complex device states (Init, Ready, Active, Error, Shutdown) with unclear transitions. When: Every embedded device with modes. No exceptions.
enum class State { Init, Ready, Active, Error, Shutdown };
void transition(State& current, Event event) {
static const std::map<std::pair<State,Event>, State> table = {
{{State::Init, Event::Configured}, State::Ready},
{{State::Ready, Event::Start}, State::Active},
{{State::Active, Event::Fault}, State::Error},
};
auto it = table.find({current, event});
if (it != table.end()) current = it->second;
}
2. Observer (signal/slot)
Problem: Sensor A changes — UI, logger and controller have to react. When: Every multi-component system.
class SensorPublisher {
std::vector<std::function<void(float)>> subscribers_;
public:
void subscribe(std::function<void(float)> cb) { subscribers_.push_back(cb); }
void notify(float value) { for (auto& cb : subscribers_) cb(value); }
};
3. Command pattern
Problem: Undo/redo, remote control, queued operations. When: CNC machines, medical-device procedures, test sequences.
struct Command {
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
std::stack<std::unique_ptr<Command>> history;
4. Singleton (with care)
Problem: The hardware abstraction layer needs exactly one instance. When: HAL, configuration manager, logger. Not for anything else. Prefer dependency injection.
class HardwareManager {
public:
static HardwareManager& instance() {
static HardwareManager inst; // Meyers Singleton
return inst;
}
private:
HardwareManager() = default;
};
5. Producer-consumer (ring buffer)
Problem: Sensor data arrives faster than it can be processed. When: ADC sampling, UART reception, every ISR-to-task communication.
template<typename T, size_t N>
class RingBuffer {
std::array<T, N> buf_;
std::atomic<size_t> head_{0}, tail_{0};
public:
bool push(const T& val) { /* lock-free write */ }
bool pop(T& val) { /* lock-free read */ }
};
6. Strategy pattern
Problem: Same algorithm, different implementations (Kalman vs. moving average vs. median filter). When: Sensor fusion, communication protocols, encryption modes.
struct FilterStrategy {
virtual float apply(std::span<const float> data) = 0;
};
class KalmanFilter : public FilterStrategy { /* ... */ };
class MedianFilter : public FilterStrategy { /* ... */ };
7. Factory pattern
Problem: Object creation based on runtime configuration (which sensor? which protocol?). When: Plugin systems, multi-protocol devices, configurable test setups.
std::unique_ptr<Sensor> createSensor(const Config& cfg) {
if (cfg.type == "gnss") return std::make_unique<GnssSensor>(cfg);
if (cfg.type == "imu") return std::make_unique<ImuSensor>(cfg);
throw std::runtime_error("Unknown sensor: " + cfg.type);
}
8. RAII
Problem: Forgotten release of hardware resources (GPIO, SPI bus, file handles). When: Always in C++. This is not negotiable.
class SpiGuard {
SPI_HandleTypeDef* handle_;
public:
SpiGuard(SPI_HandleTypeDef* h) : handle_(h) { HAL_SPI_Init(handle_); }
~SpiGuard() { HAL_SPI_DeInit(handle_); }
SpiGuard(const SpiGuard&) = delete;
};
9. Facade pattern
Problem: Complex subsystem (network stack, display driver) with too many entry points. When: Wrappers for vendor SDKs, hardware abstraction, third-party library integration.
class DisplayFacade {
public:
void showStatus(const std::string& msg); // internally: 12 vendor API calls
void showError(int code); // internally: colour, icon, buzzer
void clear();
};
10. Watchdog pattern
Problem: System hangs, undefined state. When: Every embedded system in production. If you have no watchdog, you have no product.
class SoftwareWatchdog {
std::chrono::steady_clock::time_point last_kick_;
std::chrono::milliseconds timeout_;
public:
void kick() { last_kick_ = std::chrono::steady_clock::now(); }
bool expired() const {
return (std::chrono::steady_clock::now() - last_kick_) > timeout_;
}
};
Example from practice
In a surveying system (GNSS-based, Qt/C++) we use 7 of the 10 patterns at the same time: state machine for device states, observer for sensor data, ring buffer for the raw GNSS data, RAII for the serial interface, factory for protocol selection, facade for the vendor SDK wrapper, watchdog for field monitoring. No pattern is an end in itself — each solves a concrete problem we experienced in the field.
Further reading
Software architecture with the Qt framework
Qt's architecture layers and why the first two weeks decide everything.
AI in safety-critical systems (German)
How AI works in regulated environments — IEC 62304, ISO 26262 and reality.
From the book: Chapter 7 — 70/30 as an architecture principle (PDF)
Sample chapter from "Forget Prompts. The 70/30 AI System" — free, no e-mail required.
Landsberg am Lech · alpitype.de
Related articles
KI nutzen, ohne Daten in die Cloud zu schicken
On-premise KI: Wie Systeme vollständig lokal betrieben werden.
Sind Ihre Daten überhaupt für KI nutzbar?
Datenqualität prüfen, bevor Sie in KI investieren.
Was KI in einem realen Industrieprojekt kostet
Konkrete Zahlen, Phasen und ROI aus realen Projekten.
Not sure if this applies to your case?
We can check your setup in 2 weeks and tell you if AI is feasible.
Request feasibility audit →Talk to an engineer
No sales team. You talk directly with one of our software architects about your specific problem. 30 minutes. Response within 24 hours.
Email: info@alpitype.com
LinkedIn: AlpiType