Advanced Projects
Learning Objectives
Build a logging framework, plugin application, elevator simulator, instrument-cluster domain model, mini in-memory database and booking system.
Combine concurrency, state machines, versioning, aggregates, events and idempotency in complete designs.
Prerequisites: Stages 3–4.
Intuition and Definition
Advanced projects are judged on design quality: clear boundaries, explicit invariants, honest failure handling, tests, and proportionate abstraction.
Why It Matters
These are the systems interviewers ask you to design and teams actually build.
Syntax and Language Notes
Projects 8, 10 and 11 are in C++; 9, 12 and 13 in Python.
Complete Example
Project 8 — Logging Framework (levels, sinks, formatters, decorators, async delivery)
Project 9 — Plugin Application (discovery, versioning, boundaries)
Project 10 — Elevator Simulator (state machine, scheduling, invariants)
Project 11 — Instrument Cluster Domain Model Signals with validity, display modes, prioritized warnings and a hardware abstraction. (Illustrative OOP; real clusters follow functional-safety processes such as ISO 26262 and OEM specifications.)
Project 12 — Mini In-Memory Database (entities, repositories, indexes, transactions)
Project 13 — Booking System (aggregate, events, idempotency, transactions)
#include <atomic>
#include <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <vector>
enum class Level { Debug, Info, Warn, Error };
const char* name(Level l) { static const char* n[] = {"DEBUG", "INFO", "WARN", "ERROR"}; return n[static_cast<int>(l)]; }
struct Record { Level level; std::string logger, message; long seq; };
struct Sink { virtual ~Sink() = default; virtual void write(const Record& r) = 0; };
struct ConsoleSink : Sink {
void write(const Record& r) override { std::cout << "#" << r.seq << " [" << name(r.level) << "] " << r.logger << ": " << r.message << "\n"; }
};
class LevelFilter : public Sink { // decorator: filters by level
std::unique_ptr<Sink> inner_; Level min_;
public:
LevelFilter(std::unique_ptr<Sink> s, Level m) : inner_(std::move(s)), min_(m) {}
void write(const Record& r) override { if (r.level >= min_) inner_->write(r); }
};
class AsyncSink : public Sink { // decorator: background thread, ordered delivery
std::unique_ptr<Sink> inner_;
std::queue<Record> q_; std::mutex m_; std::conditio
…#1 [INFO] can: frame batch 0
#2 [INFO] can: frame batch 1
#3 [INFO] can: frame batch 2
#5 [ERROR] diag: DTC U0100 stored
logger shut down cleanlyfrom abc import ABC, abstractmethod
API_VERSION = (2, 1) # host plugin API version (major, minor)
class Plugin(ABC):
name: str
requires_api: tuple
permissions: frozenset = frozenset()
@abstractmethod
def run(self, ctx): ...
class Context: # what plugins may touch: least privilege
def __init__(self, granted):
self._granted = granted
self.output = []
def read_dtcs(self):
if "diag.read" not in self._granted: raise PermissionError("diag.read not granted")
return ["P0301", "U0100"]
def emit(self, line): self.output.append(line)
REGISTRY = {}
def register(cls):
REGISTRY[cls.name] = cls
return cls
@register
class DtcReport(Plugin):
name, requires_api, permissions = "dtc-report", (2, 0), frozenset({"diag.read"})
def run(self, ctx): ctx.emit(f"{len(ctx.read_dtcs())} DTCs stored")
@register
class Sneaky(Plugin):
name, requires_api, permissions = "sneaky", (2, 0), frozenset()
def run(self, ctx): ctx.emit(str(ctx.read_dtcs())) # tries to read without declaring the permission
@register
class Future(Plugin):
name, requir
…dtc-report: ['2 DTCs stored']
sneaky: blocked (diag.read not granted)
future: skipped (needs API (3, 0), host has (2, 1))#include <cassert>
#include <iostream>
#include <set>
#include <string>
class Elevator {
public:
enum class State { Idle, MovingUp, MovingDown, DoorsOpen };
private:
int floor_ = 0;
const int top_;
State state_ = State::Idle;
std::set<int> stops_;
static const char* n(State s) { switch (s) { case State::Idle: return "idle"; case State::MovingUp: return "up"; case State::MovingDown: return "down"; case State::DoorsOpen: return "doors open"; } return "?"; }
public:
explicit Elevator(int top) : top_(top) {}
void request(int f) { if (f < 0 || f > top_) throw std::out_of_range("no such floor"); if (f != floor_ || state_ != State::DoorsOpen) stops_.insert(f); }
// one simulation tick; SCAN policy: keep direction while stops remain ahead
std::string tick() {
if (state_ == State::DoorsOpen) { state_ = State::Idle; return "close doors at " + std::to_string(floor_); }
if (stops_.erase(floor_)) { state_ = State::DoorsOpen; return "open doors at " + std::to_string(floor_); }
if (stops_.empty()) { state_ = State::Idle; return "idle at " + std::to_string(floor_); }
bool above = stops_.upper_bound(floor_) != stops_.end();
…t1: move up to 1
t2: move up to 2
t3: open doors at 2
t4: close doors at 2
t5: move up to 3
t6: move up to 4
t7: open doors at 4
t8: close doors at 4
t9: move up to 5
t10: move up to 6
t11: move up to 7
t12: open doors at 7
t13: close doors at 7
t14: move down to 6
t15: move down to 5
t16: move down to 4
t17: move down to 3
t18: move down to 2
t19: move down to 1
t20: open doors at 1
t21: close doors at 1
t22: idle at 1#include <algorithm>
#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <vector>
struct Signal { double value; bool valid; };
struct Display { virtual ~Display() = default; virtual void show(const std::string& line) = 0; }; // HAL port
struct ConsoleDisplay : Display { void show(const std::string& l) override { std::cout << " | " << l << "\n"; } };
enum class Mode { Normal, Sport, Service };
struct Warning { std::string text; int priority; bool latched; }; // lower number = more important
class Cluster {
std::map<std::string, Signal> signals_;
std::vector<Warning> active_;
Mode mode_ = Mode::Normal;
Display& display_;
public:
explicit Cluster(Display& d) : display_(d) {}
void onSignal(const std::string& name, Signal s) {
signals_[name] = s;
if (name == "coolant" && s.valid && s.value > 110) raise({"COOLANT OVERHEAT", 1, true});
if (name == "fuel" && s.valid && s.value < 10) raise({"LOW FUEL", 3, false});
}
void setMode(Mode m) { mode_ = m; }
void acknowledge(const std::string& text) { // non-latched warnings can be dismissed; latched ones stay
active_.erase(std::remove_if(a
…normal mode:
| speed 87 km/h
| WARNING: LOW FUEL
sport mode, overheat:
| speed 87 km/h
| rpm 3150
| WARNING: COOLANT OVERHEAT (+1)
after acknowledging, speed signal lost:
| speed --
| rpm 3150
| WARNING: COOLANT OVERHEATimport copy
class Table:
def __init__(self, key, indexes=()):
self.key, self.rows, self.indexes = key, {}, {name: {} for name in indexes}
def insert(self, row):
k = row[self.key]
if k in self.rows: raise KeyError(f"duplicate key {k}")
self.rows[k] = row
for name, idx in self.indexes.items(): idx.setdefault(row[name], set()).add(k)
def find_by(self, column, value):
if column in self.indexes: # index lookup
return [self.rows[k] for k in sorted(self.indexes[column].get(value, ()))]
return [r for r in self.rows.values() if r[column] == value] # full scan
class Database:
def __init__(self): self.tables, self._snapshot = {}, None
def create(self, name, key, indexes=()): self.tables[name] = Table(key, indexes)
def begin(self): self._snapshot = copy.deepcopy(self.tables)
def commit(self): self._snapshot = None
def rollback(self): self.tables, self._snapshot = self._snapshot, None
class VehicleRepository: # domain-facing repository
def __init__(self, db): self.t = db.tables["vehicles"]
def add(self, vin,
…ID.3: ['V1', 'V3']
rolled back: 'duplicate key V1'
Golf after rollback: ['V2'] | rows: 3Step-by-Step Walkthrough
Logging: loggers write records to a sink chain AsyncSink → LevelFilter → ConsoleSink; the async decorator delivers in order on a background thread and drains on destruction; debug messages are filtered.
Plugins: discovery by registration, semantic API versioning (same major, sufficient minor), and least-privilege contexts — the undeclared permission is blocked.
Elevator: a SCAN policy serves stops in the current direction; the late call for floor 1 is served after the upward run; an assertion guards the floor invariant.
Cluster: signals carry validity; invalid speed shows "--"; warnings are prioritized and latched warnings can't be dismissed; the display is a port.
Mini DB: a secondary index answers by_model without a scan; a failed transaction rolls back to the snapshot.
Booking: the aggregate enforces capacity and duplicate rules, is idempotent per booking id, and records domain events.
Common Mistakes
Holding locks while doing I/O in loggers; losing messages at shutdown.
Plugins with full access to the host.
Elevators whose state is spread across flags.
Clusters displaying stale or invalid values as real.
Indexes not updated on every write; transactions that partially apply.
Bookings without idempotency (double bookings on retry).
Trade-offs and Alternatives
Each project simplifies: production loggers batch writes and handle back-pressure; plugin systems isolate plugins in processes or sandboxes; databases use write-ahead logs instead of snapshots; booking systems need concurrency control (optimistic locking) and persistent idempotency records.
Real-World Use
spdlog/Serilog-style loggers, VS Code/IDE plugin hosts, elevator controllers, automotive HMI software, embedded key–value stores, workshop and appointment booking platforms.
Practice
Q1. (Easy) Why does AsyncSink's destructor join the worker?
Answer: To drain pending records and avoid destroying the queue while the thread still uses it.
Q2. (Medium) Add an "express" priority to the elevator that serves a floor next regardless of direction. What invariant must you keep?
Answer: The car must still stop only at valid floors and never move with doors open; express requests can preempt the SCAN order but must be bounded (to avoid starvation of other requests).
Q3. (Advanced) How would you make bookings safe when two servers process requests concurrently?
Answer: Store the aggregate in a database with optimistic concurrency (version column) or a unique constraint on (slot, bay)/(booking id); on conflict, reload and retry; persist idempotency keys with results so retries across servers return the same booking.
Interview Question
Q1. "Design a logging framework." Answer: Separate concerns: Logger (named, cheap to call, level check first), Record (immutable data with timestamp, level, context), formatters, sinks (console, file, network) composed with decorators (filtering, async buffering, sampling). Async delivery via a bounded queue with an overflow policy (block, drop oldest, or drop with a counter), ordered per sink, flushed on shutdown and on fatal errors. Configuration at start-up, thread-safe throughout, and no I/O under locks.
Quick Quiz
Q1. Which project uses SCAN scheduling?
Answer: The elevator simulator.
Q2. What does the plugin host check before running a plugin?
Answer: API version compatibility and declared permissions.
Q3. What makes repeated booking requests safe?
Answer: Idempotency by booking id.
Summary
Advanced projects combine patterns with concurrency, state, versioning, aggregates, events and idempotency.
Every project: requirements, UML, milestones, code, tests, review, extensions.
Related: Modules 36, 43, 51, 57, 60, 68, 69, 74–76.