Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026
Golang
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:
var name string = "Go"(đầy đủ).var name = "Go"(type inference).name := "Go"(short declaration, chỉ trong function).:=phổ biến nhất.vardùng ở package level hoặc khi cần zero value
Go không có let/const như JS.
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ã hóa 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ị phân, và rune (alias của int32) đại diện cho một ký tự Unicode.
Mỗi kiểu đều có zero value mặc định khi khai báo mà không gán giá trị: int là 0, string là chuỗi rỗng, bool là false, pointer là nil.
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
- struct → zero value của mỗi field
Ý nghĩa: code an toàn hơn, không cần check null/undefined, zero value thường là trạng thái hợp lý (ví dụ: counter bắt đầu từ 0).
Khai báo hàm cơ bản dùng từ khóa func.
Go hỗ trợ multiple return values và named returns.
// Hàm cơ bản
func add(a int, b int) int {
return a + b
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// Named return values
func swap(a, b int) (x, y int) {
x, y = b, a
return
}
// Functions là first-class citizens
add := func(a, b int) int { return a + b }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ỗ.
// Pattern (result, error)
val, err := strconv.Atoi("123")
if err != nil {
log.Fatal(err)
}
// Bỏ qua giá trị không cần dùng blank identifier
_, err := doSomething()
// Trả nhiều giá trị tùy ý
func minMax(nums []int) (min, max int) { ... }
min, max := minMax([]int{3, 1, 4, 1, 5})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.
// Định nghĩa struct
type User struct {
Name string
Age int
}
// Method gắn vào struct qua value receiver
func (u User) Hello() string {
return "Hello, " + u.Name
}
// Pointer receiver để modify struct
func (u *User) SetName(name string) {
u.Name = name
}
// Khởi tạo struct
u := User{Name: "Alice", Age: 30}
fmt.Println(u.Hello()) // "Hello, Alice"
// Composition thay inheritance
type Admin struct {
User // embed User
Level int
}
admin := Admin{User: User{Name: "Bob"}, Level: 1}
fmt.Println(admin.Hello()) // kế thừa method của Userint là pointer type, &x lấy address, p dereference.
Go không có pointer arithmetic như C. nil pointer gây panic.
x := 42
p := &x // p là *int, trỏ đến x
fmt.Println(*p) // 42 — dereference
*p = 100
fmt.Println(x) // 100 — modify qua pointer
// Dùng pointer để modify tham số
func increment(n *int) {
*n++
}
increment(&x)
// Pointer receiver — modify struct gốc
func (u *User) SetAge(age int) {
u.Age = age
}Dùng pointer khi:
- muốn modify giá trị gốc.
- struct lớn (tránh copy).
- 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.
Stdlib đủ mạnh cho production.
Không bắt buộc framework. http.ServeMux là default router.
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ok")
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}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 trong hầu hết project).
- Về chất lượng code,
go vetphát hiện các lỗi tiềm ẩn mà compiler không bắt được, còngolangci-lintlà công cụ linting tổng hợp chạy hàng chục linter cùng lúc. - Ngoài ra còn có
go modcho quản lý dependency,go generatecho code generation,go docđể xem documentation, vàgo tool pprofđể profiling hiệu năng ứng dụng.
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"]—oklà false nếu key không tồn tại (zero value của value type). - Xoá:
delete(m, "key")— không panic nếu key không tồn tại. - Không thread-safe: truy cập concurrent phải dùng
sync.Maphoặc bọc bằng mutex. - Zero value là nil: map nil chỉ đọc được, ghi sẽ panic — phải
makehoặc dùng literal trước khi gán. - Iteration order không xác định: mỗi lần range cho thứ tự khác nhau, là design có chủ ý của Go.
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 := range m { }— trả về key và value, thứ tự không xác định. - String:
for i, r := range "hello" { }— trả về byte index và rune (Unicode code point). - Channel:
for v := range ch { }— đọc đến khi channel close. - Integer (Go 1.22+):
for i := range 10 { }— range over integer, tương đươngfor i := 0; i < 10; i++. - Bỏ qua giá trị: dùng blank identifier —
for _, v := range items { }.
Pitfall: range copy value sang biến loop, không phải reference. Nếu cần mutate phần tử, dùng slice[i] thay vì v.
- Go không có try/catch/throw.
- Errors là values, check lỗi ngay sau khi gọi.
- Explicit > implicit.
// Hàm trả (result, error)
func readFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("readFile %s: %w", path, err)
}
return data, nil
}
// Xử lý lỗi tại nơi gọi
data, err := readFile("config.json")
if err != nil {
log.Fatal(err)
}
// Custom error
var ErrNotFound = errors.New("not found")
// Wrap và unwrap error
err := fmt.Errorf("service layer: %w", ErrNotFound)
errors.Is(err, ErrNotFound) // trueerrors.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.
// defer — cleanup đảm bảo luôn chạy
func processFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // luôn đóng file khi function return
// xử lý file...
return nil
}
// panic + recover — bắt lỗi nghiêm trọng
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
return a / b, nil // panic nếu b == 0
}
// defer chạy LIFO
defer fmt.Println("1") // chạy thứ 3
defer fmt.Println("2") // chạy thứ 2
defer fmt.Println("3") // chạy thứ 1Trong thực tế, hạn chế dùng panic/recover và ưu tiên trả error thông 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).
// Định nghĩa interface
type Writer interface {
Write([]byte) (int, error)
}
type Stringer interface {
String() string
}
// Struct tự động satisfy interface
type MyWriter struct{}
func (w MyWriter) Write(p []byte) (int, error) {
fmt.Print(string(p))
return len(p), nil
}
// Dùng interface làm tham số — polymorphism
func save(w Writer, data []byte) error {
_, err := w.Write(data)
return err
}
// Empty interface chấp nhận mọi type
func printAny(v any) {
fmt.Println(v)
}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.
// JSON parsing trả map[string]any
var result map[string]any
json.Unmarshal(data, &result)
// Type assertion để lấy giá trị
func printValue(i any) {
// safe assertion
if s, ok := i.(string); ok {
fmt.Println("string:", s)
return
}
// type switch cho nhiều types
switch v := i.(type) {
case int:
fmt.Println("int:", v)
case []any:
fmt.Println("slice length:", len(v))
default:
fmt.Printf("unknown: %T\n", v)
}
}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.
var i any = "hello"
// Type assertion — panic nếu sai type
s := i.(string)
// Safe type assertion — không panic
s, ok := i.(string)
if ok {
fmt.Println(s)
}
// Type switch — idiomatic Go
func describe(i any) {
switch v := i.(type) {
case string:
fmt.Printf("string of length %d\n", len(v))
case int:
fmt.Printf("int: %d\n", v)
case bool:
fmt.Printf("bool: %v\n", v)
default:
fmt.Printf("unknown type: %T\n", v)
}
}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, không tốn chi phí copy.
Dùng pointer receiver khi: cần mutate state, struct lớn (tránh copy), hoặc để giữ consistency — nếu một method dùng pointer thì tất cả method của type đó nên dùng pointer.
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 ngoặc vuông
[], kèm constraints để giới hạn kiểu được chấp nhận nhưany,comparable, hoặc custom interface constraints kiểutype Number interface { int | float64 }. - Trước Go 1.18, muốn viết hàm generic phải dùng
interface{}rồi type assertion, vừa mất type safety vừa verbose; generics giải quyết triệt để vấn đề này.
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.
func fetchData(url string) {
resp, err := http.Get(url)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close()
// xử lý response...
}
func main() {
urls := []string{"https://a.com", "https://b.com", "https://c.com"}
for _, url := range urls {
go fetchData(url) // mỗi request chạy goroutine riêng
}
time.Sleep(2 * time.Second) // đợi goroutines (dùng WaitGroup tốt hơn)
}- 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.
// Tạo channel
ch := make(chan int) // unbuffered
bch := make(chan int, 10) // buffered capacity 10
// Gửi và nhận
go func() {
ch <- 42 // send — block đến khi có receiver
}()
val := <-ch // receive — block đến khi có data
fmt.Println(val) // 42
// Directional channels (best practice)
func producer(out chan<- int) { // chỉ gửi
out <- 1
}
func consumer(in <-chan int) { // chỉ nhận
fmt.Println(<-in)
}
// Close và range
close(ch)
for v := range ch { // tự dừng khi channel đóng
fmt.Println(v)
}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.
defaultchạy nếu không case nào sẵn sàng (non-blocking). - Dùng cho timeout:
case <-time.After(5*time.Second).
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.
var wg sync.WaitGroup
urls := []string{"https://a.com", "https://b.com", "https://c.com"}
for _, url := range urls {
wg.Add(1) // đăng ký 1 goroutine
go func(u string) {
defer wg.Done() // báo hoàn thành khi return
fetch(u)
}(url)
}
wg.Wait() // block đến khi tất cả goroutines Done
fmt.Println("all done")wg.Add(n) đăng ký n goroutines. wg.Done() thường dùng kết hợp defer.
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 chain, JSON binding và validation tự động, error handling tập trung, và group routes để tổ chức API gọn gàng. Cách dùng cơ bản: r := gin.Default(); r.GET("/users/:id", getUser); r.Run(), trong đó gin.Default() đã bao gồm sẵn middleware logger và recovery.
Các framework thay thế: Echo, Fiber (dựa trên fasthttp), và Chi (tương thích net/http).
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.
// Middleware cơ bản — stdlib net/http
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !isValidToken(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r) // gọi handler tiếp theo
})
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
// Chain middlewares — request đi từ ngoài vào trong
http.Handle("/api", loggingMiddleware(authMiddleware(apiHandler)))
// Gin middleware — đơn giản hơn
r := gin.Default()
r.Use(authMiddleware()) // áp dụng cho tất cả routes
api := r.Group("/api")
api.Use(rateLimitMiddleware()) // áp dụng cho groupGo sử dụng package encoding/json.
Struct tags quyết định cách ánh xạ field sang JSON key.
// Định nghĩa struct với JSON tags
type User struct {
Name string `json:"name"`
Age int `json:"age,omitempty"` // bỏ qua nếu zero value
Email string `json:"-"` // luôn bỏ qua khi encode
}
// Marshal — struct → JSON bytes
u := User{Name: "Alice", Age: 30}
data, err := json.Marshal(u)
// {"name":"Alice","age":30}
// Unmarshal — JSON bytes → struct
var u2 User
err = json.Unmarshal(data, &u2)
// HTTP response — dùng Encoder để stream
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(User{Name: "Bob", Age: 25})
}
// HTTP request body — dùng Decoder
func createUser(w http.ResponseWriter, r *http.Request) {
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}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 nhật bảng theo struct definition, cùng với các associations như BelongsTo, HasMany, ManyToMany và hooks như BeforeCreate, AfterUpdate để chạy logic trước/sau thao tác.
Với các query phức tạp hoặc cần kiểm soát performance chặt, nhiều team chọn dùng sqlx (raw SQL kết hợp auto scanning) hoặc ent (Facebook, code generation) thay thế.
sql.Open() trả connection pool (không phải single connection).
- Set:
db.SetMaxOpenConns(25),db.SetMaxIdleConns(5),db.SetConnMaxLifetime(5*time.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ự động dọn dẹp các dependency không còn sử dụng cũng như thêm các dependency bị thiếu.
File go.sum đóng vai trò lock file (tương tự package-lock.json trong Node.js), lưu checksum chính xác của từng dependency để đảm bảo build reproducible.
Go modules còn hỗ trợ replace directive để trỏ sang local module khi phát triển, và sử dụng GOPROXY để tải dependency qua proxy server nhằm tăng tốc và đảm bảo tính sẵn có.
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.
// math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; want %d", got, want)
}
}
// Table-driven test
func TestMultiply(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive", 3, 4, 12},
{"zero", 0, 5, 0},
{"negative", -2, 3, -6},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Multiply(tt.a, tt.b)
if got != tt.want {
t.Errorf("Multiply(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
}
})
}
}Pattern test nhiều cases bằng slice of structs — DRY, dễ thêm cases, idiomatic Go.
func TestSquare(t *testing.T) {
cases := []struct {
name string
input int
want int
}{
{"positive", 5, 25},
{"zero", 0, 0},
{"negative", -3, 9},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := Square(tc.input)
if got != tc.want {
t.Errorf("Square(%d) = %d; want %d", tc.input, got, tc.want)
}
})
}
}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.
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(100, 200)
}
}
// Benchmark với setup
func BenchmarkSort(b *testing.B) {
data := generateLargeSlice(10000)
b.ResetTimer() // bỏ qua thời gian setup
for i := 0; i < b.N; i++ {
sort.Ints(data)
}
}go test -bench=. # chạy tất cả benchmarks
go test -bench=BenchmarkAdd # chạy benchmark cụ thể
go test -bench=. -benchmem # hiển thị thêm B/op, allocs/op
benchstat old.txt new.txt # so sánh 2 kết quả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 tùy chỉnh, ví dụ type MockDB struct { GetFunc func(id int) (*User, error) } rồi gọi m.GetFunc(id) trong method Get.
Cách này tận dụng implicit interface của Go nên không cần sửa code production, chỉ cần đảm bảo mock struct có đủ methods. Các thư viện phổ biến như testify/mock, gomock, hay mockery (tự động generate mock từ interface) giúp giảm boilerplate khi có nhiều methods cần mock.
Nguyên tắc quan trọng: chỉ mock ở boundary (database, external API) và tránh mock quá nhiều layer vì sẽ làm test mất ý nghĩa.
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.profrồi mở bằnggo tool pprof cpu.prof; tương tự với memory profiling dùng flag-memprofile=mem.profđể tìm các hàm allocate nhiều bộ nhớ. - Công cụ pprof hỗ trợ xem flame graph trực quan qua
pprof -http=:8080, giúp nhanh chóng nhận diện hot functions, excessive allocations, và goroutine leaks. - Ngoài ra
go tool tracecho phép xem chi tiết execution trace theo timeline, hữu ích khi debug vấn đề concurrency và latency.
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) và cho phép tùy chỉnh handler để thay đổi format hoặc destination output.
- Trước khi có slog, cộng đồng Go dùng zap (Uber, tối ưu hiệu năng cao nhất) hoặc zerolog (zero allocation) cho structured logging; cả hai vẫn phổ biến trong production vì có thêm nhiều tính năng nâng cao.
- Ưu điểm lớn nhất của structured logging là log dạng key-value pairs dễ dàng được index và query bởi các log aggregator như ELK Stack, Datadog, hay Grafana Loki.
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.
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name" validate:"required,min=2"`
Email string `json:"email" validate:"required,email"`
Password string `json:"-"` // bỏ qua khi encode JSON
CreatedAt time.Time `json:"created_at,omitempty"` // bỏ qua nếu zero value
}
// Đọc tag lúc runtime
t := reflect.TypeOf(User{})
field, _ := t.FieldByName("Email")
fmt.Println(field.Tag.Get("json")) // "email"
fmt.Println(field.Tag.Get("validate")) // "required,email"Các tags phổ biến:
- json:"name,omitempty" — encoding/json
- db:"column_name" — sqlx, GORM
- validate:"required,min=3" — go-playground/validator
- yaml:"field_name" — gopkg.in/yaml.v3
- env:"ENV_VAR" — envconfig
Tags không ảnh hưởng đến type safety tại compile time, chỉ có ý nghĩa khi package đọc chúng qua reflection.
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).
var ErrNotFound = errors.New("not found")
// Wrap lỗi qua nhiều layers
err := fmt.Errorf("service: %w", fmt.Errorf("repo: %w", ErrNotFound))
errors.Is(err, ErrNotFound) // true — dù wrap 2 lần
errors.Is(err, ErrNotFound) // false nếu dùng %v thay vì %werrors.As(err, &target) — kiểm tra type: err có phải (hoặc wrap) một lỗi thuộc kiểu cụ thể không? Dùng cho custom error types để truy xuất thêm thông tin.
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation: %s — %s", e.Field, e.Message)
}
err := fmt.Errorf("handler: %w", &ValidationError{Field: "email", Message: "invalid"})
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println(ve.Field) // "email"
fmt.Println(ve.Message) // "invalid"
}Tóm tắt: errors.Is so sánh giá trị, errors.As so sánh kiểu.
Luôn dùng %w khi wrap để giữ chain.
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.
// Không có t.Helper() — lỗi báo tại dòng trong assertEq
func assertEq(t *testing.T, got, want int) {
if got != want {
t.Errorf("got %d, want %d", got, want) // line X in assertEq
}
}
// Có t.Helper() — lỗi báo tại nơi gọi assertEq
func assertEq(t *testing.T, got, want int) {
t.Helper() // <-- thêm dòng này
if got != want {
t.Errorf("got %d, want %d", got, want) // báo tại TestXxx line Y
}
}
// Pattern dùng trong test suite
func TestCalc(t *testing.T) {
assertEq(t, Add(2, 3), 5) // khi fail → báo dòng này
assertEq(t, Add(-1, 1), 0) // khi fail → báo dòng này
}Khi nào dùng: bất kỳ helper function nào nhận testing.T hoặc testing.B đều nên gọi t.Helper() ở dòng đầu tiên.
Đây là best practice idiomatic Go.
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 tại compile time, theo dõi mọi memory access.
# Chạy tests với race detector
go test -race ./...
# Chạy binary với race detector
go run -race main.go
go build -race -o app && ./app
# Output khi phát hiện race
WARNING: DATA RACE
Write at 0x00c0000b4010 by goroutine 7:
main.increment()
/app/main.go:12
Read at 0x00c0000b4010 by goroutine 8:
main.readValue()
/app/main.go:18Overhead: ~5-10x chậm hơn và dùng nhiều memory hơn — không dùng trong production binary.
Best practice: luôn chạy -race trong CI pipeline. Với service high-load, có thể enable race detector trên 1 instance staging riêng để phát hiện race conditions khó reproduce.
sync.Mutex: Lock()/Unlock() cho exclusive access (1 goroutine tại 1 thời điểm). sync.RWMutex: RLock() cho nhiều readers đồng thời, Lock() cho exclusive write.
- Dùng khi shared state giữa goroutines.
- Luôn
defer mu.Unlock(). - Tránh deadlock: lock order nhất quán.
Package context cung cấp cơ chế truyền deadline, tín hiệu hủy (cancellation), và các giá trị request-scoped xuyên suốt chuỗi goroutine.
Nguyên tắc quan trọng: luôn truyền context như tham số đầu tiên của function, không bao giờ lưu context vào struct.
// WithTimeout — tự động hủy sau timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // luôn gọi cancel để tránh leak
resp, err := http.NewRequestWithContext(ctx, "GET", url, nil)
// WithCancel — hủy thủ công
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel() // hủy khi không cần kết quả nữa
}()
// Truyền context vào database query
rows, err := db.QueryContext(ctx, "SELECT * FROM users")
// HTTP server — mỗi request mang context riêng
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // tự hủy khi client ngắt kết nối
result, err := db.QueryContext(ctx, "SELECT ...")
_ = result
_ = err
}Tạo fixed number goroutines (workers) đọc jobs từ channel, giới hạn concurrency và tránh tạo quá nhiều goroutines.
func worker(id int, jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
results <- process(j)
}
}
func main() {
jobs := make(chan Job, 100)
results := make(chan Result, 100)
var wg sync.WaitGroup
for i := 0; i < 5; i++ { // 5 workers
wg.Add(1)
go worker(i, jobs, results, &wg)
}
for _, j := range allJobs { jobs <- j }
close(jobs)
wg.Wait()
close(results)
}golang.org/x/sync/errgroup: chạy nhiều goroutines, trả lỗi đầu tiên, cancel tất cả khi 1 lỗi. g, ctx := errgroup.WithContext(ctx); g.Go(func() error { ... }); if err := g.Wait(); err != nil { ... }.
Tốt hơn WaitGroup khi cần error propagation và cancellation.
RPC framework dùng Protocol Buffers (protobuf).
- Nhanh hơn REST (binary encoding, HTTP/2).
- Dùng khi: microservices internal communication, streaming (bidirectional), cần type safety chặt.
- Define service trong .proto file →
protocgenerate Go code. - Hỗ trợ: unary, server streaming, client streaming, bidirectional.
Graceful shutdown đợi các request đang xử lý hoàn thành trước khi thoát process, tránh mất data hoặc trả 502 cho client.
srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// Bắt SIGINT / SIGTERM (Ctrl+C, kubectl rollout, docker stop)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// Cho phép request đang chạy 30s để hoàn thành
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("forced shutdown: %v", err)
}Để implement rate limiting trong Go, cách chuẩn là dùng package golang.org/x/time/rate với token bucket algorithm: limiter := rate.NewLimiter(rate.Every(time.Second), 10) tạo limiter cho phép 10 request mỗi giây.
- Trong HTTP middleware, kiểm tra
if !limiter.Allow()và trả về status 429 Too Many Requests nếu vượt giới hạn. - Để giới hạn theo từng client (per-IP), cần dùng
map[string]*rate.Limiterkết hợp với mutex để quản lý limiter riêng cho mỗi IP, đồng thời nên dọn dẹp các limiter cũ theo định kỳ để tránh memory leak. - Trong môi trường production nhiều server, nên dùng Redis-based distributed rate limiter để đảm bảo giới hạn nhất quán across tất cả instances.
Functional options tạo API khởi tạo object linh hoạt, dễ mở rộng mà không break code cũ.
type Server struct {
port int
timeout time.Duration
maxConn int
}
// Option là function type
type Option func(*Server)
// Helper functions trả về Option
func WithPort(p int) Option {
return func(s *Server) { s.port = p }
}
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func WithMaxConn(n int) Option {
return func(s *Server) { s.maxConn = n }
}
// Constructor apply options
func NewServer(opts ...Option) *Server {
s := &Server{port: 8080, timeout: 30 * time.Second, maxConn: 100} // defaults
for _, opt := range opts {
opt(s)
}
return s
}
// Sử dụng — chỉ cần truyền options quan tâm
srv := NewServer(
WithPort(9090),
WithTimeout(60 * time.Second),
)Dependency injection trong Go thực hiện chủ yếu qua constructor injection: function khởi tạo nhận interface làm tham số, ví dụ func NewUserService(repo UserRepository) *UserService, giúp UserService không phụ thuộc vào implementation cụ thể mà chỉ phụ thuộc vào contract (interface).
Triết lý Go ưu tiên sự tường minh nên phần lớn project không cần DI framework, chỉ cần truyền dependency thủ công tại main function. Với ứng dụng lớn nhiều dependency lồng nhau, có thể dùng wire (Google, compile-time) hoặc fx (Uber, runtime).
Interface nên giữ nhỏ (1-3 methods) theo nguyên tắc Interface Segregation, và khi viết test chỉ cần inject mock implementation vào constructor là xong.
Chain stages qua channels — mỗi stage là goroutine, data chảy qua channels.
// Stage 1: generator
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
// Stage 2: transform
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
// Stage 3: filter
func evens(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
if n%2 == 0 {
out <- n
}
}
close(out)
}()
return out
}
// Compose pipeline
func main() {
for result := range evens(square(gen(1, 2, 3, 4, 5))) {
fmt.Println(result) // 4, 16
}
}Fan-out: nhiều goroutines đọc cùng channel.
Fan-in: merge nhiều channels thành 1.
Khi đưa ứng dụng Go lên production, cần đảm bảo: structured logging (slog, zap, hoặc zerolog) dạng JSON dễ parse, graceful shutdown để không mất request đang xử lý, health check endpoints cho load balancer.
Về config, đọc từ environment variables (viper hoặc envconfig). Dockerfile dùng multi-stage build với scratch hoặc distroless để giảm attack surface và image size xuống 5-15MB. Chất lượng code: race detector (go test -race) trong CI và golangci-lint để catch lỗi sớm.
Luôn propagate context xuyên suốt request chain, cấu hình connection pooling hợp lý cho database, và expose metrics qua Prometheus để monitoring hiệu năng.
Cách tối ưu nhất là dùng multi-stage build: stage đầu tiên sử dụng image golang:1.22-alpine để compile source thành binary, stage thứ hai chỉ dùng scratch (image rỗng hoàn toàn) hoặc gcr.io/distroless/static rồi copy binary vào, cho ra final image chỉ 5-15MB thay vì hơn 1GB của golang base image.
- Để binary chạy được trên scratch image, cần set
CGO_ENABLED=0khi build để tạo static binary không phụ thuộc vào thư viện C bên ngoài. - Nên tách layer
COPY go.mod go.sumvàRUN go mod downloadriêng trước khi copy source code, để Docker cache lại dependency layer và chỉ rebuild khi dependency thay đổi, giúp tăng tốc build đáng kể.
Trong Go generics, constraint là interface xác định tập hợp types (type set) được chấp nhận. comparable là built-in constraint cho phép dùng == và != — cần thiết khi muốn dùng type parameter làm map key.
// comparable — cho phép dùng == và làm map key
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item {
return true
}
}
return false
}
// Type set constraint — chỉ nhận numeric types
type Number interface {
int | int8 | int16 | int32 | int64 | float32 | float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// Union với ~ (underlying type)
type Integer interface {
~int | ~int64 // ~ nghĩa là "kiểu có underlying type là int/int64"
}
// Lồng constraints
type Ordered interface {
~int | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b {
return a
}
return b
}~T cho phép custom types dựa trên underlying type tham gia constraint, ví dụ type MyInt int vẫn thỏa ~int.
Sentinel error là biến error toàn cục được định nghĩa sẵn, phù hợp cho các điều kiện lỗi đơn giản không cần thêm context. Custom error type phù hợp khi cần mang thêm metadata về lỗi.
// Sentinel errors — so sánh bằng errors.Is
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrConflict = errors.New("already exists")
)
// Custom error type — truy xuất metadata qua errors.As
type AppError struct {
Code int
Message string
Err error // wrapped underlying error
}
func (e *AppError) Error() string {
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
func (e *AppError) Unwrap() error { return e.Err } // quan trọng cho error chain
func getUser(id int) (*User, error) {
user, err := db.Find(id)
if err != nil {
return nil, &AppError{Code: 404, Message: "user not found", Err: ErrNotFound}
}
return user, nil
}
// Caller có thể check cả hai cách
err := getUser(99)
errors.Is(err, ErrNotFound) // true — nhờ Unwrap()
var ae *AppError
if errors.As(err, &ae) {
fmt.Println(ae.Code) // 404
}Nguyên tắc: định nghĩa errors trong package, export chúng để caller dùng errors.Is.
Tránh dùng err.Error() == "..." string comparison.
Observability gồm 3 trụ cột: logs, metrics, traces.
Prometheus là tiêu chuẩn de facto cho metrics trong Go.
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests by status and method",
},
[]string{"method", "status"},
)
httpDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request latency",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path"},
)
)
func init() {
prometheus.MustRegister(httpRequests, httpDuration)
}
// Middleware ghi metrics
func metricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rw := &responseWriter{ResponseWriter: w, statusCode: 200}
next.ServeHTTP(rw, r)
duration := time.Since(start).Seconds()
httpRequests.WithLabelValues(r.Method, fmt.Sprint(rw.statusCode)).Inc()
httpDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
})
}
// Expose /metrics endpoint
mux.Handle("/metrics", promhttp.Handler())4 loại metrics: Counter (chỉ tăng), Gauge (tăng/giảm), Histogram (distribution + percentiles), Summary (similar Histogram).
Scrape bởi Prometheus server → visualize với Grafana.
gofmt là formatter chính thức của Go, chuẩn hóa indentation (tab), spacing, và cú pháp. Không có option cấu hình — một chuẩn duy nhất cho toàn bộ cộng đồng Go. Chạy: gofmt -w .
goimports = gofmt + tự động thêm/xóa import statements. Đây là công cụ được khuyến nghị dùng trong editor (VS Code, GoLand) thay thế gofmt.
goimports -w . # format + fix imports
goimports -l . # chỉ list files cần fixgolangci-lint là meta-linter chạy hàng chục linters song song: staticcheck (bugs tiềm ẩn), errcheck (bỏ sót check error), govet (misuse of sync primitives), revive (code style), gosec (security), v.v.
golangci-lint run ./...
golangci-lint run --fix ./... # tự fix một số issuesCấu hình qua .golangci.yml:
linters:
enable:
- errcheck
- staticcheck
- gosimple
- unused
- reviveBest practice: tích hợp goimports vào editor (save hook), chạy golangci-lint trong CI pipeline và fail build nếu có lỗi.