Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

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ư 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.

go
// 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ỗ.
go
// 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.
go
// Đị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 User

int là pointer type, &x lấy address, p dereference.

Go không có pointer arithmetic như C. nil pointer gây panic.

go
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:

  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.

Stdlib đủ mạnh cho production.

Không bắt buộc framework. http.ServeMux là default router.

go
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 vet phát hiện các lỗi tiềm ẩn mà compiler không bắt được, còn golangci-lint là 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 mod cho quản lý dependency, go generate cho 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"]ok là 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.Map hoặc bọc bằng mutex.
  • Zero value là nil: map nil chỉ đọc được, ghi sẽ panic — phải make hoặ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 đương for 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.
go
// 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) // true

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.

go
// 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ứ 1

Trong 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).

go
// Đị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.

go
// 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.
go
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ểu type 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.
go
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.
go
// 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. default chạ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.

go
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.

go
// 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 group

Go sử dụng package encoding/json.

Struct tags quyết định cách ánh xạ field sang JSON key.

go
// Đị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.
go
// 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.

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.

go
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)
    }
}
bash
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.prof rồi mở bằng go 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 trace cho 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.

go
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).

go
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ì %w

errors.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.

go
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.

go
// 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.

bash
# 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:18

Overhead: ~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.