10 câu hỏi có thể gặp khi phỏng vấn
Chọn theo kỹ năng và cấp của tin (Golang, SQL & Database, Cloud (AWS, Azure, GCP)). Gợi ý trả lời do Drama Công Ty biên soạn để bạn ôn nhanh, không phải đề của công ty.
01Generics trong Go dùng thế nào? Khi nào nên dùng generics thay vì interface?Golang · Senior
Gợi ý trả lời
Ôn thêm câu hỏi Golang →Từ Go 1.18, hàm và kiểu có thể nhận type parameter với ràng buộc (constraint):
func Map[T, U any](in []T, f func(T) U) []U { out := make([]U, 0, len(in)) for _, v := range in { out = append(out, f(v)) } return out }- Constraint có thể là
any,comparable,cmp.Ordered, hoặc interface tự định nghĩa chứa tập kiểu (~int | ~float64). - Thư viện chuẩn đã có các package generic như
slicesvàmaps.
Khi nào dùng:
- Generics: cấu trúc dữ liệu và hàm tiện ích làm việc giống hệt nhau với nhiều kiểu (cache, set, hàm lọc, phân trang).
- Interface: khi cần hành vi khác nhau cho từng kiểu (lưu vào DB hay vào file, nhiều cổng thanh toán).
Hay sai: generic hoá mọi thứ ngay từ đầu. Code Go chuộng đơn giản; hãy viết cụ thể trước, khi có hai ba chỗ lặp lại thật sự thì mới trích generic.
- Constraint có thể là
02Một service Go có độ trễ cao và tốn nhiều bộ nhớ. Bạn profile và tối ưu thế nào?Golang · Senior
Gợi ý trả lời
Ôn thêm câu hỏi Golang →Đo bằng pprof trước khi sửa. Import
net/http/pprof(đặt ở cổng nội bộ) rồi dùnggo tool pprofđể xem CPU profile, heap, allocs, goroutine và block/mutex profile.Các hướng tối ưu thường gặp:
- Giảm cấp phát (allocation): cấp phát nhiều làm GC chạy nhiều. Cấp phát trước
capcho slice và map, tránh chuyển đổi[]byte/stringthừa, dùngstrings.Builder. - Escape analysis:
go build -gcflags=-mcho biết biến nào bị đẩy lên heap. sync.Poolđể tái sử dụng buffer ở đường xử lý nóng.- Tinh chỉnh GC:
GOMEMLIMITđặt giới hạn bộ nhớ mềm (hữu ích trong container),GOGCđổi tần suất GC. - Viết benchmark (
go test -bench . -benchmem) để so sánh trước và sau khi sửa.
Đừng quên ngoài code: truy vấn DB chậm, connection pool quá nhỏ, gọi API ngoài không timeout.
Người phỏng vấn muốn nghe: bạn đọc được flame graph và dựa vào số liệu, không tối ưu theo cảm giác.
- Giảm cấp phát (allocation): cấp phát nhiều làm GC chạy nhiều. Cấp phát trước
03Cần xử lý 10.000 job (gọi API ngoài) với tối đa 20 job chạy cùng lúc, dừng sớm nếu có lỗi. Bạn thiết kế thế nào?Golang · Senior
Gợi ý trả lời
Ôn thêm câu hỏi Golang →Đây là bài worker pool có giới hạn concurrency. Cách gọn nhất là
errgroup(packagegolang.org/x/sync/errgroup):g, ctx := errgroup.WithContext(ctx) g.SetLimit(20) for _, job := range jobs { g.Go(func() error { return process(ctx, job) }) } if err := g.Wait(); err != nil { return err }SetLimitkhiếng.Gochặn lại khi đã đủ 20 goroutine đang chạy.- Lỗi đầu tiên sẽ huỷ
ctx; các job khác phải tôn trọngctx(truyền vào HTTP request) để dừng sớm.
Cách cổ điển: tạo 20 worker đọc từ một channel job, gửi kết quả qua channel khác, đóng channel đúng thứ tự.
Cần nghĩ thêm: rate limit phía API ngoài (
golang.org/x/time/rate), retry có backoff, có cần dừng hết khi một job lỗi hay chỉ ghi lại lỗi rồi tiếp tục.Hay sai: tạo 10.000 goroutine cùng lúc gọi API ngoài, làm cạn connection và bị đối tác chặn.
04Bảng có hàng trăm triệu dòng, query ngày càng chậm. Bạn dùng partitioning, sharding hay read replica?SQL & Database · Senior
Gợi ý trả lời
Ôn thêm câu hỏi SQL & Database →Mỗi kỹ thuật giải một vấn đề khác nhau, nên cần xác định điểm nghẽn là đọc, ghi hay kích thước dữ liệu.
- Read replica: giảm tải đọc. Phải chấp nhận replication lag; luồng vừa ghi xong đọc lại nên đọc từ primary.
- Partitioning (trong một DB): chia bảng theo khoảng thời gian hoặc theo key. Query có điều kiện trên partition key chỉ quét phần liên quan, xoá dữ liệu cũ bằng
DROP PARTITIONrất nhanh. - Sharding (nhiều DB): khi một máy không chịu nổi lượng ghi hoặc dung lượng. Đổi lại phải chọn shard key cẩn thận, query xuyên shard và transaction phân tán rất khó.
Thứ tự hợp lý: tối ưu query và index, archive dữ liệu cũ, thêm replica và cache, partition, cuối cùng mới shard.
Hay sai: chọn shard key gây hot spot, vd. theo ngày tạo khiến mọi lượt ghi dồn vào một shard.
05Làm sao migrate schema (thêm cột, đổi kiểu, thêm index) trên bảng lớn mà không downtime?SQL & Database · Senior
Gợi ý trả lời
Ôn thêm câu hỏi SQL & Database →Chia thay đổi thành nhiều bước tương thích ngược để phiên bản code cũ và mới chạy song song được (expand rồi contract).
- Expand: thêm cột mới cho phép
NULLhoặc có default; tránh câu lệnh viết lại cả bảng. - Code mới ghi vào cả cột cũ và mới.
- Backfill dữ liệu cũ theo từng lô nhỏ, có nghỉ giữa các lô để không gây lag replica.
- Chuyển code sang đọc cột mới, kiểm tra kỹ.
- Contract: bỏ cột cũ ở một lần deploy sau.
Index trên bảng lớn: PostgreSQL dùng
CREATE INDEX CONCURRENTLY; MySQL dùng online DDL hoặc công cụ như gh-ost, pt-online-schema-change.Người phỏng vấn muốn nghe: bạn biết DDL có thể giữ lock khiến mọi query xếp hàng, nên đặt
lock_timeoutvà luôn có kế hoạch rollback.- Expand: thêm cột mới cho phép
06Multi-cloud có đáng không? Bạn tư vấn thế nào khi sếp muốn chạy song song AWS và Azure?Cloud (AWS, Azure, GCP) · Senior
Gợi ý trả lời
Ôn thêm câu hỏi Cloud (AWS, Azure, GCP) →Multi-cloud có lý do chính đáng nhưng chi phí vận hành rất lớn; cần hỏi rõ mục tiêu trước khi đồng ý.
Lý do hợp lý:
- Yêu cầu pháp lý hoặc khách hàng chỉ định nhà cung cấp.
- Dùng dịch vụ đặc thù tốt nhất của từng bên.
- Công ty sáp nhập, mỗi bên đã dùng một cloud.
Cái giá:
- Đội phải giỏi hai nền tảng, IAM và mạng khác nhau.
- Chi phí truyền dữ liệu giữa cloud.
- Chỉ dùng mẫu số chung, mất lợi thế dịch vụ managed.
Giảm phụ thuộc thường chỉ cần container, Terraform và tránh API độc quyền ở phần lõi.
Người phỏng vấn muốn nghe: biết hỏi "để giải quyết vấn đề gì", không trả lời có/không ngay.
07Channel buffered và unbuffered khác nhau thế nào?
selectdùng để làm gì? Khi nào xảy ra deadlock?GolangGợi ý trả lời
Ôn thêm câu hỏi Golang →- Unbuffered: gửi bị chặn đến khi có bên nhận (và ngược lại). Đây là điểm đồng bộ giữa hai goroutine.
- Buffered (
make(chan T, n)): gửi chỉ bị chặn khi buffer đầy, nhận bị chặn khi buffer rỗng.
selectchờ trên nhiều channel cùng lúc, thường dùng cho timeout hoặc huỷ:select { case res := <-results: handle(res) case <-ctx.Done(): return ctx.Err() }Quy tắc: chỉ bên gửi đóng channel; gửi vào channel đã đóng gây panic; nhận từ channel đã đóng trả giá trị zero ngay lập tức (
v, ok := <-chđể phân biệt).for range chdừng khi channel đóng.Deadlock xảy ra khi mọi goroutine đều đang chờ nhau, ví dụ gửi vào channel unbuffered mà không có ai nhận. Runtime báo "all goroutines are asleep" nếu toàn bộ chương trình bị kẹt; kẹt một phần thì không báo gì và thành goroutine leak.
08Map trong Go có an toàn khi dùng đồng thời không? Bạn phát hiện và xử lý race condition thế nào?Golang
Gợi ý trả lời
Ôn thêm câu hỏi Golang →Không. Đọc và ghi map đồng thời có thể làm runtime dừng chương trình với lỗi "concurrent map read and map write". Các biến dùng chung khác cũng có data race nhưng thường im lặng cho kết quả sai.
Phát hiện bằng race detector:
go test -race ./...hoặcgo run -race. Nên bật trong CI.Cách xử lý:
sync.Mutexbọc map, hoặcsync.RWMutexkhi đọc nhiều hơn ghi rất nhiều.sync.Maphợp với vài trường hợp đặc thù (key ghi một lần đọc nhiều lần, hoặc mỗi goroutine dùng tập key riêng); không phải thay thế mặc định.- Bộ đếm đơn giản:
atomic.Int64. - Hoặc tránh chia sẻ: để một goroutine sở hữu dữ liệu, các goroutine khác gửi yêu cầu qua channel.
Hay sai: copy struct có chứa
sync.Mutex(truyền theo giá trị), khiến mỗi bản có một khoá riêng.go vetcảnh báo được lỗi này.09
context.Contextdùng để làm gì? Có những quy ước nào khi dùng context?GolangGợi ý trả lời
Ôn thêm câu hỏi Golang →Context mang tín hiệu huỷ, deadline và một ít giá trị gắn với request, truyền xuyên suốt chuỗi gọi hàm. Khi client ngắt kết nối hoặc hết thời gian, mọi thao tác bên dưới (truy vấn DB, gọi HTTP) đều có thể dừng sớm.
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) defer cancel() row := db.QueryRowContext(ctx, query, id)Quy ước:
- Context là tham số đầu tiên, tên
ctx; không lưu context trong struct. - Luôn
defer cancel()để giải phóng tài nguyên. - Goroutine chạy lâu phải kiểm tra
ctx.Done(). WithValuechỉ dùng cho dữ liệu theo request (request ID, thông tin trace), không dùng để truyền tham số tuỳ chọn.
Hay sai: dùng context của request cho việc chạy nền sau khi đã trả response. Request xong là context bị huỷ; hãy dùng
context.WithoutCancelhoặc context riêng.- Context là tham số đầu tiên, tên
10Điểm mạnh lớn nhất của bạn là gì?Câu hỏi hành vi & văn hoá
Gợi ý trả lời
Ôn thêm câu hỏi Câu hỏi hành vi & văn hoá →Chọn một hai điểm mạnh liên quan tới vị trí, và chứng minh bằng ví dụ thật, không liệt kê tính từ.
Cấu trúc: điểm mạnh, ví dụ ngắn, kết quả, liên hệ với công việc mới.
Câu mẫu: "Điểm mạnh của em là chủ động tìm nguyên nhân gốc. Ở dự án trước, hệ thống thỉnh thoảng bị timeout mà không ai tái hiện được. Em tự đọc log, thêm theo dõi ở các điểm nghi ngờ và tìm ra một truy vấn bị khoá bảng vào giờ cao điểm. Sau khi sửa, lỗi đó không còn xuất hiện. Em nghĩ điều này hữu ích với vị trí bên mình vì hệ thống có lượng truy cập lớn."
Hay sai: "Em chăm chỉ, ham học hỏi, chịu được áp lực" mà không có ví dụ. Ai cũng nói vậy nên không đọng lại gì.
Bạn đã phỏng vấn ở Giftee Tech Vietnam?
Kể lại các vòng và câu hỏi đã gặp (ẩn danh) để người sau ôn đúng trọng tâm.
Chia sẻ kinh nghiệm phỏng vấn