How Allo was built
A walkthrough of every problem the assignment posed, the approach I took, and the specific decisions that make the solution correct under concurrency.
1. Data model for inventory and reservations
The schema has five tables. The key insight is separating catalogue (products) from stock (inventory) and from holds (reservations).
How do you track available stock without a race?
Each inventory row stores total_units and reserved_units. Available stock is always total_units − reserved_units — computed at query time, never stored. A CHECK constraint enforces reserved_units ≤ total_units at the database level as a hard backstop.
This means the database itself will reject any update that would oversell, even if application logic has a bug.
Why a separate reservations table instead of decrementing stock immediately?
Decrementing on add-to-cart and incrementing on abandon is fragile — abandoned carts never reliably trigger cleanup. Instead, a reservation is a time-bounded hold: stock is logically unavailable but not yet sold. The transition is:
pending → confirmed (payment succeeded, stock permanently decremented) pending → released (customer cancelled, units returned) pending → expired (TTL elapsed, units returned by cron)
The idempotency_keys table deduplicates POST requests so retried payments never double-charge or double-reserve.
2. Race-free reservation API
The hardest part of the assignment: two concurrent requests for the last unit must result in exactly one success and one 409 insufficient_stock.
The concurrency problem
Without locking, two requests can both read available = 1, both decide to proceed, and both insert a reservation — overselling by one unit. This is a classic TOCTOU (time-of-check / time-of-use) race.
Solution: SELECT FOR UPDATE inside a stored function
All reservation logic runs inside a single Postgres transaction in reserve_units(). The critical section:
-- Acquires a row-level lock. Concurrent callers block here. SELECT * FROM inventory WHERE product_id = $1 AND warehouse_id = $2 FOR UPDATE; -- Only one transaction holds the lock at a time. -- Re-check availability with fresh data: IF available_units < requested_qty THEN RAISE EXCEPTION 'insufficient_stock'; END IF; UPDATE inventory SET reserved_units = reserved_units + requested_qty WHERE product_id = $1 AND warehouse_id = $2; INSERT INTO reservations (...) RETURNING *;
With N concurrent requests for the last unit, the first acquires the lock and commits. The remaining N−1 wake up, re-read the now-updated count, and raise insufficient_stock. No Redis, no application-level mutex, no race window.
Why a stored function instead of application-layer transactions?
A stored function runs entirely inside Postgres. There is no network round-trip between the lock acquisition and the update — the lock is held for microseconds. An application-layer transaction would hold the lock across two network hops (read → application logic → write), dramatically increasing contention under load.
Idempotency
POST endpoints accept an Idempotency-Key header. The first request claims the key (status 0 = in-flight), runs the handler, then stores the response. A retry with the same key replays the stored response with Idempotent-Replay: true. A different body with the same key returns 409 idempotency_conflict. An in-flight duplicate polls for up to 5 s then returns 425.
3. Frontend
Product listing — paginated grid
Products are fetched server-side (Next.js App Router, force-dynamic). The grid renders 21 items initially; a "Load more" button reveals the next batch client-side from the already-fetched array — no extra network request. Cards animate in with a staggered fade using motion/react.
Product detail — sticky reserve panel
The detail page shows stock broken down by warehouse. The reserve panel is sticky on desktop. Clicking "Reserve" opens a modal that calls POST /api/reservations and redirects to the checkout page on success. The modal closes on Escape or outside click — no close button per design spec.
Checkout — realtime countdown
The checkout page subscribes to the reservation row via Supabase Realtime. If the reservation is confirmed or released from another tab, the UI updates instantly. A 5-second polling fallback handles environments where WebSockets are blocked. The countdown timer ticks client-side from expires_at.
4. Reservation expiry — three layers
A reservation that is never confirmed or released must eventually free its units. Three independent mechanisms ensure this:
Layer 1 — Vercel Cron
vercel.json schedules GET /api/cron/expire-reservations every minute. The handler calls expire_reservations() which bulk-updates all rows where status = 'pending' and expires_at ≤ now(), returning reserved units to inventory.
Layer 2 — Lazy expiry on read
Every GET /api/reservations/:id call checks the timestamp inline and expires the reservation if needed before returning. This means a reservation is never served as "pending" after its TTL, even if the cron hasn't run yet.
Layer 3 — Lazy expiry on confirm
confirm_reservation() re-checks expires_at inside the same lock that decrements stock. A payment that races the expiry timer cannot succeed against a stale hold — the function raises reservation_expired and the stock is never decremented.
Bonus: what was added beyond the spec
Live video-to-ASCII hero
The hero section renders a looping video as coloured ASCII art in real time using a canvas-based renderer — no library, ~150 lines.
Cinematic design system
Custom Tailwind v4 design tokens: canvas-night / canvas-cream tracks, display type scale, pill buttons, elevation shadows.
106 seeded products
Four warehouses (blr-01, del-01, mum-01, hyd-01) with category-matched Unsplash images for every product.
Docs page with ISR
/docs fetches the README from GitHub and renders it as markdown with 1-hour ISR — always up to date without a redeploy.
Security hardening
Supabase advisor issues resolved: inventory view recreated with security_invoker = true, EXECUTE revoked from anon/public on internal functions.
CDN caching
hero.mp4 served with Cache-Control: public, max-age=31536000, immutable via Vercel Edge Network. Browser preload hint in <head>.
Honest trade-offs
Reservation IDs are UUID v4 (not enumerable), but in production reservations would be attached to authenticated users.
The anon Supabase key can read all tables. Required for Realtime to work without auth. Production would add row-level security policies.
A production PR would include a Postgres concurrency test (10 connections, 10 concurrent reserve_units calls for the last unit, assert exactly 1 succeeds) and Playwright E2E tests.
Keys live forever in this demo. Production would prune rows older than 24 h.