Dành cho level Senior đến Solution Architect. Câu hỏi theo kiểu scenario-based design - trả lời tư duy, không cần code dài.
1. Async/Await & Concurrency
Câu hỏi lý thuyết
- [Senior] Khi nào gọi
Task.Runtrong ASP.NET Core request pipeline là một bad practice? Hãy giải thích Thread Pool starvation diễn ra như thế nào. - [Senior] Phân biệt
TaskvàValueTask. Trong trường hợp đọc dữ liệu từ cache memory, bạn sẽ thiết kế method signature dùngTask<T>hayValueTask<T>, và tại sao? - [Architect] Bạn có một API endpoint nhận file upload lên đến 500MB và ghi vào disk. Nếu sử dụng
IFormFile.CopyToAsync, memory API có thể spike. Giải pháp của bạn là gì để tối ưu memory footprint và vẫn không block thread? - [Architect] Giải thích vấn đề "Captive Dependency" trong DI khi kết hợp giữa Singleton và Scoped service. Hậu quả là gì đối với một DbContext bị capture?
🎯 Use Case 1: Dashboard Aggregation API
Có một API GET /dashboard/{userId} cần lấy:
- Profile từ User Service (HTTP)
- 10 orders gần nhất từ Database (EF Core)
- Số thông báo chưa đọc từ Notification Service (HTTP)
- Điểm loyalty từ Loyalty Service (HTTP)
Trả lời theo góc nhìn design:
5. [Senior] Những phần nào có thể chạy song song? Có rủi ro gì nếu dùng Task.WhenAll bừa?
6. [Senior] Nếu DB dùng EF Core, bạn sẽ lưu ý gì khi chạy song song?
7. [Architect] Khi nào nên fail toàn bộ request, khi nào nên trả partial data (graceful degradation)?
8. [Architect] CancellationToken nên đi qua những đâu? Nếu Notification Service mất 8 giây, bạn xử lý thế nào?
🎯 Use Case 2: High-Throughput Background Processor
Bạn có một BackgroundService xử lý 5,000 messages/giây từ Azure Service Bus. Mỗi message cần:
- Deserialize JSON
- Validate business rules
- Ghi DB
- Publish event tiếp
- [Senior] Bạn sẽ dùng
Channel<T>hayBlockingCollection<T>? Giải thích tại saoChannel<T>là async-first. - [Architect] Nếu processing rate < incoming rate, bạn thiết kế backpressure thế nào? Khi nào nên reject message vs khi nào nên buffer?
⚠️ Anti-Patterns - Async/Concurrency
Những sai lầm thường gặp ở các dự án .NET thực tế
| # | Anti-Pattern | Tại sao nguy hiểm |
|---|---|---|
| 1 | Async naming, sync implementation - GetDataAsync() nhưng bên trong gọi .ToList() hoặc .Result | Fake async - vẫn block thread, misleading API contract |
| 2 | Bọc sync I/O bằng Task.Run - await Task.Run(() => File.ReadAllText(...)) | Tưởng là async, thật ra chỉ chuyển block sang thread pool thread khác |
| 3 | Controller async, Service sync - Controller dùng await, nhưng Service bên trong gọi sync DB | Bề ngoài hiện đại, ruột vẫn blocking. Thread vẫn bị block |
| 4 | Fire-and-forget trong request - _ = SendEmailAsync() không await | Mất lỗi, mất dữ liệu, mất kiểm soát. Request trả OK nhưng email chưa chắc gửi |
| 5 | Task.WhenAll với cùng DbContext - Song song query trên 1 DbContext instance | DbContext KHÔNG thread-safe. Bug ngẫu nhiên, data corruption |
| 6 | Không phân biệt CPU-bound vs I/O-bound - Dùng Task.Run cho HTTP call, dùng async cho tính toán nặng | Chọn sai primitive → ThreadPool starvation hoặc performance loss |
| 7 | ConfigureAwait(false) bừa bãi trong ASP.NET Core | ASP.NET Core không có SynchronizationContext, ConfigureAwait(false) vô tác dụng nhưng gây confuse |
| 8 | Deadlock bằng .Result / .Wait() - service.GetAsync().Result trong sync method | Classic deadlock trong ASP.NET (có SyncContext), thread starvation trong ASP.NET Core |
2. Memory Management & Performance
Câu hỏi lý thuyết
- [Senior] Trình bày vòng đời của Garbage Collector (Gen 0, Gen 1, Gen 2, LOH). Việc gọi
GC.Collect()thủ công thường có hại hơn là có lợi, tại sao? - [Senior]
stringtrong C# là immutable. Giải thích cơ chếstring.Intern()và khi nào tính năng này gây memory leak?
🎯 Use Case 3: Real-time Message Processor Memory Spike
Một background service xử lý 10,000 MQTT messages/giây. Mỗi xử lý cấp phát chuỗi (string concatenation) và byte array. App bị CPU spike và latency cao do GC chạy liên tục.
- [Senior] Bạn sẽ tối ưu thế nào với
ArrayPool<byte>,Span<T>, vàstring.Create()? - [Architect] Monitor bằng cách nào? Bạn sẽ dùng metrics gì để biết GC đang là bottleneck (GC Pause %, Gen2 Collections/sec)?
- [Architect] Quyết định chọn Server GC vs Workstation GC dựa vào tiêu chí gì?
🎯 Use Case 4: API Response Time Regression
Sau khi deploy version mới, API /api/products chậm hơn 3x (từ 50ms → 150ms). Không thay đổi DB query. BenchmarkDotNet local chạy bình thường.
- [Senior] Bạn sẽ diagnose bằng những tool nào? (dotnet-counters, dotnet-trace, Application Insights)
- [Architect] Kể 3 nguyên nhân thường gặp nhất mà "code không đổi nhưng chậm hơn" trong .NET.
⚠️ Anti-Patterns - Memory & Performance
| # | Anti-Pattern | Tại sao nguy hiểm |
|---|---|---|
| 1 | String concatenation trong loop - result += item.Name + "," | Mỗi lần tạo string mới → GC pressure, O(n²) memory |
| 2 | ToList() khi chỉ cần iterate - .ToList().Where(...) thay vì .Where(...) | Cấp phát List không cần thiết, đặc biệt tệ với large dataset |
| 3 | Boxing value types - object obj = 42; hoặc non-generic collections | Heap allocation cho mỗi value type → GC pressure |
| 4 | Không dispose IDisposable - quên using cho HttpClient, Stream, DbContext | Memory leak, connection leak, file handle exhaustion |
| 5 | LINQ materialization quá sớm - .ToList() rồi mới .Where().Select() | Load toàn bộ data vào memory rồi mới filter thay vì push filter xuống DB |
3. Entity Framework Core Khắc Nghiệt
Câu hỏi lý thuyết
- [Senior] N+1 query là gì và làm sao để phát hiện? So sánh
Include()(eager loading) vs Projection (Select()) vs Split Queries. - [Senior] Giải thích cách EF Core
ChangeTrackerhoạt động. Tại saoAsNoTracking()lại làm query lấy danh sách read-only nhanh hơn đáng kể?
🎯 Use Case 5: E-Commerce Product Listing Performance
API GET /api/products cần trả danh sách 50 products kèm:
- Category name (FK)
- Average rating (từ bảng Reviews - hàng triệu records)
- Thumbnail URL (từ bảng ProductImages)
- Inventory status (từ bảng Stock)
- [Senior] Viết query bằng EF Core mà KHÔNG gây N+1. Bạn chọn
IncludehaySelectprojection? Tại sao projection thường tốt hơn cho API responses? - [Senior] Khi nào Split Query (
AsSplitQuery()) lại TỆ hơn single query? (Hint: think about data consistency trong 2 roundtrips) - [Architect] Nếu bảng Products có 10 triệu records, bạn có dùng EF Core cho search/filter không? Khi nào nên bypass EF dùng Dapper hoặc Raw SQL?
🎯 Use Case 6: Bulk Data Import
Hệ thống cần import 500,000 records từ CSV vào SQL Server mỗi đêm. Team hiện dùng foreach + AddAsync + SaveChangesAsync mỗi record.
- [Senior] Tại sao cách hiện tại có thể mất hàng GIỜ? ChangeTracker đang làm gì ở 500K records?
- [Architect] Thiết kế pipeline import: dùng
EF Core ExecuteUpdate/ExecuteDelete,SqlBulkCopy, hay staging table + MERGE? Trade-offs?
⚠️ Anti-Patterns - EF Core
| # | Anti-Pattern | Tại sao nguy hiểm |
|---|---|---|
| 1 | Inject DbContext vào Singleton service | DbContext là Scoped. Captive dependency → stale data, memory leak |
| 2 | SaveChanges trong loop - foreach(item) { db.Add(item); await db.SaveChangesAsync(); } | 1 transaction per record = 500K roundtrips. Dùng AddRange + 1 SaveChanges |
| 3 | SELECT * khi chỉ cần 3 fields - .ToListAsync() trả entity đầy đủ | Load toàn bộ columns vào memory, ChangeTracker track không cần thiết |
| 4 | Lazy loading trong API - Navigation property tự load khi serialize JSON | N+1 ẩn: serializer truy cập property → trigger DB query cho MỖI entity |
| 5 | DbContext dùng chung giữa threads - Task.WhenAll(query1, query2) cùng DbContext | DbContext KHÔNG thread-safe. Kết quả random, exceptions khó debug |
| 6 | Quên AsNoTracking cho read-only queries | ChangeTracker snapshot mỗi entity → chậm và tốn memory với large datasets |
| 7 | OrderBy trên column không có index - .OrderByDescending(x => x.CreatedAt) mà CreatedAt chưa index | Full table scan trên triệu records. DBA sẽ giết bạn |
4. Middleware & DI
Câu hỏi lý thuyết
- [Senior] Trong ASP.NET Core, thứ tự đăng ký Middleware cực kỳ quan trọng. Hãy cho biết hậu quả nếu
UseAuthorization()được đặt trướcUseAuthentication(). - [Senior] DI Lifetime: Singleton, Scoped, Transient. Vẽ ra sơ đồ khi nào mỗi instance được tạo và dispose trong 1 HTTP request lifecycle.
🎯 Use Case 7: Multi-Tenant API
Bạn xây API cho SaaS product phục vụ 200 tenants, mỗi tenant có DB riêng (database-per-tenant).
- [Senior] Middleware nào sẽ extract
TenantIdtừ request? (Header, subdomain, JWT claim?) Architecture decision của bạn? - [Architect] Làm sao inject đúng connection string cho DbContext theo từng tenant? Bạn sẽ dùng
IDbContextFactory, factory pattern, hay custom scope? - [Architect] Rate limit per-tenant: tenant Free 100 req/min, tenant Pro 10,000 req/min. Thiết kế middleware + data store cho distributed environment?
🎯 Use Case 8: Custom Distributed Rate Limiter
Bạn cần thiết kế rate limit middleware cho API chạy 5 pods đằng sau Load Balancer.
- [Architect] In-memory counter sẽ fail thế nào? Tại sao mỗi pod count riêng = rate limit sai?
- [Architect] Bạn chọn Redis Sliding Window hay Fixed Window? Trade-offs? Có cần Lua script không?
⚠️ Anti-Patterns - DI & Middleware
| # | Anti-Pattern | Tại sao nguy hiểm |
|---|---|---|
| 1 | Service Locator - var service = serviceProvider.GetService<T>() thay vì constructor inject | Ẩn dependencies, khó test, violate DIP |
| 2 | God Service - 1 class inject 15+ dependencies | Vi phạm SRP. Class làm quá nhiều thứ. Cần tách nhỏ |
| 3 | Scoped trong Singleton - Singleton service inject Scoped service | Captive dependency: Scoped sống lâu bằng Singleton → stale data |
| 4 | Middleware order sai - CORS sau Authorization | Request bị reject trước khi CORS headers được set → browser block |
| 5 | Catch-all exception middleware nuốt lỗi - catch (Exception) { return 500; } không log | Mất thông tin debug. Production incident mù mờ |
👉 Xem đáp án chi tiết tại: Answers/ans-01-dotnet-csharp.md