37 câu hỏi System Design
Bộ câu hỏi ôn tập theo flow requirement → estimate → decision → trade-off → failure/metric → migration. Mỗi câu giữ nguyên ý nguồn BK, sau đó bổ sung khung trả lời phỏng vấn để luyện cách nói có numbers, failure/security/evolution và follow-up.
Framework và numbers
Luyện cách mở bài, lượng hóa scale/SLO và kết thúc một design có cấu trúc.
1. Mở đầu một đề mơ hồ thế nào?
Ý nguồn bắt buộcClarify critical journey, users, exclusions, scale, latency, availability, consistency, retention và security trước component.
Mở rộng để trả lời phỏng vấn
- Chốt 1–2 critical journeys và ai sử dụng chúng.
- Đặt scale bằng peak QPS/concurrent users, payload và data growth thay vì chỉ DAU.
- Ghi rõ latency/availability/consistency/retention/security cùng những gì out-of-scope.
Failure / trade-off / follow-up: Nếu interviewer chưa cho số, nêu assumption có thể thay đổi và tiếp tục; tránh đứng yên chờ dữ liệu hoàn hảo.
2. Vì sao cần estimate?
Ý nguồn bắt buộcChọn order of magnitude, loại bỏ over/under-design và xác định bottleneck; assumptions quan trọng hơn precision.
Mở rộng để trả lời phỏng vấn
- Tính order-of-magnitude cho traffic, storage, bandwidth và concurrency.
- Dùng estimate để tìm component có nguy cơ chạm limit trước.
- Nói rõ assumption nào nhạy nhất và metric nào sẽ validate sau khi chạy thật.
Failure / trade-off / follow-up: Sai số 2–3× thường ít nguy hiểm hơn việc bỏ sót peak, skew hoặc growth trend.
3. Average QPS che gì?
Ý nguồn bắt buộcPeak, flash crowd, diurnal pattern, burst duration, autoscaling lag và per-key skew.
Mở rộng để trả lời phỏng vấn
- Tách average, peak, burst duration, flash crowd và per-key skew.
- Kiểm tra autoscaling warm-up có nhanh hơn burst hay không.
- Đặt headroom, queue/backpressure hoặc load shedding cho sudden spike.
Failure / trade-off / follow-up: Một hệ thống chịu 10k QPS trung bình vẫn có thể fail ở 40k QPS trong 60 giây nếu capacity chỉ scale theo cửa sổ dài.
4. Latency gấp đôi ở cùng QPS?
Ý nguồn bắt buộcTheo Little's Law, in-flight concurrency xấp xỉ gấp đôi, tăng pressure lên threads/connections/memory.
Mở rộng để trả lời phỏng vấn
- Dùng Little's Law: concurrency ≈ throughput × latency.
- Nếu QPS giữ nguyên mà latency tăng 2× thì in-flight work xấp xỉ tăng 2×.
- Theo dõi thread/connection pool, memory, queueing time và timeout amplification.
Failure / trade-off / follow-up: Failure loop thường là latency tăng → concurrency tăng → resource saturation → latency tiếp tục tăng.
5. SLO availability cần định nghĩa gì?
Ý nguồn bắt buộcSuccessful event, eligible traffic, scope/journey, measurement window và partial degradation.
Mở rộng để trả lời phỏng vấn
- Định nghĩa successful event và eligible traffic cho đúng user journey.
- Chọn measurement window và latency threshold đi kèm availability nếu cần.
- Tách partial degradation khỏi hard failure để SLI phản ánh trải nghiệm người dùng.
Failure / trade-off / follow-up: Follow-up: error budget dùng để quyết định mức risk deploy/change nào chấp nhận được.
6. RPO khác RTO?
Ý nguồn bắt buộcRPO là data loss tối đa; RTO là thời gian phục hồi. Design backup/replication/failover phải chứng minh cả hai.
Mở rộng để trả lời phỏng vấn
- RPO trả lời 'có thể mất tối đa bao nhiêu dữ liệu?'; RTO trả lời 'phục hồi trong bao lâu?'.
- Map RPO vào replication/backup frequency; map RTO vào detection, restore/failover và validation time.
- Luôn có restore/failover drill để chứng minh, không chỉ tồn tại backup.
Failure / trade-off / follow-up: Ví dụ: backup mỗi 24h không thể tự chứng minh RPO 5 phút.
7. Chọn deep dive thế nào?
Ý nguồn bắt buộcPhần rủi ro/khác biệt nhất theo requirement và estimate, không phần quen thuộc nhất với ứng viên.
Mở rộng để trả lời phỏng vấn
- Chọn phần có risk hoặc uncertainty lớn nhất so với requirement.
- Ưu tiên hot path, data model/consistency, partitioning, failover hoặc security nếu đó là differentiator.
- Nêu vì sao phần này đáng dành thời gian thay vì đi sâu mọi component.
Failure / trade-off / follow-up: Deep dive tốt biến requirement thành decision + trade-off + failure behavior + metric.
8. Diagram tốt cần gì?
Ý nguồn bắt buộcBoundaries, protocol/arrows, read/write flow, source of truth, sync/async và failure domains.
Mở rộng để trả lời phỏng vấn
- Vẽ trust/system boundaries, storage/source of truth và protocol chính.
- Phân biệt read/write, sync/async và fan-out/fan-in.
- Đánh dấu failure domain hoặc state transition quan trọng.
Failure / trade-off / follow-up: Diagram nên trả lời 'data đi đâu, ai sở hữu state, fail ở đâu' chứ không chỉ liệt kê boxes.
9. Khi nào chưa cần microservices?
Ý nguồn bắt buộcKhi một deploy/DB đáp ứng scale, team nhỏ và autonomy chưa bù network/operations/consistency cost.
Mở rộng để trả lời phỏng vấn
- Bắt đầu bằng modular monolith nếu một deploy và database vẫn đáp ứng scale/ownership.
- Chỉ tách service khi có clear boundary: scaling, autonomy, reliability hoặc compliance.
- Tính thêm network hops, distributed transaction, observability và on-call burden.
Failure / trade-off / follow-up: Evolution trigger: team/domain độc lập, hotspot scaling khác biệt, blast-radius hoặc release coupling trở thành pain đo được.
10. Recap cuối buổi gồm gì?
Ý nguồn bắt buộcScope, key decisions, largest risk, consistency/failure behavior, cost và next evolution trigger.
Mở rộng để trả lời phỏng vấn
- Nhắc lại scope và assumption lớn nhất.
- Tóm tắt 2–3 decisions quan trọng cùng trade-off.
- Nêu failure behavior, largest risk, cost driver và trigger cho phiên bản tiếp theo.
Failure / trade-off / follow-up: Recap giúp interviewer thấy bạn biết kết thúc design thay vì tiếp tục thêm component vô hạn.
Components và data
Tập trung partitioning, cache, queue, replication, backup, identifier và lựa chọn datastore.
11. Chọn partition key?
Ý nguồn bắt buộcHỗ trợ dominant access/order, cardinality cao, phân phối đều; phân tích hot tenant/key và cross-shard query.
Mở rộng để trả lời phỏng vấn
- Chọn key theo dominant access/order scope và cardinality.
- Ước lượng per-partition QPS/data để phát hiện hot key trước.
- Nói rõ cross-shard query/transaction và rebalance strategy.
Failure / trade-off / follow-up: Security/evolution: tenant key có thể giúp isolation nhưng heavy tenant cần isolate hoặc sub-shard.
12. Tránh hot partition?
Ý nguồn bắt buộcSalt/bucket, isolate heavy tenant, adaptive split, cache/locality; ghi aggregation/order trade-off.
Mở rộng để trả lời phỏng vấn
- Đo skew theo key/tenant trước khi sửa.
- Salt/bucket khi write hotspot; isolate heavy tenant khi noisy-neighbor rõ ràng.
- Adaptive split hoặc cache locality khi phù hợp, nhưng mô tả cost của aggregation/order.
Failure / trade-off / follow-up: Follow-up: salting giảm hotspot nhưng làm query theo logical key phải fan-out/merge.
13. Cache invalidation thế nào?
Ý nguồn bắt buộcSource of truth ở DB; invalidate/update sau commit, TTL/version, tolerate race và degraded miss path.
Mở rộng để trả lời phỏng vấn
- DB/source là source of truth; cache chỉ là derived state.
- Invalidate/update sau commit, dùng TTL và version để tránh stale overwrite.
- Thiết kế miss/degraded path khi cache down để origin không bị thundering herd.
Failure / trade-off / follow-up: Security: cache key phải chứa các dimension ảnh hưởng authorization để tránh cross-tenant/user data leak.
14. Cache stampede?
Ý nguồn bắt buộcSingle-flight, jitter TTL, stale-while-revalidate, early refresh và bounded concurrency tới origin.
Mở rộng để trả lời phỏng vấn
- Single-flight/coalescing để một key chỉ có một refresh đang chạy.
- Jitter TTL, early refresh hoặc stale-while-revalidate để tránh expiry đồng loạt.
- Bound concurrency tới origin và có circuit/load shedding khi origin chậm.
Failure / trade-off / follow-up: Metric: cache hit rate chưa đủ; cần origin QPS, refresh concurrency và p95/p99 miss latency.
15. Queue size burst?
Ý nguồn bắt buộc(arrival−service rate)×burst duration, cộng payload/retention/headroom; kiểm tra drain time và deadline.
Mở rộng để trả lời phỏng vấn
- Backlog messages ≈ (arrival rate − service rate) × burst duration khi arrival > service.
- Nhân payload để ra bytes, rồi cộng retention, replication và headroom.
- Tính drain time sau burst và so với processing deadline/SLO.
Failure / trade-off / follow-up: Queue hấp thụ burst nhưng không tạo downstream capacity; backlog vô hạn chỉ trì hoãn outage.
16. Replay event an toàn?
Ý nguồn bắt buộcIdempotent consumer, schema compatibility, filter/dry-run/rate limit/audit và bảo vệ downstream.
Mở rộng để trả lời phỏng vấn
- Consumer phải idempotent và schema/version còn tương thích.
- Replay bằng filter/dry-run/canary/rate limit, có audit và checkpoint.
- Bảo vệ downstream bằng concurrency cap và theo dõi lag/error before ramp-up.
Failure / trade-off / follow-up: Security: replay tool là privileged operation; cần authorization, audit và phạm vi dữ liệu rõ.
17. Read replica có caveat?
Ý nguồn bắt buộcLag, stale/read-your-writes, failover, connection routing và replica saturation.
Mở rộng để trả lời phỏng vấn
- Xác định tolerated staleness và journey cần read-your-writes.
- Route session-sensitive read về primary hoặc dùng version/token khi cần.
- Theo dõi replication lag, replica saturation và failover/routing behavior.
Failure / trade-off / follow-up: Replica tăng read capacity/availability nhưng không miễn phí consistency.
18. Backup khác replication?
Ý nguồn bắt buộcReplication availability/read scale nhưng sao chép lỗi/xóa; backup cho point-in-time/disaster và phải restore-test.
Mở rộng để trả lời phỏng vấn
- Replication phục vụ availability/read scale nhưng thường replicate cả delete/corruption.
- Backup/PITR phục vụ recovery theo thời điểm và disaster độc lập hơn.
- Định kỳ restore-test và đo thời gian để chứng minh RTO.
Failure / trade-off / follow-up: Backup chưa restore-test chỉ là giả định về recovery.
19. ID strategy trade-off?
Ý nguồn bắt buộcCentral sequence đơn giản/locality; UUID phân tán nhưng index/size; time-sortable có clock/privacy caveat.
Mở rộng để trả lời phỏng vấn
- Sequence: compact/locality tốt nhưng cần coordination hoặc allocation strategy.
- UUID: tạo phân tán, nhưng index/storage lớn hơn và random UUID có thể kém locality.
- Time-sortable ID cải thiện locality/order nhưng phải xem clock, predictability và privacy metadata.
Failure / trade-off / follow-up: Chọn ID theo uniqueness scope, write distribution, index behavior và exposure ra public API.
20. SQL hay NoSQL?
Ý nguồn bắt buộcChọn theo invariant, transaction, access patterns, scale, consistency và team operations; không theo slogan.
Mở rộng để trả lời phỏng vấn
- Bắt đầu từ invariant/transaction và access patterns.
- Ước lượng scale, consistency, query flexibility và operational skill của team.
- Nêu rõ điều gì khó hơn nếu chọn phương án còn lại.
Failure / trade-off / follow-up: Có thể dùng polyglot persistence, nhưng chỉ khi benefit vượt complexity của nhiều source/consistency model.
Failure, security và evolution
Đưa failure mode, security, cost và migration vào design thay vì chỉ mô tả happy path.
21. Graceful degradation?
Ý nguồn bắt buộcGiữ critical journey, bỏ/đánh dấu optional data, bounded fallback và SLI user-visible.
Mở rộng để trả lời phỏng vấn
- Xác định critical journey và optional dependencies/features.
- Khi dependency fail, trả partial/stale/bounded fallback thay vì treo toàn request.
- Đo SLI user-visible cho degraded mode và đặt thời gian/quality bound.
Failure / trade-off / follow-up: Security không được degrade theo kiểu bypass auth/authorization chỉ để giữ availability.
22. Regional failover gồm bước nào?
Ý nguồn bắt buộcDetection, fencing writer cũ, data state/RPO, traffic shift, capacity warm-up, validation và failback.
Mở rộng để trả lời phỏng vấn
- Detect sự cố và fence writer cũ trước khi promote để tránh split-brain.
- Xác nhận data state/RPO, warm capacity và shift traffic có kiểm soát.
- Validate correctness/SLI rồi mới failback; failback cũng cần plan.
Failure / trade-off / follow-up: Drill phải đo detection + decision + traffic shift + validation, không chỉ DNS switch.
23. Multi-region active-active khi nào?
Ý nguồn bắt buộcKhi latency/availability/data residency justify conflict/routing/consistency/operations cost.
Mở rộng để trả lời phỏng vấn
- Chỉ justify khi latency, availability hoặc residency yêu cầu rõ.
- Định nghĩa routing và conflict/consistency model cho write concurrent.
- Tính thêm data transfer, observability, deployment, incident và operational burden.
Failure / trade-off / follow-up: Active-active là trade-off consistency/complexity, không phải default nâng cấp của active-passive.
24. Zero-downtime schema migration?
Ý nguồn bắt buộcExpand compatible, mixed-version deploy, backfill/checkpoint, switch, verify rồi contract.
Mở rộng để trả lời phỏng vấn
- Expand: thêm schema/API tương thích với version cũ.
- Deploy mixed-version, backfill có checkpoint/rate-limit và verify.
- Switch read/write path, quan sát, rồi contract phần cũ sau khi rollback window đóng.
Failure / trade-off / follow-up: Không combine destructive schema change và app cutover vào một bước không rollback được.
25. Chống abuse URL shortener?
Ý nguồn bắt buộcAuth/quota, malicious URL scanning, block/report, redirect reputation, enumeration resistance và audit.
Mở rộng để trả lời phỏng vấn
- Auth/quota/rate limit theo user/IP/tenant và risk score.
- Scan malicious destination, block/report và quản lý redirect reputation.
- Dùng ID khó enumerate khi privacy yêu cầu và audit admin/moderation actions.
Failure / trade-off / follow-up: Threat model cần xem phishing, malware, enumeration, brute force và abuse automation.
26. SLI checkout degraded?
Ý nguồn bắt buộcTỷ lệ checkout critical path hoàn tất trong latency target; recommendation failure không tính như payment failure.
Mở rộng để trả lời phỏng vấn
- Định nghĩa critical checkout path và latency target.
- Chỉ tính dependency optional như recommendation là degraded feature, không biến thành payment failure.
- Tách success rate, latency và correctness/business reconciliation nếu cần.
Failure / trade-off / follow-up: SLI nên đại diện outcome người dùng thay vì health từng microservice.
27. Autoscaling đến muộn vì sao?
Ý nguồn bắt buộcDetection/window, provisioning/warm-up và downstream cap; sudden burst cần headroom, queue hoặc shedding.
Mở rộng để trả lời phỏng vấn
- Đo detection/window lag, provisioning/warm-up và time-to-ready.
- Kiểm tra downstream hard cap; scale app không giúp nếu DB đã saturate.
- Dùng headroom, queue, admission control hoặc shedding cho burst nhanh hơn autoscaling.
Failure / trade-off / follow-up: Metric scaling nên liên hệ load driver thực tế; CPU không phải lúc nào phản ánh queue/IO bottleneck.
28. 10x traffic thay đổi gì?
Ý nguồn bắt buộcXác định reads/writes/data/regions tăng gì, component chạm limit đầu tiên, metric và migration targeted.
Mở rộng để trả lời phỏng vấn
- Xác định 10× là reads, writes, storage, tenants hay regions — không giả định tất cả cùng tăng.
- Tìm limit đầu tiên bằng per-component capacity/metric.
- Thực hiện migration targeted theo bottleneck thay vì redesign toàn hệ thống.
Failure / trade-off / follow-up: Evolution tốt có trigger đo được: partition size, p99, replica lag, cost/user hoặc deploy frequency.
29. Kiểm soát cost?
Ý nguồn bắt buộcRight-size, tier/retention, cache/CDN/compression, managed-vs-self-host TCO, egress và SLO-tiered workloads.
Mở rộng để trả lời phỏng vấn
- Right-size theo utilization và peak headroom; tier data theo access/retention.
- Cache/CDN/compression để giảm compute/egress khi economics phù hợp.
- So managed vs self-host bằng TCO gồm on-call, upgrades, backup, incidents và staffing.
Failure / trade-off / follow-up: Đừng tối ưu cost bằng cách phá SLO; dùng workload tiering và budget/guardrail theo business criticality.
30. Component nào hoãn ở MVP?
Ý nguồn bắt buộcBất kỳ component không giải requirement hiện tại; ghi trigger rõ để thêm sau, thường multi-region, microservices hoặc stream platform.
Mở rộng để trả lời phỏng vấn
- Mỗi component phải map vào requirement hiện tại.
- Hoãn multi-region/microservices/stream platform nếu chưa có trigger cụ thể.
- Ghi evolution trigger để chứng minh bạn không bỏ quên, chỉ defer có chủ đích.
Failure / trade-off / follow-up: MVP design tốt tối thiểu về complexity nhưng vẫn có observability, backup, security baseline và migration path.
Câu hỏi tổng hợp bổ sung
Câu tổng hợp để nối workload shape với data path, async/sync và bottleneck metrics.
31. Thiết kế hệ thống write-heavy hoặc read-heavy khác nhau thế nào?
Ý nguồn bắt buộcBắt đầu bằng tỷ lệ/peak read-write, payload, access pattern, consistency và latency SLO. Read-heavy thường tận dụng cache/CDN, read replica, precomputed view và keyset pagination; phải nêu invalidation, TTL, stampede và stale/read-your-writes policy. Write-heavy ưu tiên partition key đều, batching/append log, idempotency, backpressure và giảm secondary index; message queue giúp absorb burst nhưng không tạo thêm downstream capacity. Dùng sync khi cần kết quả tức thời, async khi chấp nhận pending/eventual state; luôn mô tả source of truth, overload behavior, replay/reconciliation và metric chứng minh bottleneck.
Mở rộng để trả lời phỏng vấn
- Estimate read/write peak, payload, access pattern, latency và consistency trước.
- Read-heavy: cache/CDN/replica/precompute; giải invalidation, TTL, stampede và stale policy.
- Write-heavy: even partitioning, batching/append log, idempotency, backpressure, ít secondary index; queue chỉ absorb burst.
- Sync cho outcome tức thời; async cho pending/eventual state; luôn có source of truth, replay/reconciliation và bottleneck metrics.
Failure / trade-off / follow-up: Follow-up nên so p99 read latency, write amplification, replication lag, cache miss amplification và queue drain time.
Follow-up theo tình huống
Các follow-up thường dùng để kiểm tra độ sâu về cache, queue, consistency và backpressure.
32. Khi nào chọn cache-aside, write-through hoặc write-behind?
Ý nguồn bắt buộcCache-aside đơn giản và source of truth ở DB nhưng miss/stale race cần xử lý. Write-through cập nhật cache cùng write path, đọc mới nhanh hơn nhưng cache có thể chứa dữ liệu ít dùng và dual-write failure cần semantics rõ. Write-behind gom write tăng throughput nhưng tăng rủi ro mất dữ liệu, reorder và consistency; chỉ dùng khi durability/recovery cho phép.
Mở rộng để trả lời phỏng vấn
- Cache-aside: app đọc DB khi miss, phù hợp phổ biến nhưng cần chống stale/race.
- Write-through: write path cập nhật cache, read mới nhanh nhưng dual-write semantics phải rõ.
- Write-behind: batch async tăng throughput nhưng thêm risk mất/reorder và recovery complexity.
Failure / trade-off / follow-up: Chọn theo durability, freshness SLO, write/read ratio và khả năng rebuild cache.
33. Thiết kế cache key, TTL và invalidation từ đâu?
Ý nguồn bắt buộcCache key phải bao gồm version, tenant, locale/filter và authorization-sensitive dimensions cần thiết. TTL dựa freshness SLO, update frequency và cost khi miss; thêm jitter để tránh expiry đồng loạt. Invalidate/update sau DB commit, chống stale overwrite bằng version và thiết kế degraded path khi cache down.
Mở rộng để trả lời phỏng vấn
- Key gồm version + tenant + locale/filter + authorization-sensitive dimensions cần thiết.
- TTL dựa freshness SLO, update frequency và miss cost; thêm jitter.
- Invalidate/update sau commit, dùng version/compare-and-set để tránh stale overwrite.
Failure / trade-off / follow-up: Khi cache down, degraded path phải có bounded concurrency để không đánh sập source.
34. Message queue tạo backpressure thế nào khi consumer chậm?
Ý nguồn bắt buộcGiới hạn producer rate/in-flight, consumer concurrency/prefetch và queue/retention; đo lag age, depth, drain time và downstream saturation. Khi budget đầy, reject/throttle/spill theo priority thay vì queue vô hạn. Autoscale chỉ giúp nếu bottleneck không nằm ở database hoặc dependency có capacity cố định.
Mở rộng để trả lời phỏng vấn
- Bound producer rate/in-flight và consumer concurrency/prefetch.
- Đo lag age, depth, drain time và downstream saturation — age thường gần user impact hơn depth.
- Khi budget đầy, reject/throttle/spill theo priority; không để queue vô hạn.
Failure / trade-off / follow-up: Autoscale consumer chỉ có ích nếu dependency phía sau còn capacity.
35. Làm sao giữ ordering và tránh duplicate side effect qua queue?
Ý nguồn bắt buộcPartition theo aggregate/order key để giữ thứ tự trong scope, không hứa global order nếu không cần. Consumer lưu idempotency/inbox state atomically với business effect hoặc dùng unique constraint/state transition. Broker exactly-once không tự bao phủ external database/API; cần retry, replay và reconciliation.
Mở rộng để trả lời phỏng vấn
- Partition theo aggregate/order key; tránh hứa global order nếu không cần.
- Ghi idempotency/inbox state atomically với business effect hoặc dùng unique constraint/state transition.
- Retry/replay/reconciliation cho unknown outcomes; broker exactly-once không tự cover external DB/API.
Failure / trade-off / follow-up: Follow-up: nêu scope ordering và thời gian giữ idempotency key/dedup state.
36. Thiết kế write-heavy nên giảm áp lực ở đâu trước?
Ý nguồn bắt buộcChọn partition key phân phối đều, tránh secondary index không phục vụ query, batch/append khi semantics cho phép và tách hot tenant/key. Queue absorb burst, idempotency bảo vệ retry, backpressure bảo vệ database. Theo dõi write latency, WAL/log rate, lock/contention, replication lag, partition skew và storage growth.
Mở rộng để trả lời phỏng vấn
- Even partitioning và isolate hot tenant/key.
- Giảm secondary index không phục vụ query; batch/append khi semantics cho phép.
- Queue absorb burst + idempotency + backpressure bảo vệ DB.
Failure / trade-off / follow-up: Metrics: write p95/p99, WAL/log rate, lock contention, replication lag, partition skew và storage growth.
37. Read-heavy dùng replica và precomputed view có consistency trade-off gì?
Ý nguồn bắt buộcReplica/cache/materialized view giảm tải source nhưng có lag và stale data. Xác định journey nào cần read-your-writes, route các read đó về primary hoặc dùng version/session token; các read khác chấp nhận bounded staleness. Projection phải rebuild/reconcile được và cache miss path không được làm source quá tải.
Mở rộng để trả lời phỏng vấn
- Replica/cache/materialized view giảm load nhưng tạo lag/staleness.
- Journey cần read-your-writes route primary hoặc dùng version/session token; phần khác chấp nhận bounded staleness.
- Projection phải rebuild/reconcile được và cache miss path phải bảo vệ source.
Failure / trade-off / follow-up: Nêu freshness SLO và metric lag cụ thể thay vì nói chung 'eventual consistency'.
Coverage: 37/37 câu hỏi · 5/5 nhóm · không dùng JavaScript hoặc dependency ngoài.