Skip to content
DevRecall
← Roadmap
99

Project Deep-Dive

drafted

Interviewer đà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.
  1. 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.

  2. 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ì.

  3. 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'.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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. 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~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. 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ướcInterviewer hỏiBạ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ốngCâu trả lời yếuCâ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àmNhậ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ờiNói vòng vo"Tôi không chắc; tôi sẽ kiểm tra bằng…"
Thứ chưa dùng productionNó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.

Interview Questions

Showing 63 / 63

// pseudo: repository tenant-aware
class OrderRepo {
  constructor(private ctx: { tenantId: string }) {}
  findById(id: string) {
    return db.order.findFirst({ where: { id, tenantId: this.ctx.tenantId } });
  }
}
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 };
}