Lesson 05

PAM architecture β€” their domain

15 minutes of fluent iGaming-architecture talk and you're ahead of 90% of candidates.

1 Β· What is a PAM?

🧠 in plain english
Think of a casino with 4 different gambling games + a cashier's window + an ID-check counter + a security manager. PAM = the cashier's window plus the ID counter plus the security manager, all in one. Every game checks with the PAM before letting you play. PAM holds your identity, your money, your limits, your rules.

PAM = Player Account Management. The operational core of any iGaming platform:

  • Accounts, sessions, login
  • KYC tiers (light verification β†’ full verification)
  • Wallet (deposits, withdrawals, balance)
  • Limits (daily/weekly deposit caps, self-exclusion)
  • Bonus engine
  • Compliance / audit trail

Sportsbook + casino + payment providers all plug into the PAM, not the other way round.

2 Β· The system diagram

               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚           FRONTEND                 β”‚
               β”‚  Next.js Β· per-tenant theme Β· WS   β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚   BFF (Next API / Nest)     β”‚ ← auth, aggregation, WS hub
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β–Ό                  β–Ό                     β–Ό
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚  PAM    │◄────► β”‚ Wallet   β”‚ ◄─────► β”‚  Sportsbook β”‚
      β”‚ accountsβ”‚       β”‚ ledger   β”‚         β”‚  + casino   β”‚
      β”‚ KYC     β”‚       β”‚ idempotentβ”‚        β”‚  vendor     β”‚
      β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                  β”‚
           β–Ό                  β”‚
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”
      β”‚complianceβ”‚        β”‚payment β”‚
      β”‚ + audit  β”‚        β”‚ PSPs   β”‚
      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3 Β· Whitelabel multi-tenancy β€” switch operator live

🧠 in plain english
Whitelabel = one factory, many brand stickers. The casino code is built once. Each operator (Bluestar, Redbet, Goldstrike) gets their own brand colors, currency, and rules, but the engine is identical. New operator = new config, not new code.

Click each operator below. Watch the brand color, currency, and feature visibility change. Notice how β€œLive Betting” is greyed out for the UK operator β€” that's a feature flag, not different code.

β˜…
BLUESTAR CASINO
Β£ GBP
Slots
Live Casino
Live Betting
Promotions
tenant=bluestar Β· region=UK Β· liveBetting=false (regulator)
// 1. Middleware resolves tenant from host
export function middleware(req: NextRequest) {
  const host = req.headers.get("host"); // operator-a.com
  const tenant = resolveTenant(host);
  req.headers.set("x-tenant", tenant.id);
}

// 2. Theme loaded server-side, injected as CSS vars
async function RootLayout({ children, params: { tenant } }) {
  const theme = await getTenantTheme(tenant);
  return (
    <html style={{
      "--brand-primary": theme.primary,
      "--brand-radius":  theme.radius,
    } as CSSProperties}>{children}</html>
  );
}

// 3. Flags evaluated server-side per tenant + region
const flags = await flagsClient.evaluate({ tenant, user, country });
// flags = { liveBetting: false, casino: true }

{flags.liveBetting && <LiveBettingTab />}
Golden rule: configuration over code. Onboarding a new operator must require ZERO deploys. Theme + feature flags drive everything.

4 Β· Money operations β€” idempotency

🧠 in plain english
Imagine your card got double-charged at the supermarket. The cashier swiped twice because the first beep was unclear. Idempotency keys are the cashier saying β€œwait, I already processed transaction #ABC123. Don't charge again.” On the web: a flaky network or a double-click can submit two POSTs. Without idempotency keys, the user is charged twice.

Step through it line-by-line. Watch the BAD wallet hit $200 (overcharged), watch the GOOD wallet stay at $150 because the server stores the idempotency key and returns the cached result on retry:

Idempotency keys β€” wallet ops, line-by-line
ts
step 0 / 10
1// User double-clicks 'Deposit $50' on flaky network
2// Both clicks fire requests.
3
4// ❌ WITHOUT idempotency
5POST /deposit { amount: 50 }
6POST /deposit { amount: 50 } // duplicate from retry/double-click
7// server has no way to detect duplicate β†’ charges twice
8
9// βœ… WITH idempotency key
10const key = crypto.randomUUID(); // client generates once
11POST /deposit { amount: 50, idempotencyKey: key }
12POST /deposit { amount: 50, idempotencyKey: key } // same key
13// server stores result by key β†’ returns cached result on duplicate
BAD wallet
$100
GOOD wallet
$100
server idempotency table
empty
network
press β–Ά play to step through this code, or use ← β†’ to scrub.

6 Β· Cache stampede β€” the senior trap

This is the question that separates seniors from mid-levels. β€œYou have Redis caching profiles. The cache for a popular profile expires. 1000 users hit it within the same second. What happens?”

Step through the BAD path β€” no lock, every request hits the DB:

Cache stampede β€” what goes wrong without a lock
ts
step 0 / 5
1// ❌ BAD β€” no lock, all misses hit DB at once
2async function getProfile(username) {
3 let data = await redis.get(`profile:${username}`);
4 if (!data) {
5 // 1000 concurrent requests all reach this branch
6 data = await db.profile.findUnique({ where: { username }});
7 await redis.set(`profile:${username}`, data, "EX", 60);
8 }
9 return data;
10}
πŸ”΄Redis
fresh
🐘Postgres
0
queries run
in-flight requests0
log
press β–Ά play to step through this code, or use ← β†’ to scrub.

Now the senior fix β€” stale-while-revalidate plus a Redis SETNX lock so only ONE request rebuilds:

SWR + SETNX lock β€” the senior fix
ts
step 0 / 8
1// βœ… GOOD β€” stale-while-revalidate + lock
2async function getProfile(username) {
3 const cached = await redis.get(`profile:${username}`);
4 if (cached && !cached.stale) return cached.data;
5
6 if (cached?.stale) {
7 // serve stale immediately, refresh in background
8 refreshInBackground(username);
9 return cached.data;
10 }
11
12 // cold cache: try to acquire lock
13 const gotLock = await redis.set(
14 `lock:profile:${username}`, "1", "NX", "EX", 5);
15
16 if (gotLock) {
17 const data = await db.profile.findUnique(...); // only ONE call
18 await redis.set(`profile:${username}`, data, "EX", 60);
19 await redis.del(`lock:profile:${username}`);
20 return data;
21 }
22
23 // someone else is rebuilding β€” wait briefly + retry
24 await sleep(50);
25 return getProfile(username);
26}
πŸ”΄Redis
fresh
🐘Postgres
0
queries run
in-flight requests0
log
press β–Ά play to step through this code, or use ← β†’ to scrub.
πŸ“£ say this in the interview
β€œClient generates a UUID per operation. Server stores the result keyed on user, op, and key in a unique-indexed table. Subsequent requests with the same key return the cached result β€” no double-charge.”

5 Β· The non-negotiables for money

⚑ quick check
A user clicks 'Withdraw $500'. Network is flaky, the request times out. They click again. The server eventually receives both requests. Without idempotency, what happens?

πŸ“Œ Smart questions to ask Wednesday

  1. β€œSingle multi-tenant codebase or per-operator deployments?”
  2. β€œFeature flags β€” build-time, edge middleware, or runtime client?”
  3. β€œWallet ops β€” UUID idempotency from the client, or BFF dedup?”
  4. β€œWhat's the SSR / RSC split for the game lobby?”
  5. β€œTanStack Query, SWR, or custom cache?”
  6. β€œRUM provider? How do you alert on INP regressions?”
  7. β€œWebSockets vs SSE for real-time? Multiplexed or per-feature?”
  8. β€œBFF β€” Next API routes, Nest service, or GraphQL gateway?”
AI tutor
llama-3.1-8b-instant
Ask anything about the interview
Concept clarifications, β€œexplain X simpler,” rehearsal phrasing, follow-up questions to model answers.