Udaiy’s Blog

System Design Thinking - basic

use case: Inventory Management System

A compact mental compile sheet for writing an inventory management system under time pressure. Topic → why → code anchor. Retype from memory; adapt return strings to the prompt.


60-second start

Write this first:

E — Entities: Product, Warehouse, InventoryError*
S — State:   catalog{}, warehouses{}, ledger defaultdict(dict), locks defaultdict(Lock)
L — Logic:   Guard → Lock → Mutate → Return exact string
P — Parser:  strip → split → int() → try/except → "\n".join

When a test fails, check these two first:

  1. int(...) cast on quantities
  2. Extra space / wrong exact output string

1. Mental model

Stock lives at (warehouse, product), not on Product

Why: A product does not “have 50 units.” W1 may hold 30 and W2 may hold 20. Quantity on Product loses location and breaks transfer.

catalog     : ProductID   → Product(id, name)
warehouses  : WarehouseID → Warehouse(id, location)
ledger      : WarehouseID → { ProductID → qty:int }
locks       : WarehouseID → threading.Lock
{"W1": {"P1": 50, "P2": 10}, "W2": {"P1": 100}}

ESLP compile order

Why: Methods before state → KeyError thrash. Nouns → memory → mutations → IO.

Meaning Ask yourself
E Entities + errors What nouns? What fails?
S State Where stored? How locked?
L Logic Guard → Lock → Mutate → Return
P Parser strip → split → try/catch → exact strings

2. Entities & exceptions

@dataclass for Product / Warehouse

Why: Clear identity fields; less boilerplate than raw dicts.

from dataclasses import dataclass

@dataclass(slots=True)
class Product:
    id: str
    name: str

@dataclass(slots=True)
class Warehouse:
    id: str
    location: str

Drop slots=True if the runtime is old — behavior matters more.

Domain exceptions

Why: One catch in the parser maps to the prompt’s exact error string.

class InventoryError(Exception): pass
class InsufficientStockError(InventoryError): pass
class EntityNotFoundError(InventoryError): pass
class InvalidInputError(InventoryError): pass
class CapacityExceededError(InventoryError): pass

Rewrite exception → output mapping in one place after reading the prompt.


3. State

defaultdict(dict) ledger

Why: First touch of a warehouse auto-creates {}. Fewer nested existence checks.

from collections import defaultdict
import threading

self.catalog = {}
self.warehouses = {}
self.ledger = defaultdict(dict)           # w → {p → qty}
self.locks = defaultdict(threading.Lock)  # w → Lock

Always .get(p_id, 0) on read

Why: Missing product in a warehouse means 0, not KeyError.

current = self.ledger[w_id].get(p_id, 0)

4. Logic — Guard → Lock → Mutate → Return

Unified modify_stock(w, p, qty)

Why: ADD and REMOVE share one path. Parser passes +qty or -qty.

def modify_stock(self, w_id, p_id, qty: int) -> str:
    if w_id not in self.warehouses:
        raise EntityNotFoundError("warehouse")
    if p_id not in self.catalog:
        raise EntityNotFoundError("product")

    with self.locks[w_id]:
        current = self.ledger[w_id].get(p_id, 0)
        new_qty = current + qty
        if new_qty < 0:
            raise InsufficientStockError("stock")
        self.ledger[w_id][p_id] = new_qty
        return str(new_qty)  # only if prompt wants remaining qty

Return exactly what samples show ("50", not "Remaining: 50").

CRUD existence checks

Why: Hidden cases love duplicate IDs and unknown IDs.

Per-warehouse locks

Why: Two writers can both read current=1 and both decrement → negative stock. Per-warehouse locks allow concurrent ops on different warehouses.

Deadlock-free transfer

Why: A locks W1→W2 while B locks W2→W1 → deadlock. Always acquire in sorted ID order.

def transfer_stock(self, w_from, w_to, p_id, qty: int) -> str:
    if qty <= 0:
        raise InvalidInputError("qty")
    if w_from not in self.warehouses or w_to not in self.warehouses:
        raise EntityNotFoundError("warehouse")
    if p_id not in self.catalog:
        raise EntityNotFoundError("product")

    if w_from == w_to:
        # threading.Lock is NOT reentrant — never double-acquire
        return "SUCCESS"  # or error per prompt

    a, b = sorted([w_from, w_to])
    with self.locks[a]:
        with self.locks[b]:
            src = self.ledger[w_from].get(p_id, 0)
            if src < qty:
                raise InsufficientStockError("stock")
            self.ledger[w_from][p_id] = src - qty
            self.ledger[w_to][p_id] = self.ledger[w_to].get(p_id, 0) + qty
    return "SUCCESS"

5. Parser

Command loop shape

def ArrayChallenge(strArr):
    manager = InventoryManager()
    out = []
    for line in strArr:
        line = line.strip()
        if not line:
            continue
        parts = line.split()
        cmd = parts[0].upper()
        try:
            if cmd == "ADD_PRODUCT":
                out.append(manager.add_product(parts[1], " ".join(parts[2:])))
            elif cmd == "ADD_WAREHOUSE":
                out.append(manager.add_warehouse(parts[1], " ".join(parts[2:])))
            elif cmd == "ADD_STOCK":
                out.append(manager.modify_stock(parts[1], parts[2], int(parts[3])))
            elif cmd == "REMOVE_STOCK":
                out.append(manager.modify_stock(parts[1], parts[2], -int(parts[3])))
            elif cmd == "TRANSFER":
                out.append(manager.transfer_stock(
                    parts[1], parts[2], parts[3], int(parts[4])))
            elif cmd == "GET_STOCK":
                out.append(manager.get_stock(parts[1], parts[2]))
            else:
                out.append("ERROR:_UNKNOWN_COMMAND")
        except ValueError:
            out.append("ERROR:_INVALID_NUMBER_FORMAT")
        except IndexError:
            out.append("ERROR:_MISSING_ARGUMENTS")
        except InventoryError:
            out.append("ERROR")  # replace with prompt’s exact strings
    return "\n".join(out)

Gotchas

Threat Defense Why
" ADD_STOCK " strip() Leading spaces break the command token
"P1 Mac Book" " ".join(parts[2:]) Names are multi-word
Verbose returns Match samples literally Diff checkers are exact
Missing ledger key .get(p, 0) Absence = zero
Qty as string int(parts[k]) Need arithmetic
Blank lines if not line: continue Empty split crashes
Arg order swap Recheck prompt wid vs pid first is a common fail

Stdin fallback

if __name__ == "__main__":
    import sys, ast
    raw = sys.stdin.read().strip()
    if raw.startswith("["):
        arr = ast.literal_eval(raw)
    else:
        arr = raw.splitlines()
    print(ArrayChallenge(arr))

6. Curveballs (only if asked)

Capacity

Why: ADD/TRANSFER-in can fail even when the product exists.

total = sum(self.ledger[w_id].values())
if total + qty > warehouse.capacity:
    raise CapacityExceededError("capacity")

Atomic multi-item checkout

Why: All lines succeed or none apply.

# 1) validate every (pid, qty) against ledger
# 2) then apply all deductions
# never mutate halfway

Audit / history

Why: Append-only log for HISTORY queries.

self.events.append(("ADD_STOCK", w_id, p_id, qty, new_qty))

FIFO perishable batches

Why: Expiry — remove oldest batch first.

# ledger[w][p] = deque of (expiry, qty)
# REMOVE pops from the left; skip/reject expired per prompt

Reservations

Why: Prevent oversell between order create and ship.

available = on_hand - reserved
reserve  → reserved += qty   (if available >= qty)
commit   → on_hand -= qty; reserved -= qty
release  → reserved -= qty

7. Typing anchors (memorize shapes)

import sys, threading, ast
from collections import defaultdict
from dataclasses import dataclass
class InventoryManager:
    def __init__(self):
        self.catalog = {}
        self.warehouses = {}
        self.ledger = defaultdict(dict)
        self.locks = defaultdict(threading.Lock)
a, b = sorted([w_from, w_to])
parts = line.strip().split(); cmd = parts[0].upper()
name = " ".join(parts[2:])

8. Prompt adaptation (2 minutes)

  1. Copy sample I/O into a comment
  2. List commands + argument order
  3. Write exact success strings
  4. Write exact error strings
  5. Implement ESLP
  6. Sweep: strip, int, .get, lock sort, same-warehouse, negative qty, unknown IDs

9. Pocket sheet

E: Product, Warehouse, InventoryError*
S: catalog{}, warehouses{}, ledger defaultdict(dict), locks defaultdict(Lock)
L: Guard → Lock → Mutate → Return EXACT string
P: strip → split → upper → int() → try/except → "\n".join

ledger[w][p] = qty
read: ledger[w].get(p, 0)

same-wh transfer: no double-lock
transfer: a,b = sorted([from,to]); lock a then b

fail checklist: int | spaces | .get | strip | join names | lock order

10. Failure taxonomy

Symptom Likely cause
Almost all fail Wrong entry function / join separator
First command fails Forgot strip / wrong index
Multi-word name wrong Used parts[2] not join
Transfer hangs Same warehouse double-lock
Off-by formatting Trailing space/newline; ERROR vs ERROR:_...
Negative stock Missing new < 0 guard or missing lock