Tổng hợp 99 câu hỏi phỏng vấn Golang kèm đáp án chi tiết, song ngữ Việt–Anh — sắp xếp từ cơ bản đến nâng cao. Ôn luyện miễn phí, tự động lưu tiến độ.
Go là ngôn ngữ lập trình do Google tạo ra (2009), biên dịch (compiled), statically typed, cú pháp đơn giản. Ưu điểm: tốc độ gần C/C++, concurrency mạnh (goroutines), binary đơn (không cần runtime), stdlib phong phú, phù hợp cho microservices, cloud tools, APIs.
3 cách: (1) var name string = "Go" (đầy đủ). (2) var name = "Go" (type inference). (3) name := "Go" (short declaration, chỉ trong function). := phổ biến nhất. var dùng ở package level hoặc khi cần zero value. Go không có let/const như…
Go có các nhóm kiểu dữ liệu cơ bản: nhóm số gồm int, int8/16/32/64, float32/64, complex64/128; nhóm văn bản là string (immutable, mã hoá UTF-8); và bool cho giá trị logic true/false. Ngoài ra còn có byte (alias của uint8) dùng cho dữ liệu nhị…
Giá trị mặc định khi khai báo biến không khởi tạo — Go đảm bảo mọi biến đều có giá trị hợp lệ (khác JS/undefined, Python/None). Cụ thể: - int/float → 0 - string → "" (rỗng) - bool → false - pointer/slice/map/channel/interface → nil…
Khai báo hàm cơ bản dùng từ khoá func. Go hỗ trợ multiple return values và named returns. Functions là first-class citizens, có thể gán cho biến hoặc truyền như tham số.
Pattern phổ biến nhất: trả (result, error). Nếu không cần 1 giá trị, dùng (blank identifier). Khác biệt lớn với JS/TS: Go không dùng try/catch mà handle error ngay tại chỗ.
Mỗi file Go thuộc 1 package (package main). main package + func main() = entry point. Import: import "fmt" hoặc grouped import. Exported names (public) viết hoa chữ cái đầu: fmt.Println. Lowercase = unexported (private trong package).
Go KHÔNG có class. Struct là kiểu dữ liệu composite, methods gắn vào struct qua receiver. Composition thay vì inheritance: embed struct trong struct khác.
int là pointer type, &x lấy address, p dereference. Go không có pointer arithmetic như C. nil pointer gây panic. Dùng pointer khi: (1) muốn modify giá trị gốc. (2) struct lớn (tránh copy). (3) method cần modify receiver.
Array: size cố định, value type [5]int. Slice: dynamic, reference type []int. Slice dùng phổ biến hơn. make([]int, 0, 10) tạo slice capacity 10. append(slice, item) thêm phần tử. Slice là view lên underlying array.
Map là kiểu dữ liệu key-value tích hợp sẵn trong Go, khai báo bằng m := map[string]int{"a": 1} hoặc make(map[string]int). - Check key tồn tại: val, ok := m["key"] — ok là false nếu key không tồn tại (zero value của value type). - Xoá:…
range là cú pháp duy nhất để iterate qua collection trong Go, hoạt động khác nhau theo từng kiểu dữ liệu: - Slice / array: for i, v := range slice { } — trả về index và value. - Map: for k, v :=…
Go không có try/catch/throw. Errors là values, check lỗi ngay sau khi gọi. Explicit implicit. errors.Is(), errors.As() để check error chain.
defer đảm bảo một function sẽ được gọi khi function chứa nó return (LIFO). panic dừng chương trình khi gặp lỗi không thể khôi phục. recover bắt panic bên trong deferred function. Trong thực tế, hạn chế dùng panic/recover và ưu tiên trả error thông…
Interface = tập hợp method signatures. Implicit implementation: struct tự động satisfy interface nếu có đủ methods (không cần implements keyword). interface{} (hoặc any) là empty interface chấp nhận mọi type.
any (alias interface{}) chấp nhận mọi type, nhưng cần type assertion để dùng value cụ thể. Go 1.18+ có generics thay thế nhiều use cases. Dùng cho: generic containers, JSON parsing, function nhận nhiều types.
Type assertion lấy giá trị cụ thể từ interface. Type switch xử lý nhiều types một cách gọn gàng. Tương tự instanceof trong JS.
Hai dạng receiver có ngữ nghĩa khác nhau về copy và mutation: - Value receiver func (u User) Name() — nhận một copy của struct, không thể modify giá trị gốc. - Pointer receiver func (u User) SetName(n string) — modify trực tiếp struct gốc,…
Generics cho phép viết function và type hoạt động với nhiều kiểu dữ liệu mà không cần lặp code, ví dụ func MapT any, U any U) []U nhận slice bất kỳ và trả về slice đã transform. Type parameters được khai báo trong dấu…
Goroutine là lightweight thread do Go runtime quản lý. Khác OS thread: goroutine stack default ~8KB, tự grow/shrink; Go scheduler multiplex goroutines lên OS threads (M:N scheduling). Có thể chạy hàng triệu goroutines cùng lúc.
Kênh giao tiếp giữa goroutines. Unbuffered channel: block đến khi cả sender và receiver sẵn sàng. Buffered: không block nếu buffer chưa đầy.
select { case v := <-ch1: ... case ch2 <- x: ... default: ... } chờ multiple channel operations, chạy case sẵn sàng đầu tiên. Nếu nhiều case sẵn sàng → chọn random. default chạy nếu không case nào sẵn sàng (non-blocking). Dùng cho timeout:…
Dùng khi cần đợi nhiều goroutines hoàn thành trước khi tiếp tục. Đơn giản hơn channel cho fan-out/fan-in. wg.Add(n) đăng ký n goroutines. wg.Done() thường dùng kết hợp defer.
Stdlib đủ mạnh cho production. Không bắt buộc framework. http.ServeMux là default router.
Gin là HTTP web framework phổ biến nhất trong hệ sinh thái Go, được thiết kế tập trung vào hiệu năng với routing dựa trên radix tree, nhanh hơn đáng kể so với http.ServeMux mặc định. So với stdlib net/http, Gin cung cấp sẵn: middleware…
Middleware trong Go là một wrapper function nhận handler và trả về handler mới, cho phép chèn logic trước hoặc sau khi xử lý request.
Go sử dụng package encoding/json. Struct tags quyết định cách ánh xạ field sang JSON key.
GORM là ORM phổ biến nhất trong Go, cung cấp API trực quan để thao tác database: db.Create(&user) để insert, db.First(&user, 1) để query theo primary key, và db.Where("age ?", 18).Find(&users) cho query có điều kiện. GORM hỗ trợ auto migration (db.AutoMigrate(&User{})) để tự tạo/cập…
sql.Open() trả connection pool (không phải single connection). Set: db.SetMaxOpenConns(25), db.SetMaxIdleConns(5), db.SetConnMaxLifetime(5time.Minute). KHÔNG defer db.Close() mỗi request vì sql.DB là long-lived pool — chỉ close khi app shutdown. Prepared statements cho queries lặp lại. Context timeout cho queries: db.QueryContext(ctx, query).
Go modules là hệ thống quản lý dependency chính thức của Go, khởi tạo bằng go mod init module-name để tạo file go.mod chứa tên module và danh sách dependencies. Khi cần thêm thư viện, dùng go get package@version, và go mod tidy để tự…
File test.go, function TestXxx(t testing.T). Run: go test ./.... Go không có built-in assert — dùng if/t.Errorf. Table-driven tests là pattern phổ biến.
Pattern test nhiều cases bằng slice of structs — DRY, dễ thêm cases, idiomatic Go. Mỗi sub-test chạy độc lập với t.Run, có thể filter: go test -run TestSquare/positive.
func BenchmarkXxx(b testing.B) — Go đo lường performance tự động điều chỉnh số lần chạy.
Go khuyến khích mocking dựa trên interface: define một interface nhỏ cho dependency cần mock, sau đó tạo mock struct implement interface đó với behavior tuỳ chỉnh, ví dụ type MockDB struct { GetFunc func(id int) (User, error) } rồi gọi m.GetFunc(id) trong method Get.…
Go đi kèm bộ toolchain rất mạnh ngay từ khi cài đặt: go build để compile thành binary, go run để compile và chạy luôn, go test để chạy unit test và benchmark, và go fmt để format code theo chuẩn thống nhất (bắt buộc…
Go có hỗ trợ profiling tích hợp sẵn thông qua package net/http/pprof: chỉ cần import "net/http/pprof" là server tự expose các endpoint profiling tại /debug/pprof/. Để phân tích CPU, dùng go test -cpuprofile=cpu.prof rồi mở bằng go tool pprof cpu.prof; tương tự với memory profiling…
Từ Go 1.21, stdlib cung cấp package log/slog cho structured logging với cú pháp slog.Info("user created", "id", user.ID, "email", user.Email), output ra JSON có cấu trúc rõ ràng thay vì chuỗi text khó parse. Slog hỗ trợ các mức log chuẩn (Debug, Info, Warn, Error)…
Chọn Go khi: CPU-intensive tasks, high concurrency (100K+ connections), microservices, CLI tools, system programming, cần type safety mạnh. Chọn Node.js khi: full-stack JS, rapid prototyping, npm ecosystem, real-time (Socket.io), team quen JS. Go nhanh hơn ~10-50x cho compute, Node.js ecosystem lớn hơn.
Struct tags là metadata gắn vào field của struct dưới dạng string literal sau tên field, được đọc tại runtime thông qua reflection. Phổ biến nhất là JSON tags và validation tags. Các tags phổ biến: - json:"name,omitempty" — encoding/json - db:"columnname" — sqlx, GORM…
Cả hai dùng để kiểm tra error chain (chuỗi lỗi được wrap), nhưng mục đích khác nhau. errors.Is(err, target) — kiểm tra identity: err có phải (hoặc wrap) target không? Dùng cho sentinel errors (lỗi được định nghĩa sẵn). errors.As(err, &target) — kiểm tra type:…
t.Helper() đánh dấu function hiện tại là helper function. Khi test fail, Go runtime báo lỗi tại caller của helper (không phải bên trong helper), giúp dễ đọc output test hơn. Khi nào dùng: bất kỳ helper function nào nhận testing.T hoặc testing.B đều nên…
Race detector (-race flag) là công cụ tích hợp trong Go runtime, phát hiện data race — khi hai goroutines truy cập cùng vùng nhớ đồng thời mà không sync, ít nhất một thao tác là write. Race detector hoạt động bằng cách instrument binary…
byte là alias của uint8 (1 byte thô); rune là alias của int32, đại diện cho một code point Unicode. String trong Go là chuỗi byte UTF-8 bất biến, không phải mảng ký tự. Hệ quả: - s[i] trả về byte, và len(s) đếm byte…
Tham số cuối cùng của hàm có thể khai báo ...T để nhận số lượng đối số biến thiên. Bên trong hàm, tham số đó là một []T thường. Hai chiều dùng ...: - Gọi với nhiều đối số rời: sum(1, 2, 3). - Spread…
Cả hai đều cấp phát, nhưng cho mục đích khác nhau: - new(T) — dùng cho mọi kiểu. Cấp phát một T đã zeroed và trả về con trỏ T. Nó không khởi tạo gì thêm. - make(T, ...) — chỉ cho slice, map, channel.…
Khi một package được nạp, Go khởi tạo theo thứ tự: 1. Các package được import khởi tạo trước (đệ quy) — mỗi package chỉ một lần. 2. Biến cấp package được gán, theo thứ tự phụ thuộc (không phải thứ tự viết): biến nào…
iota là bộ đếm compile-time: nó reset về 0 ở đầu mỗi block const và tăng 1 sau mỗi dòng ConstSpec. Kết hợp với một kiểu riêng, đây là cách Go dựng enum. - Gán một kiểu (Weekday int) để tạo enum có type an…
struct{} là struct không có field nào → chiếm 0 byte. Mọi giá trị struct{}{} share cùng một địa chỉ, nên dùng nó ở đâu chỉ cần "sự hiện diện" chứ không cần dữ liệu. Ba chỗ thường gặp: - Set: map[string]struct{} — tiết kiệm…
Một slice là view gồm (con trỏ, len, cap) trỏ vào một backing array. Nhiều slice có thể share cùng array → sửa qua slice này, slice kia thấy theo. Điểm bẫy nằm ở append: - Còn cap dư → append ghi in-place lên array…
sort.Slice(s, less) sắp xếp slice tại chỗ dựa trên hàm so sánh less(i, j) bool — trả về true nếu phần tử i phải đứng trước j. Không cần implement interface sort.Interface. Lưu ý: - sort.Slice không ổn định (thứ tự phần tử bằng nhau…
Unbuffered (make(chan int)): mỗi lần gửi phải có một bên nhận sẵn sàng ngay tại thời điểm đó — gửi và nhận là một điểm đồng bộ. Buffered (make(chan int, 3)): gửi chỉ chặn khi buffer đầy, nhận chỉ chặn khi buffer rỗng. Runtime phát…
close là tín hiệu "không còn dữ liệu nào nữa", không phải thao tác giải phóng bộ nhớ (channel do GC thu hồi). Sau khi đóng: - Nhận: trả về ngay lập tức, không chặn. Dữ liệu còn trong buffer được trả ra trước, hết…
select không có default sẽ chặn cho tới khi một nhánh sẵn sàng. Thêm default biến nó thành thao tác không chặn: nếu không có nhánh nào sẵn sàng ngay, default chạy luôn. Ứng dụng chính là try-send / try-receive — bỏ bớt việc khi…
Vì cancel là thứ giải phóng tài nguyên gắn với context đó, kể cả khi công việc đã hoàn tất bình thường. Mỗi context con được đăng ký vào danh sách con của context cha. Với WithTimeout / WithDeadline còn có thêm một time.Timer đang…
Goroutine leak là goroutine được tạo ra nhưng không bao giờ kết thúc — nó chặn vĩnh viễn trên một thao tác không bao giờ hoàn tất. Goroutine đó giữ luôn stack, biến nó tham chiếu và cả connection nó đang cầm, nên GC không…
Mọi thao tác gửi hoặc nhận trên channel nil đều chặn vĩnh viễn — không panic, không lỗi, chỉ đứng im. Đây là hành vi được định nghĩa trong spec chứ không phải bug. Hệ quả trong select: nhánh có channel nil không bao giờ…
Vấn đề của pipeline là stage phía sau có thể dừng sớm (đủ dữ liệu, người dùng huỷ request, lỗi), còn stage phía trước thì vẫn đang chặn ở lệnh gửi. Muốn dừng sạch, mọi thao tác gửi phải đi kèm một nhánh thoát. done…
Tạo 10.000 goroutine cùng lúc là chạy được về mặt kỹ thuật nhưng sẽ mở 10.000 kết nối, làm đối tác trả 429 hoặc chính service bị hết file descriptor. Cần chặn trần số việc chạy đồng thời. Cách 1 — semaphore bằng buffered channel.…
sync.Once bảo đảm một đoạn khởi tạo chạy đúng một lần dù có bao nhiêu goroutine gọi tới. Kiểm tra bằng biến bool thường là sai: hai goroutine có thể cùng thấy false và cùng khởi tạo. Điểm quan trọng thứ hai: Do chặn cho…
Tài liệu context giới hạn rõ: WithValue chỉ dành cho dữ liệu theo phạm vi một request đi xuyên qua biên process/API — request ID, trace ID, danh tính người dùng đã xác thực, locale. Không dùng nó để truyền tham số của hàm, và…
1. Gọi Add bên trong goroutine. Tài liệu yêu cầu Add phải xảy ra trước Wait. Đặt Add(1) trong goroutine thì Wait có thể chạy khi bộ đếm vẫn là 0 và trả về ngay, bỏ qua toàn bộ công việc. 2. Truyền WaitGroup theo…
http.Handler là interface chỉ có một method: http.HandlerFunc là một kiểu hàm có sẵn method ServeHTTP gọi lại chính nó — nhờ vậy một hàm thường có thể đóng vai Handler mà không cần định nghĩa struct riêng: Thực tế: Ý nghĩa khi phỏng vấn:…
Cả hai đều in ra chuỗi giống nhau, nhưng %w giữ lại lỗi gốc trong chuỗi unwrap, còn %v chỉ ghép chuỗi và cắt đứt liên kết. Nguyên tắc dùng: - Bọc bằng %w khi tầng trên cần phân biệt loại lỗi để quyết định…
Embedding là khai báo một field không có tên, chỉ có kiểu. Field và method của kiểu được nhúng được promote lên struct ngoài, gọi trực tiếp như của chính nó. Không phải kế thừa. Đây là composition cộng đường tắt cú pháp: - Không…
defer chạy khi hàm kết thúc, không phải khi hết một vòng lặp. Đặt trong loop nghĩa là mọi tài nguyên bị giữ đến tận cuối hàm — vòng lặp vài nghìn file sẽ chạm giới hạn file descriptor. Cách sửa 1 — tách thân…
Vì http.Server mặc định có mọi timeout bằng 0, nghĩa là không giới hạn. Một client chậm (hoặc cố tình giữ kết nối) có thể chiếm goroutine và file descriptor vô hạn định — dạng tấn công Slowloris. Điểm hay bị hỏi thêm: - WriteTimeout…
Middleware bọc từ trong ra ngoài, nhưng khi request tới thì chạy từ ngoài vào trong, rồi phần sau lời gọi next chạy ngược lại — giống ngăn xếp. Hệ quả thực tế: - Middleware đo thời gian / recover panic phải ở ngoài cùng…
Vì method set của T chỉ gồm method có value receiver, còn method set của T gồm cả hai. Nếu interface đòi một method khai báo trên T thì chỉ con trỏ mới thoả mãn. Lý do sâu hơn: gọi r.Save() trên biến addressable, compiler…
Không. Spec quy định thứ tự lặp trên map là không xác định, và runtime còn cố tình ngẫu nhiên hoá điểm bắt đầu mỗi lần chạy để lập trình viên không lỡ phụ thuộc vào nó. Muốn ổn định thì sắp xếp khoá rồi…
t.Run biến mỗi case thành một subtest độc lập: có tên riêng trong output, chạy lẻ được bằng go test -run TestX/casename, và một case fail không dừng các case còn lại. Lưu ý: - Trước Go 1.22, biến loop tc dùng chung một ô…
omitempty bỏ field khỏi JSON khi giá trị là empty value theo định nghĩa của encoding/json: false, 0, con trỏ/interface nil, và string/array/slice/map có độ dài 0. Danh sách này không bao gồm struct. Hệ quả hay gặp khi làm API: - Page: 0 và…
string trong Go là bất biến, []byte thì không — nên []byte(s) và string(b) thường phải cấp phát bộ nhớ mới và copy toàn bộ dữ liệu. Trên hot path xử lý request, việc này tạo áp lực GC rõ rệt. Các trường hợp compiler…