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

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ã.
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.
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).
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.
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).
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.
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.
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")
}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ộ).
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.
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ệ.
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.
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.
Sẵn sàng chinh phục phỏng vấn Go?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
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.
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.
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")
}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.
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.
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.
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.
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ẽ.
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.
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.
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/opBenchmark 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.
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ó.
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# Updating dependencies
go get -u ./... # All dependencies
go get -u=patch ./... # Patches onlyTệ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.mdThư 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.
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 structsBiế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).
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.
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 operationsHã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.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Phỏng vấn kỹ thuật Go: Goroutine, Channel và Concurrency
Các câu hỏi phỏng vấn kỹ thuật Go về goroutine, channel và các pattern concurrency. Ví dụ code, bẫy thường gặp và câu trả lời chuyên gia để chuẩn bị phỏng vấn kỹ thuật Go năm 2026.

Tính Đồng Thời trong Go: Goroutine và Channel - Hướng Dẫn Hoàn Chỉnh
Làm chủ tính đồng thời trong Go với goroutine và channel. Các mẫu nâng cao, đồng bộ hóa, câu lệnh select và các phương pháp hay nhất với ví dụ mã chi tiết.

Go: Kiến thức cơ bản cho lập trình viên Java/Python năm 2026
Học Go nhanh chóng bằng cách tận dụng kinh nghiệm Java hoặc Python. Goroutine, channel, interface và các pattern thiết yếu cho quá trình chuyển đổi suôn sẻ.