Low-Level Design (LLD) Patterns: SOLID, Concurrency & Contracts
Practical Low-Level Design (LLD): SOLID interfaces, optimistic vs pessimistic concurrency control, idempotent state machines, and schema normalization.
Architectural Summary (AEO)
Clean Low-Level Design (LLD) prevents race conditions and spaghetti rewrites as codebases grow. Production LLD relies on dependency-inverted interface contracts, explicit finite state machines for order/payment lifecycles, optimistic locking (`version` columns) to prevent lost updates, and normalized core schemas.
Reference System Flow
Controller (HTTP / Zod Validation) │ ▼ Service Layer (Pure Domain Rules + State Machine) │ depends on Interface (PaymentGateway, LeadRepository) ▼ Adapter Layer (PostgreSQL Optimistic Lock / Stripe / SMTP)
1. Dependency Inversion & Interface Contracts
Keep domain services decoupled from concrete SDKs (Stripe, Nodemailer, AWS S3). Defining thin repository and provider interfaces lets unit tests swap in deterministic in-memory fakes in milliseconds.
2. Optimistic vs Pessimistic Concurrency Control
Double-booking and balance-overdraft bugs happen when two requests read the same row concurrently. Use Optimistic Locking (`UPDATE accounts SET balance = $1, version = version + 1 WHERE id = $2 AND version = $3`) for low-contention flows, and `SELECT ... FOR UPDATE` pessimistic row locks for hot inventory.
3. Finite State Machines Over Boolean Flags
Avoid boolean soup (`isPaid`, `isCancelled`, `isRefunded`) which allows impossible states. Model a single `status` enum with an explicit transition map (`pending -> paid -> fulfilled | refunded`) enforced at the domain boundary.
Optimistic Locking Update Pattern (SQL + JS)
async function updateInventoryOptimistic(db, itemId, delta, expectedVersion) {
const result = await db.query(
`UPDATE inventory
SET quantity = quantity + $1, version = version + 1
WHERE id = $2 AND version = $3 AND quantity + $1 >= 0
RETURNING *`,
[delta, itemId, expectedVersion]
);
if (result.rowCount === 0) {
throw new ConflictError("Concurrent modification or insufficient stock.");
}
return result.rows[0];
}Architectural Trade-Offs
| Concurrency Control | Lock Contention | Throughput | Best Fit |
|---|---|---|---|
| Optimistic (version check) | None (retries on conflict) | Very High | User profiles, CMS, low-collision orders |
| Pessimistic (FOR UPDATE) | Blocks competing rows | Lower under contention | Flash-sale inventory, ledger transfers |
| Atomic SQL Increment | Minimal row-latch | Highest | Counters, quotas, wallet debits |
Production Benchmarks & Field Observations
- Replacing boolean flags with explicit state-transition guards eliminates an entire class of invalid-webhook race conditions in payment pipelines.
Related Commercial Services
Engineering FAQs
When is denormalization acceptable in SQL schemas?
Denormalize selectively for immutable historical snapshots (e.g., storing `unit_price` on `order_items` even though `products.price` exists) or precomputed read counters.
How do you enforce LLD boundaries in a Next.js codebase?
Keep Route Handlers thin (parse + auth + call service), place business logic in pure `src/lib/` modules, and validate all external payloads with Zod.
