// pseudo: repository tenant-aware
class OrderRepo {
constructor(private ctx: { tenantId: string }) {}
findById(id: string) {
return db.order.findFirst({ where: { id, tenantId: this.ctx.tenantId } });
}
}
Project Deep-Dive
draftedInterviewer đào sâu vào từng dự án trên CV: multi-tenant e-commerce, monolith → microservices, banking SPA, real-time finance — chuỗi prove-it cho từng claim, STAR template và số liệu cần thu thập.
0/63 rated · mastery 0%
Bài học
12 bài · ~144 phút đọc. Học theo thứ tự, rồi làm câu hỏi.- 01~17 phút· 5 câu
Deep-dive interview vận hành ra sao: chuỗi prove-it, pitch 2/5/15 phút và ranh giới ownership
Interviewer chọn một dòng trên CV rồi đào tới đáy: chuỗi What → Why → How → Broke → Numbers → Change, cách trình bày một dự án trong 2, 5 hoặc 15 phút, cách nói rõ phần của mình so với phần của team, và cách trả lời trung thực về gap (AWS, NestJS). Kèm một claim inventory checker chạy thật để tìm lỗ hổng trước buổi phỏng vấn.
- 02~11 phút· 4 câu
Vẽ kiến trúc dự án trên bảng: request trace, điểm nghẽn 10x và thiết kế lại
Cách vẽ kiến trúc P1 (e-commerce multi-tenant B2B2C) đúng mức zoom và kể theo một request thật; nói cụ thể tenant là ai, user là ai và một người mua ở hai retailer được mô hình hoá thế nào (schema chạy thật trên Postgres); ước lượng hộp nào hỏng đầu tiên khi traffic 10x; và trả lời hai câu judgment: làm lại từ đầu thì đổi gì, thêm quốc gia thứ ba với currency, thuế và data residency khác thì đổi gì.
- 03~12 phút· 5 câu
Bằng chứng: số liệu, cách đo, sự cố và bài học
Biến 'reduced DB load significantly' thành con số bảo vệ được: baseline, percentile thay vì mean, chuẩn hoá theo traffic và loại yếu tố gây nhiễu (script chạy thật trên dữ liệu minh hoạ cho thấy mean giảm 55% mà p95 không đổi, và một nửa mức giảm DB load đến từ traffic giảm). Cách chứng minh 'zero data loss', cách kể một incident xuyên BE/DB/FE, và cách trả lời câu 'bài học production quan trọng nhất'.
- 04~11 phút· 4 câuverify
P1: tenant isolation từ token đến SQL, test cross-tenant và noisy neighbor
Trả lời chuỗi câu hỏi về claim 'tenant-aware data access & authorization': tenant đi từ token qua middleware tới query, chỗ nào enforce, cái gì bắt được một raw query quên filter trước và sau production (RLS chạy thật trên Postgres 17: query quên WHERE vẫn chỉ thấy dữ liệu của tenant, INSERT sai tenant bị chặn, context không leak qua connection tái sử dụng), cách test cross-tenant bằng node:test, và cách xử lý một tenant lớn làm chậm mọi tenant khác.
- 05~11 phút· 4 câuverify
P1: JWT, Google OAuth và RBAC theo tenant
Trả lời claim 'JWT/OAuth incl. Google OAuth, RBAC, permission checks across APIs and resources': lifetime và nơi lưu access/refresh token, rotation và revoke, liên kết Google identity với account có sẵn mà không mở đường account takeover, role theo membership của từng tenant, permission check deny-by-default, và quyền mới lan tới user đang đăng nhập nhanh tới đâu. Demo chạy thật: verify JWT pin algorithm, token_version revoke ngay, route thiếu permission bị phát hiện, quy tắc account linking.
- 06~12 phút· 6 câuverify
P1: slow query, index và migration zero-downtime
Kể một slow query từ triệu chứng tới execution plan và thay đổi cụ thể (demo thật trên Postgres 17 với 1 triệu dòng: tenant nhỏ mất 151 ms vì planner quét ngược index created_at và loại 1 triệu dòng, composite index (tenant_id, status, created_at) đưa xuống 0,15 ms), chi phí và vòng đời của index, rồi migration expand/contract: rename cột trên bảng hot bằng cột mới + trigger dual-write + backfill theo batch có throttle + verify theo tenant, chạy thật với ghi đồng thời; cách chứng minh zero data loss và cách rollback.
- 07~11 phút· 5 câuverify
P1: Redis cho hot API, stampede, race khi invalidate và Redis down
Trả lời claim 'introduced Redis caching for hot APIs': chọn endpoint bằng dữ liệu, key luôn có tenant, cache-aside với TTL + jitter và invalidate sau commit. Ba demo chạy thật trên Redis 7.4 + ioredis 6: 500 miss đồng thời gây 500 lần gọi DB, lock SET NX PX còn 1; race delete-after-update để lại giá cũ trong cache và cách sửa bằng ghi có version qua Lua; Redis chết với cấu hình mặc định làm một request treo 73 giây, cấu hình đúng fallback sau 17 ms.
- 08~11 phút· 4 câuverify
P1: Checkout, sự cố charged-but-failed và Shift Management
Trả lời câu hỏi về hai feature P1 bạn own: Checkout chống double charge và oversell bằng idempotency key + conditional update (chạy thật trên Postgres 17: 30 khách tranh 10 sản phẩm cộng 2 lần double click, kết quả đúng 10 đơn, khách double click chỉ có 1 đơn), cách điều tra 'bị trừ tiền nhưng đơn failed'; và Shift Management với UTC + IANA zone của store, ca qua nửa đêm và DST (ca 01:30–03:30 đêm DST chỉ dài 1 giờ), chống chồng ca bằng exclusion constraint.
- 09~12 phút· 8 câuverify
P1: Data Enrichment và Indexer Service vào Elasticsearch
Trả lời hai claim pipeline của P1: Data Enrichment Service (adapter theo provider, validate ở biên, normalize, dedupe, quarantine và data circuit breaker khi provider đổi format, chạy thật: payload đổi tên field làm 100% record bị quarantine và batch bị chặn thay vì ghi đè catalog) và Indexer Service (DB → Elasticsearch bằng polling/event/CDC, document id ổn định, external version chống ghi cũ đè mới, tombstone cho delete, phát hiện drift theo tenant, reindex qua alias swap, và scale 10x).
- 10~13 phút· 10 câuverify
P2: monolith → microservices, Kafka, outbox và Memcached
Trả lời chuỗi câu hỏi về migration back-office: vì sao migrate, chọn boundary và service đầu tiên, strangler routing và verify parity, giữ DB write và Kafka event nhất quán (chạy thật trên Postgres 17: relay crash sau khi publish tạo 5 message trùng, consumer dedupe theo event id áp dụng đúng 8 lần), partition key và ordering, message format cũ làm consumer crash loop (tolerant reader + DLQ, chạy thật), Memcached TTL + refresh với lock add (chạy thật trên Memcached 1.6: 200 request sau soft expiry chỉ 1 lần gọi DB), và câu 'có chọn microservices lại không'.
- 11~11 phút· 6 câuverify
P3: banking React SPA, state, component library và hiệu năng
Trả lời các claim của P3: mô tả sản phẩm ở mức khái quát hoá, thứ gì thuộc Redux vs local state vs server state, component API của core library, cách tìm và sửa một màn hình re-render mỗi phím gõ (chạy thật với Redux Toolkit 2.13: selector trả mảng mới gây 13 lần re-render cho 13 phím, createSelector còn 0), một kết quả code splitting có số trước/sau (esbuild 0.28 + React 19.3: initial JS từ 139,5 KB xuống 69,0 KB gzip khi tách thư viện chart theo route), và custom hook có test (minor units không dùng float, chặn double submit).
- 12~13 phút· 6 câuverify
P4: Socket.IO real-time, CSV bottleneck và load test
Trả lời các claim của P4 (financial web apps real-time): real-time nghĩa là gì và vì sao Socket.IO thay vì SSE/polling, scale nhiều node và bù message bị lỡ (chạy thật Socket.IO 4.8: rớt mạng giữa chừng, connection state recovery giao lại đủ 6/6 message), CSV import bằng stream + batch (chạy thật: 1,5 triệu dòng, readFile + split đạt peak RSS 674 MB và chết ở heap 128 MB, stream giữ 89–103 MB; insert từng dòng 23,5 giây vs batch 1.000 dòng 0,24 giây cho 20.000 dòng), load test để tìm cái hỏng đầu tiên (autocannon: DB pool), và migrate class component sang hooks an toàn.
Tóm tắt & cheat sheet của track
TL;DR
- Phần deep-dive là nơi senior interview thắng hoặc thua: interviewer chọn một dòng trên CV và hỏi liên tục "what exactly did you do → why that way → what broke → what would you change → numbers?" tới khi chạm giới hạn hiểu biết của bạn.
- Mỗi claim trên CV phải có: một kiến trúc vẽ được trên bảng, một câu chuyện STAR, 2–3 con số thật, và một điều bạn sẽ làm khác.
- Nguyên tắc vàng: không bịa. Nếu không có số chính xác, nói ước lượng và cách bạn biết. Nếu phần đó do người khác làm, nói rõ phần của bạn.
- Bốn dự án: P1 e-commerce multi-tenant B2B2C (2024–2026, trọng tâm), P2 monolith → microservices back-office (2023–2024), P3 banking React SPA (2022–2023), P4 financial web apps real-time (2022).
- Khoảng trống đã biết (NestJS, AWS chưa dùng production; CV chưa có số liệu) → chuẩn bị câu trả lời trung thực từ trước.
Khái niệm cốt lõi — chuỗi prove-it
| Bước | Interviewer hỏi | Bạn cần có |
|---|---|---|
| What | "What exactly did you build/change?" | Phạm vi cụ thể, phần của bạn |
| Why | "Why that approach and not X?" | Ít nhất 1 phương án đã loại + lý do |
| How | "Walk me through the flow / schema / code" | Diagram, tên bảng/endpoint/key |
| Broke | "What went wrong?" | Một sự cố/bug thật và cách xử lý |
| Numbers | "How much? How did you measure?" | Số trước/sau + công cụ đo |
| Change | "What would you do differently?" | Một cải thiện thực tế, không phải "không có gì" |
P1 — Multi-tenant B2B2C e-commerce platform (2024–2026)
Nền tảng phục vụ retailer và khách hàng ở hai quốc gia; team quốc tế (PO, QA, Designer, Tech Lead), giao tiếp tiếng Anh, phân tán. Stack: Node, TS, Express, React, Next.js, relational DB (SQL Server), Redis, Elasticsearch.
Claims sẽ bị đào: (1) own feature end-to-end · (2) schema change, indexing, query optimization cho high-traffic API · (3) tenant-aware data access & authorization · (4) zero-downtime, zero-data-loss migration · (5) giới thiệu Redis caching cho hot API · (6) JWT/OAuth, Google OAuth, RBAC, permission check · (7) Checkout, Address Management, Shift Management · (8) Data Enrichment Service · (9) Indexer Service → Elasticsearch · (10) refactor legacy, xử lý prod issue BE/DB/FE · (11) AI-assisted workflow có human review.
STAR template (dùng cho mỗi claim)
- S: API/feature nào, traffic/tenant/dữ liệu cỡ nào, vấn đề gì.
- T: bạn được giao gì / tự nhận gì.
- A: các bước bạn làm, phương án đã loại, khó khăn giữa chừng.
- R: số trước/sau, tác động kinh doanh.
- Reflection: sẽ làm khác gì.
Numbers to collect (điền số liệu thật của bạn): số tenant/retailer · số user/đơn hàng/ngày · RPS của hot API · p95/p99 latency trước/sau cache và index · cache hit ratio, TTL · kích thước bảng khi migration · thời gian backfill · số lần migration zero-downtime · số sản phẩm được enrich/index, thời gian reindex · số provider · số role/permission · tỷ lệ lỗi checkout.
P2 — Enterprise back-office: monolith → Node/Express microservices (2023–2024)
Claims: decomposition thành service · Kafka async communication · caching + data transformation · backward compatibility trong incremental migration · Memcached TTL + refresh giảm tải DB đáng kể.
STAR template: S = monolith gặp vấn đề gì (deploy, scale, coupling) → T = phần bạn phụ trách (service/luồng nào) → A = cách chọn boundary, routing, event, cache, đảm bảo tương thích → R = số service/luồng migrate, DB load giảm bao nhiêu → Reflection.
Numbers to collect: số service bạn tham gia extract · thời gian migration · throughput Kafka (msg/s), số topic/partition · DB load trước/sau Memcached (QPS, CPU) · hit ratio, TTL · số incident do migration.
P3 — Banking React SPA (2022–2023)
Claims: reusable component architecture · memoization, code splitting, lazy loading, dynamic imports · business logic trong custom hooks · core component library · Redux Toolkit.
STAR template: S = vấn đề performance/maintainability cụ thể → T = phần bạn own → A = profile (React DevTools Profiler, bundle analyzer), thay đổi cụ thể → R = bundle size/render time trước/sau → Reflection.
Numbers to collect: bundle size initial trước/sau · số route/chunk · thời gian render của màn hình nặng · số component trong library, số màn hình dùng · LCP/TTI nếu có.
P4 — Financial web apps, real-time (2022)
Claims: reliability · Socket.IO real-time · React class components · unit + load testing · fix CSV processing bottleneck.
Numbers to collect: số connection đồng thời · kích thước CSV, thời gian xử lý trước/sau · memory peak trước/sau · công cụ load test, target RPS/concurrent user, cái gì hỏng đầu tiên.
Trade-offs — cách kể về quyết định
| Tình huống | Câu trả lời yếu | Câu trả lời senior |
|---|---|---|
| Không nhớ số | Bịa số chính xác | "Khoảng X theo dashboard lúc đó; đây là cách tôi đo" |
| Phần do người khác làm | Nhận là của mình | "Phần đó do teammate; tôi làm Y và tích hợp với nó" |
| Quyết định bạn không đồng ý | Chê quyết định | "Team chọn A vì ràng buộc Z; tôi đề xuất B; nhìn lại…" |
| Không biết câu trả lời | Nói vòng vo | "Tôi không chắc; tôi sẽ kiểm tra bằng…" |
| Thứ chưa dùng production | Nói như đã dùng | "Chưa dùng production; tôi đã POC và hiểu trade-off…" |
Pitfalls
- Không vẽ được kiến trúc của chính dự án mình.
- Không phân biệt được phần mình làm với phần team làm.
- "Significantly", "much faster" không có số; hoặc số không bảo vệ được.
- Không nhớ tên bảng/endpoint/key/topic — dấu hiệu làm hời hợt.
- Không có câu chuyện "cái gì đã hỏng" — interviewer nghi ngờ độ sâu.
- Tiết lộ thông tin mật của client (tên, số liệu nội bộ nhạy cảm) — nói ở mức khái quát hoá.
Cheat sheet
- Mỗi claim: 1 diagram · 1 STAR · 2–3 số · 1 phương án đã loại · 1 điều hỏng · 1 điều làm khác.
- Tenant: token → middleware → repository/query → cache key → search index — tenant ở mọi tầng.
- Migration: expand → dual-write/backfill theo batch → verify → switch read → contract; luôn có rollback.
- Cache: key có tenant, TTL + invalidation on write, stampede protection, fallback khi Redis down.
- Microservices: strangler fig, boundary theo domain, Kafka key theo entity, idempotent consumer, outbox, backward compat.
- Frontend: đo trước (Profiler, bundle analyzer), memo có lý do, split theo route.
- Real-time/CSV: Socket.IO scale bằng adapter + sticky session; CSV bằng stream + batch + backpressure.
References
- Martin Fowler — Strangler Fig Application ↗
- Martin Fowler — Parallel Change (expand/contract) ↗
- microservices.io — Transactional Outbox pattern ↗
- AWS SaaS Lens — Tenant isolation ↗
- Redis docs — Client-side caching and cache patterns ↗
- Elasticsearch — Aliases (zero-downtime reindex) ↗
- Socket.IO — Using multiple nodes ↗
- react.dev — lazy ↗
- Node.js — Backpressuring in Streams ↗
- Stripe — Idempotent requests ↗
Interview Questions
Showing 63 / 63
import { pipeline } from 'node:stream/promises';
import { parse } from 'csv-parse';
await pipeline(
fs.createReadStream(path),
parse({ columns: true }),
async function* (rows) {
let batch = [];
for await (const row of rows) {
batch.push(validate(row));
if (batch.length === 1000) { await repo.bulkInsert(batch); batch = []; }
}
if (batch.length) await repo.bulkInsert(batch);
},
);
function useTransferForm(accountId: string) {
const [amount, setAmount] = useState('');
const error = validateAmount(amount); // pure, unit-tested separately
const submit = useCallback(async () => {
if (error) return;
await api.transfer({ accountId, amount: toMinorUnits(amount) });
}, [accountId, amount, error]);
return { amount, setAmount, error, submit };
}