# Top 25 câu hỏi phỏng vấn Go: hướng dẫn dành cho nhà phát triển > Chinh phục buổi phỏng vấn Go với 25 câu hỏi được hỏi nhiều nhất. Goroutine, channel, interface và mẫu đồng thời kèm ví dụ mã. - Published: 2026-02-06 - Updated: 2026-04-28 - Author: SharpSkill - Tags: go, golang, interview, concurrency, goroutines - Reading time: 18 min --- Phỏng vấn kỹ thuật Go đánh giá khả năng nắm vững các khái niệm cốt lõi của ngôn ngữ: tính đồng thời, quản lý bộ nhớ và các mẫu thiết kế đặc trưng. Hướng dẫn này tập hợp 25 câu hỏi được hỏi nhiều nhất kèm câu trả lời chi tiết và ví dụ mã. > **Lời khuyên cho buổi phỏng vấn** > > Go đề cao sự đơn giản và dễ đọc. Người phỏng vấn ưu tiên những câu trả lời ngắn gọn nhưng thể hiện hiểu biết sâu hơn là các giải pháp phức tạp quá mức. ## Nền tảng ngôn ngữ Go ### 1. `var` và `:=` khác nhau ở điểm nào? Khai báo `var` cho phép chỉ định kiểu dữ liệu rõ ràng và hoạt động ở cấp package. Toán tử `:=` tự suy luận kiểu nhưng chỉ dùng được bên trong các hàm. ```go // declaration.go package main // Package level - var required var globalConfig = "production" func main() { // var with explicit type var count int = 10 // var with type inference var name = "Alice" // Short declaration - functions only age := 25 // Multiple declarations var ( host = "localhost" port = 8080 ) } ``` Trong hàm, khai báo ngắn `:=` được ưa chuộng nhờ tính cô đọng, còn `var` vẫn cần thiết cho các biến cấp package. ### 2. Hệ thống kiểu của Go hoạt động như thế nào? Go dùng kiểu tĩnh kết hợp suy luận kiểu. Ngôn ngữ phân biệt kiểu giá trị (sao chép khi gán) với kiểu tham chiếu (chia sẻ cùng cấu trúc nền tảng). ```go // types.go package main import "fmt" func main() { // Value types - full copy a := [3]int{1, 2, 3} b := a // Copies the array b[0] = 100 // Doesn't modify a fmt.Println(a) // [1 2 3] // Reference types - share data slice1 := []int{1, 2, 3} slice2 := slice1 // Same underlying array slice2[0] = 100 // Also modifies slice1 fmt.Println(slice1) // [100 2 3] // Maps are also references m1 := map[string]int{"a": 1} m2 := m1 m2["a"] = 100 fmt.Println(m1["a"]) // 100 } ``` Mảng là kiểu giá trị, còn slice, map và channel là kiểu tham chiếu. ### 3. Phân biệt giữa mảng và slice Mảng có kích thước cố định được xác định lúc biên dịch. Slice là góc nhìn động trên mảng nền với ba thành phần: con trỏ, độ dài và sức chứa. ```go // arrays_slices.go package main import "fmt" func main() { // Array - fixed size, value type arr := [5]int{1, 2, 3, 4, 5} // Slice - view over the array slice := arr[1:4] // [2 3 4] fmt.Printf("len=%d, cap=%d\n", len(slice), cap(slice)) // len=3, cap=4 // Modifications affect original array slice[0] = 20 fmt.Println(arr) // [1 20 3 4 5] // Direct creation with make dynamic := make([]int, 3, 10) // len=3, cap=10 // Append may reallocate dynamic = append(dynamic, 1, 2, 3, 4, 5) } ``` Trong Go, slice là kiểu được ưa chuộng cho các tập hợp động. ### 4. Câu lệnh `defer` hoạt động ra sao? `defer` lên lịch một lời gọi hàm để chạy ở cuối hàm bao quanh. Các lời gọi bị hoãn xếp chồng và được thực thi theo thứ tự LIFO (vào sau, ra trước). ```go // defer.go package main import ( "fmt" "os" ) func main() { // LIFO order defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3") // Prints: 3, 2, 1 } // Typical use case: resource cleanup func readFile(path string) ([]byte, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() // Always executes // Read file... return os.ReadFile(path) } // Caution: arguments are evaluated immediately func deferArgs() { x := 10 defer fmt.Println(x) // Captures 10 x = 20 // Prints: 10 } ``` `defer` đảm bảo việc thực thi ngay cả khi xảy ra panic, vì vậy rất phù hợp cho việc dọn dẹp tài nguyên. ### 5. Interface trong Go là gì? Interface mô tả một tập hợp method. Bất kỳ kiểu nào triển khai các method đó đều mặc nhiên thỏa mãn interface, không cần khai báo tường minh. ```go // interfaces.go package main import "fmt" // Interface definition type Writer interface { Write([]byte) (int, error) } // Type that implicitly implements Writer type FileLogger struct { path string } func (f *FileLogger) Write(data []byte) (int, error) { // Write to file fmt.Println("Writing to", f.path) return len(data), nil } // Empty interface - accepts any type func printAny(v interface{}) { fmt.Printf("Type: %T, Value: %v\n", v, v) } // Type assertion func process(w Writer) { // Type check if fl, ok := w.(*FileLogger); ok { fmt.Println("FileLogger with path:", fl.path) } } ``` Việc triển khai interface ngầm định giúp các package được tách rời mạnh mẽ. ## Tính đồng thời và goroutine ### 6. Goroutine là gì và khác thread thế nào? Goroutine là một thread nhẹ do runtime của Go quản lý. Nó chiếm vài KB stack (so với vài MB của thread hệ thống) và bộ lập lịch của Go ghép kênh hàng nghìn goroutine vào một số ít thread hệ thống. ```go // goroutines.go package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup // Launch 1000 goroutines for i := 0; i < 1000; i++ { wg.Add(1) go func(id int) { defer wg.Done() time.Sleep(100 * time.Millisecond) fmt.Printf("Goroutine %d finished\n", id) }(i) // Pass i by value } wg.Wait() fmt.Println("All goroutines completed") } ``` > **Cạm bẫy phổ biến** > > Biến lặp luôn nên được truyền theo giá trị tới goroutine. Nếu không, mọi goroutine có thể giữ lại cùng một giá trị cuối cùng. ### 7. Channel hoạt động ra sao? Channel tạo ra cơ chế giao tiếp và đồng bộ giữa các goroutine. Chúng có thể có buffer (kèm sức chứa) hoặc không buffer (đồng bộ). ```go // channels.go package main import "fmt" func main() { // Unbuffered channel - blocks until received ch := make(chan int) go func() { ch <- 42 // Blocks until read }() value := <-ch // Receives value fmt.Println(value) // Buffered channel - doesn't block until full buffered := make(chan string, 2) buffered <- "first" buffered <- "second" // buffered <- "third" // Would block fmt.Println(<-buffered) // "first" fmt.Println(<-buffered) // "second" } ``` Channel không buffer đảm bảo đồng bộ, còn channel có buffer cho phép tách rời theo thời gian giữa bên gửi và bên nhận. ### 8. Cách dùng `select` với nhiều channel? `select` chờ đồng thời nhiều thao tác trên channel. Thao tác sẵn sàng đầu tiên sẽ được thực hiện; nếu nhiều thao tác sẵn sàng cùng lúc, lựa chọn diễn ra ngẫu nhiên. ```go // select.go package main import ( "fmt" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(100 * time.Millisecond) ch1 <- "from ch1" }() go func() { time.Sleep(200 * time.Millisecond) ch2 <- "from ch2" }() // Wait with timeout for i := 0; i < 2; i++ { select { case msg := <-ch1: fmt.Println(msg) case msg := <-ch2: fmt.Println(msg) case <-time.After(500 * time.Millisecond): fmt.Println("Timeout") } } // Non-blocking select with default select { case msg := <-ch1: fmt.Println(msg) default: fmt.Println("No message available") } } ``` `select` là công cụ cốt lõi để quản lý tính đồng thời trong Go một cách tinh tế. ### 9. Làm sao tránh race condition? Race condition xảy ra khi nhiều goroutine truy cập dữ liệu chung mà không đồng bộ. Go cung cấp nhiều cơ chế bảo vệ. ```go // race_conditions.go package main import ( "fmt" "sync" "sync/atomic" ) // Solution 1: Mutex type SafeCounter struct { mu sync.Mutex count int } func (c *SafeCounter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.count++ } // Solution 2: RWMutex for read-heavy workloads type Cache struct { mu sync.RWMutex data map[string]string } func (c *Cache) Get(key string) string { c.mu.RLock() // Multiple readers allowed defer c.mu.RUnlock() return c.data[key] } func (c *Cache) Set(key, value string) { c.mu.Lock() // Single writer defer c.mu.Unlock() c.data[key] = value } // Solution 3: atomic for simple counters var atomicCounter int64 func incrementAtomic() { atomic.AddInt64(&atomicCounter, 1) } func main() { // Detection: go run -race main.go counter := SafeCounter{} var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Println("Count:", counter.count) } ``` Cờ trình biên dịch `-race` phát hiện race condition lúc chạy. ### 10. Hãy giải thích mẫu worker pool Mẫu worker pool giới hạn tính đồng thời bằng cách tạo một số lượng cố định goroutine xử lý các tác vụ từ một hàng đợi. ```go // worker_pool.go package main import ( "fmt" "sync" "time" ) func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { fmt.Printf("Worker %d processing job %d\n", id, job) time.Sleep(100 * time.Millisecond) // Simulate work results <- job * 2 } } func main() { const numJobs = 10 const numWorkers = 3 jobs := make(chan int, numJobs) results := make(chan int, numJobs) var wg sync.WaitGroup // Start workers for w := 1; w <= numWorkers; w++ { wg.Add(1) go worker(w, jobs, results, &wg) } // Send jobs for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs) // Wait and close results go func() { wg.Wait() close(results) }() // Collect results for result := range results { fmt.Println("Result:", result) } } ``` Mẫu này giúp tránh tốn bộ nhớ và CPU do sinh ra quá nhiều goroutine. ## Xử lý lỗi và panic/recover ### 11. Xử lý lỗi trong Go diễn ra thế nào? Go dùng giá trị trả về tường minh cho lỗi, không có exception. Theo quy ước, error là tham số trả về cuối cùng. ```go // errors.go package main import ( "errors" "fmt" ) // Sentinel errors for comparison var ( ErrNotFound = errors.New("resource not found") ErrUnauthorized = errors.New("access unauthorized") ) // Custom error type type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation %s: %s", e.Field, e.Message) } func validateAge(age int) error { if age < 0 { return &ValidationError{ Field: "age", Message: "must be positive", } } return nil } func main() { // Basic check if err := validateAge(-5); err != nil { // Type assertion for custom error var valErr *ValidationError if errors.As(err, &valErr) { fmt.Printf("Field: %s\n", valErr.Field) } } // Sentinel error comparison err := findUser("unknown") if errors.Is(err, ErrNotFound) { fmt.Println("User not found") } } func findUser(id string) error { // Error wrapping with context return fmt.Errorf("findUser %s: %w", id, ErrNotFound) } ``` Bao bọc với `%w` xâu chuỗi các lỗi mà vẫn cho phép kiểm tra lỗi gốc. ### 12. Khi nào dùng panic và recover? `panic` chấm dứt luồng bình thường và mở stack ngược trở lại. `recover` bắt panic trong một `defer` và cho phép tiếp tục thực thi. ```go // panic_recover.go package main import "fmt" func safeOperation() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered from panic: %v", r) } }() riskyOperation() return nil } func riskyOperation() { // Simulates an operation that can panic panic("something went wrong") } // Legitimate use case: initialization validation func MustCompileRegex(pattern string) *Regexp { r, err := regexp.Compile(pattern) if err != nil { panic(err) // Programming error } return r } func main() { err := safeOperation() if err != nil { fmt.Println("Recovered error:", err) } fmt.Println("Program continues") } ``` > **Quy tắc vàng** > > Panic chỉ nên dành cho lỗi lập trình (vi phạm bất biến). Với lỗi đã lường trước (thiếu file, lỗi mạng), tốt nhất là luôn trả về error. ## Struct, method và embedding ### 13. Receiver theo giá trị và theo con trỏ khác nhau ra sao? Receiver theo giá trị nhận một bản sao của struct, còn receiver theo con trỏ nhận một tham chiếu và có thể thay đổi bản gốc. ```go // receivers.go package main import "fmt" type Counter struct { value int } // Value receiver - works on copy func (c Counter) GetValue() int { return c.value } // Pointer receiver - modifies original func (c *Counter) Increment() { c.value++ } // Pointer receiver for large structs (avoids copy) type LargeStruct struct { data [1000]int } func (l *LargeStruct) Process() { // Avoids copying 8000 bytes } func main() { c := Counter{value: 0} c.Increment() // Go automatically converts fmt.Println(c.GetValue()) // 1 // Careful with interfaces var _ fmt.Stringer = &c // OK if method on *Counter } ``` Quy tắc: nếu một method dùng receiver theo con trỏ, mọi method khác của kiểu cũng nên dùng receiver theo con trỏ để bảo đảm nhất quán. ### 14. Embedding trong Go hoạt động ra sao? Embedding nhúng một kiểu vào kiểu khác và kế thừa method cùng field. Đây không phải kế thừa cổ điển mà là composition. ```go // embedding.go package main import "fmt" type Logger struct { prefix string } func (l *Logger) Log(msg string) { fmt.Printf("[%s] %s\n", l.prefix, msg) } // Embedding Logger type Service struct { *Logger // Pointer embedding name string } func NewService(name string) *Service { return &Service{ Logger: &Logger{prefix: name}, name: name, } } func main() { svc := NewService("API") // Promoted method - direct access svc.Log("Starting") // Explicit access also works svc.Logger.Log("Explicit") // Promoted field fmt.Println(svc.prefix) // "API" } ``` Embedding mang lại sự linh hoạt khi tổ hợp các kiểu mà tránh được sự cứng nhắc của kế thừa. ### 15. Triển khai mẫu singleton trong Go thế nào? Package `sync` cung cấp `sync.Once` để bảo đảm việc khởi tạo chỉ chạy một lần ngay cả với nhiều goroutine đồng thời. ```go // singleton.go package main import ( "fmt" "sync" ) type Database struct { connectionString string } var ( instance *Database once sync.Once ) func GetDatabase() *Database { once.Do(func() { fmt.Println("Single initialization") instance = &Database{ connectionString: "postgres://...", } }) return instance } func main() { // Concurrent calls - single initialization var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() db := GetDatabase() fmt.Printf("Instance: %p\n", db) }() } wg.Wait() } ``` `sync.Once` an toàn trong môi trường đa luồng và gọn gàng hơn so với mutex kết hợp double-check locking. ## Context và hủy bỏ ### 16. Package context phục vụ gì? Package `context` quản lý deadline, tín hiệu hủy và các giá trị gắn với một request xuyên suốt cây gọi hàm. ```go // context.go package main import ( "context" "fmt" "time" ) func main() { // Context with timeout ctx, cancel := context.WithTimeout( context.Background(), 2*time.Second, ) defer cancel() // Always call cancel result := make(chan string, 1) go func() { // Simulate long operation time.Sleep(3 * time.Second) result <- "completed" }() select { case res := <-result: fmt.Println(res) case <-ctx.Done(): fmt.Println("Timeout:", ctx.Err()) } } // Propagation through functions func fetchData(ctx context.Context, url string) ([]byte, error) { // Early check if ctx.Err() != nil { return nil, ctx.Err() } req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err } // HTTP client respects context resp, err := http.DefaultClient.Do(req) // ... } ``` Mọi hàm có thể chạy lâu nên nhận `context.Context` ở vị trí tham số đầu tiên. ### 17. Làm sao thực hiện graceful shutdown cho chương trình? Các tín hiệu hệ thống như SIGINT và SIGTERM có thể được bắt để cho phép tắt chương trình một cách sạch sẽ. ```go // graceful_shutdown.go package main import ( "context" "fmt" "os" "os/signal" "syscall" "time" ) func main() { // Context cancelled on signal ctx, stop := signal.NotifyContext( context.Background(), syscall.SIGINT, syscall.SIGTERM, ) defer stop() // Start server server := startServer() // Wait for signal <-ctx.Done() fmt.Println("\nShutting down...") // Timeout for graceful shutdown shutdownCtx, cancel := context.WithTimeout( context.Background(), 5*time.Second, ) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { fmt.Println("Shutdown error:", err) } fmt.Println("Shutdown complete") } ``` Mẫu này đảm bảo các kết nối đang hoạt động được hoàn tất đúng cách trước khi dừng hẳn. ## Test và benchmark ### 18. Viết test trong Go bằng cách nào? Package có sẵn `testing` cung cấp các chức năng cơ bản. Test nằm trong các file `*_test.go`. ```go // calculator_test.go package calculator import "testing" func TestAdd(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Add(2, 3) = %d; want 5", result) } } // Table-driven tests func TestAddTableDriven(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive", 2, 3, 5}, {"negative", -1, -1, -2}, {"mixed", -1, 5, 4}, {"zero", 0, 0, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }) } } ``` Table-driven test là mẫu đặc trưng của Go để kiểm thử nhiều trường hợp cùng lúc. ### 19. Viết benchmark thế nào? Benchmark dùng `testing.B` và chạy bằng `go test -bench`. ```go // benchmark_test.go package main import ( "strings" "testing" ) func BenchmarkStringConcat(b *testing.B) { for i := 0; i < b.N; i++ { var s string for j := 0; j < 100; j++ { s += "a" } } } func BenchmarkStringBuilder(b *testing.B) { for i := 0; i < b.N; i++ { var sb strings.Builder for j := 0; j < 100; j++ { sb.WriteString("a") } _ = sb.String() } } // Typical results: // BenchmarkStringConcat-8 50000 28000 ns/op // BenchmarkStringBuilder-8 1000000 1200 ns/op ``` Benchmark phơi bày sự khác biệt về hiệu năng giữa các phương án. ## Generics (Go 1.18+) ### 20. Cách dùng generics trong Go? Go 1.18 giới thiệu tham số kiểu, cho phép viết mã tổng quát mà vẫn giữ tính an toàn về kiểu. ```go // generics.go package main import "fmt" // Generic function func Map[T, U any](slice []T, fn func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = fn(v) } return result } // Custom type constraint type Number interface { int | int64 | float64 } func Sum[T Number](values []T) T { var sum T for _, v := range values { sum += v } return sum } // Generic type type Stack[T any] struct { items []T } func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) } func (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item, true } func main() { // Usage doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 }) fmt.Println(doubled) // [2 4 6] fmt.Println(Sum([]int{1, 2, 3, 4, 5})) // 15 stack := &Stack[string]{} stack.Push("hello") stack.Push("world") val, _ := stack.Pop() fmt.Println(val) // "world" } ``` Generics loại bỏ nhu cầu sao chép mã hoặc dùng `interface{}`. ## Module và phụ thuộc ### 21. Hệ thống module Go vận hành thế nào? Module Go quản lý phụ thuộc với phiên bản hóa theo ngữ nghĩa. Tệp `go.mod` định nghĩa module và các phụ thuộc của nó. ```go // go.mod example module github.com/user/myproject go 1.21 require ( github.com/gin-gonic/gin v1.9.1 github.com/lib/pq v1.10.9 ) // Essential commands: // go mod init github.com/user/project // go mod tidy - clean dependencies // go get package@v1.2.3 - add/update // go mod vendor - copy locally ``` ```bash # Updating dependencies go get -u ./... # All dependencies go get -u=patch ./... # Patches only ``` Tệp `go.sum` chứa checksum mật mã để bảo đảm tính toàn vẹn của phụ thuộc. ### 22. Cách tổ chức một dự án Go? Cấu trúc chuẩn dựa trên quy ước cộng đồng và không áp đặt quy tắc cứng nhắc. ``` myproject/ ├── cmd/ │ └── api/ │ └── main.go # Entry point ├── internal/ # Private to module │ ├── handler/ │ ├── service/ │ └── repository/ ├── pkg/ # Reusable external code ├── go.mod ├── go.sum └── README.md ``` Thư mục `internal` là đặc biệt: nội dung của nó không thể import bởi các module khác. ## Các câu hỏi nâng cao ### 23. Garbage collector trong Go vận hành ra sao? Go dùng garbage collector dạng tricolor mark-and-sweep, chạy đồng thời và được tối ưu cho độ trễ thấp. ```go // gc_optimization.go package main import "runtime" func main() { // GC configuration // GOGC=100 (default) - triggers GC when heap doubles // Force GC runtime.GC() // Memory statistics var stats runtime.MemStats runtime.ReadMemStats(&stats) println("Alloc:", stats.Alloc) println("NumGC:", stats.NumGC) println("PauseTotalNs:", stats.PauseTotalNs) } // Optimization techniques // 1. Reuse allocations with sync.Pool // 2. Pre-allocate slices with make([]T, 0, cap) // 3. Avoid repeated string/[]byte conversions // 4. Use pointers for large structs ``` Biến môi trường `GODEBUG=gctrace=1` hiển thị nhật ký GC. ### 24. Hãy giải thích bộ lập lịch của Go Bộ lập lịch Go dùng mô hình M:N, ánh xạ N goroutine lên M thread hệ thống với ba thực thể: G (goroutine), M (thread) và P (bộ xử lý logic). ```go // scheduler.go package main import ( "fmt" "runtime" ) func main() { // Number of logical processors (P) fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // Number of active goroutines fmt.Println("NumGoroutine:", runtime.NumGoroutine()) // Yield processor to other goroutines runtime.Gosched() // M:P:G model // - G: goroutine (lightweight stack ~2KB) // - M: OS thread (machine) // - P: logical processor (execution context) // // Each P has a local queue of Gs // Work stealing when queue is empty } ``` Từ Go 1.14, bộ lập lịch hỗ trợ giành lại CPU (preemptive), tránh việc một goroutine chiếm trọn một P. ### 25. Cách tối ưu hiệu năng trong Go? Việc tối ưu bắt đầu từ profiling để xác định nút thắt cổ chai. ```go // profiling.go package main import ( "os" "runtime/pprof" ) func main() { // CPU profiling f, _ := os.Create("cpu.prof") pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() // Code to profile... // Memory profiling mf, _ := os.Create("mem.prof") defer mf.Close() pprof.WriteHeapProfile(mf) } // Analysis: go tool pprof cpu.prof // Common optimization techniques: // 1. Avoid allocations in hot loops // 2. Use sync.Pool for reusable objects // 3. Prefer []byte over string for mutations // 4. Use bufio for I/O // 5. Batch database operations ``` > **Quy tắc tối ưu** > > Hãy đo lường trước khi tối ưu. Profiling thường lộ ra những nút thắt cổ chai bất ngờ. ## Kết luận 25 câu hỏi này bao quát các khái niệm cơ bản được đánh giá trong phỏng vấn Go: **Danh mục chuẩn bị:** - ✅ Thành thạo goroutine và channel - ✅ Hiểu interface ngầm định - ✅ Xử lý lỗi đặc trưng của Go - ✅ Sử dụng context đúng cách - ✅ Mẫu đồng thời (mutex, worker pool) - ✅ Test và benchmark - ✅ Hiểu generics từ Go 1.18+ Chìa khóa thành công cho buổi phỏng vấn Go: thể hiện khả năng đánh đổi giữa sự đơn giản và hiệu năng, đồng thời biết khi nào nên dùng mẫu đồng thời nào. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/go/top-25-go-interview-questions