When would you choose table-driven over switch-case?
Table-driven becomes the better choice when an FSM grows past about 5 states or has significant transition uniformity. Three concrete reasons:
1. The transition table is the spec. A 2D matrix with rows = states, columns = events, cells = (next state, action) is reviewable as a literal table. You can verify completeness by inspection ("every cell has something defined"). Adding a new event adds a column without touching existing cells. Adding a new state adds a row. The structure stays linear; the dispatch logic is unchanged.
2. Testing is straightforward. As discussed in the testing question, transition coverage is a generated loop iterating rows × columns. Switch-case FSMs require manual enumeration of each (state, event) combo.
3. Code generation is natural. If you ever want to generate the FSM from a spec file (YAML, JSON, Yakindu, Stateflow), the natural output is a transition table. Switch-case generation is awkward.
Switch-case is better when:
- The FSM is small (≤ 5 states), so the whole thing fits in one screen
- Performance matters at the dispatch level — switch-case typically compiles to a tight jump table with no indirect call overhead
- You want each case's logic visible in line with control flow rather than encoded as data
The breakdown threshold is roughly when adding a new event becomes painful: if you're touching 8 different case blocks to add one event, switch-case is past its sweet spot. Refactor to table-driven or function-pointer.
A common middle ground in practice: switch-case for the outer state dispatch, but with shared helper functions for common actions. This avoids code duplication without going fully to a table.
Source: State Machines Q&A
