Designing a system is more about efficiently dividing it into manageable components than just creating it. In the modern world, where devices perform multiple tasks, efficient design is paramount. One powerful method to achieve this is by using state machines.
Without beating around bush, let’s get to out board.
To illustrate, let’s consider a music player toy with a single button.
Example: Music Player Toy
We will keep it simple and divide its operation into two main responsibilities:
- Music On
- Music Off
We assume the device includes software capable of processing signals and button presses.
Design 1: Basic Button Press
Button Press → Interrupt → Music Start

In this design, music starts playing on a button press, and we assume it stops automatically when the song ends.
But what happens if the button is pressed multiple times during playback? There are two possible ways to handle this:
- Ignore multiple presses: The system starts the song once and ignores subsequent presses until the current song ends.
- Process every press: Each button press restarts the song, regardless of whether the previous song finished.
Handling multiple button presses this way may lead to race conditions, where two or more processes try to modify a shared resource (e.g., the playback state) simultaneously. For example, if a button press is processed while the state is still being updated, some presses may be missed, or unexpected behavior might occur. While this is not hazardous in a simple music player, it could be problematic in critical systems.
Enhanced Encapsulation
In the previous design, we saw how the system transitions between states: a button press starts playback, and a timeout stops it. However, exposing these transitions directly may not be ideal for real-world systems. A better approach is to encapsulate the logic within a state machine.
Benefits of Encapsulation:
Encapsulation hides the internal details of how states transition and what tasks are performed during each transition. This makes the system more modular and easier to debug. By isolating state-specific logic, engineers can focus on individual states without worrying about how other parts of the system are affected, leading to cleaner and more maintainable code.
Object-Oriented State Pattern
In this approach, each state is represented as an object with methods to handle transitions and events. Here’s an example:
class MusicPlayer {
class State Idle, Music_Playing;
class State current_state;
bool is_music_playing;
MusicPlayer() {
current_state = Idle;
is_music_playing = false;
Idle.checkForButtonPress();
}
~MusicPlayer() {
free(current_state);
current_state = nullptr;
}
class State checkForButtonPress() {
if (button_pressed_received) { // via hardware interrupt
is_music_playing = true;
current_state = Music_Playing;
}
}
};
Why This Approach is Scalable:
By treating states as independent objects, it becomes easier to add new states or modify existing ones without affecting the overall design. This pattern is particularly useful in systems with complex state transitions, as each state encapsulates its behavior and transitions.
Table-Driven State Machine
For systems with many similar states (e.g., a piano keyboard), a state table can simplify transitions. Here’s a simplified example:
State | Event | Next State | Action |
---|
Idle | Button Press | Music Playing | Start Music |
Music Playing | Song Ends | Idle | Stop Music |
While creating a state table can be tedious, it provides clarity and ensures consistency in state transitions, making debugging and scaling more manageable.
Event-Driven State Machine
This model is widely used in embedded and RTOS systems. The essence of an event-driven state machine is that the system acts only when an event occurs.
For example, in a traffic control system, the states (Red, Green, Yellow) represent events that trigger light changes:

(Red) -->[Timer Expired]--> (Green)
(Green) -->[Timer Expired]--> (Yellow)
(Yellow) -->[Timer Expired]--> (Red)
Why This Model Fits Real-Time Systems:
Event-driven state machines are highly reactive and efficient. They consume minimal resources when idle and respond promptly to events, making them ideal for real-time applications like traffic control or embedded devices.
Comparison of State Machine Models
Model | Use Case | Advantages | Disadvantages |
---|
Basic Interrupt-Driven | Simple systems with few states | Easy to implement | Susceptible to race conditions |
Enhanced Encapsulation | Modular systems | Easier debugging, cleaner code | Slightly more complex design |
Object-Oriented Pattern | Systems with complex transitions | Scalable, modular | Higher memory usage |
Table-Driven | Systems with many similar states | Clear and consistent transitions | Tedious to create/manage table |
Event-Driven | Real-time or reactive systems | Highly efficient, resource-light | Requires event management logic |
Conclusion
State machines are integral to designing efficient systems, especially in embedded and real-time applications. By breaking a system into manageable states and transitions, engineers can create robust, scalable designs. Whether it’s a simple music player or a complex traffic control system, state machines provide clarity, modularity, and reliability—qualities essential for modern system design.