← Back to DevBytes

Top 50 Go Interview Questions for Senior Developers

Top 50 Go Interview Questions for Senior Developers

Go (Golang) has become one of the most popular languages for building scalable backend systems, cloud-native applications, and distributed services. For senior developers, Go interviews go far beyond basic syntax β€” they test deep understanding of concurrency models, memory management, the runtime, interface design, and production-grade engineering decisions. This tutorial walks through the 50 most important Go interview questions, complete with practical code examples, explanations, and best practices.

Why This Matters

Senior Go developers are expected to reason about goroutine lifecycle, garbage collection pauses, channel semantics, escape analysis, and the trade-offs between value and pointer semantics. Interviewers want to see not just whether you can write Go, but whether you understand why Go behaves the way it does. Mastering these questions prepares you for system design discussions, debugging scenarios, and architectural decisions in real-world codebases.

Section 1: Language Fundamentals & Internals

1. What is the difference between var, short variable declaration :=, and const?

var declares a variable with an explicit type (or inferred type), usable at package and function scope. := is short declaration syntax, only valid inside functions, and requires at least one new variable on the left side. const declares compile-time constants that cannot be addresses and must be of basic types.

package main

import "fmt"

const Pi = 3.14159

func main() {
    var x int = 10
    y := 20 // short declaration
    x, z := 30, 40 // x is reassigned, z is new
    fmt.Println(x, y, z)
}

2. What is the zero value in Go, and why does it matter?

Every type in Go has a zero value that is assigned automatically when a variable is declared without initialization. This eliminates entire classes of uninitialized-memory bugs common in C. For numeric types it is 0, for strings it is "", for booleans false, for pointers, slices, maps, channels, functions, and interfaces it is nil.

type Config struct {
    Port    int
    Host    string
    Enabled bool
    Tags    []string
}

func main() {
    var c Config
    // Port=0, Host="", Enabled=false, Tags=nil
    fmt.Printf("%+v\n", c)
}

3. How does Go handle multiple return values, and what is the idiomatic pattern?

Go functions can return multiple values. The idiomatic pattern is to return a value and an error, with the error as the last return value. This is a core convention across the standard library.

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result)
}

4. What is the difference between arrays and slices?

Arrays in Go are fixed-size value types β€” assigning or passing an array copies all elements. Slices are dynamic, reference-based views over an underlying array. A slice header contains a pointer to the backing array, a length, and a capacity.

func main() {
    arr := [3]int{1, 2, 3} // array
    s := arr[:]            // slice referencing arr

    s2 := make([]int, 0, 5) // slice with capacity 5
    s2 = append(s2, 1, 2, 3)

    fmt.Println(len(s2), cap(s2)) // 3 5
}

5. What happens when a slice grows beyond its capacity?

When append exceeds capacity, Go allocates a new, larger backing array, copies the old elements, and returns a new slice header. The growth strategy roughly doubles capacity for small slices and grows by ~25% for larger ones, though this is an implementation detail that has changed across Go versions.

func main() {
    s := make([]int, 0)
    prevCap := cap(s)
    for i := 0; i < 20; i++ {
        s = append(s, i)
        if cap(s) != prevCap {
            fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
            prevCap = cap(s)
        }
    }
}

6. Explain the difference between len() and cap() for slices.

len() returns the number of elements currently in the slice. cap() returns the capacity β€” the number of elements in the underlying array starting from the slice's first element. A slice can be resliced up to its capacity without allocation.

7. What is a string in Go internally?

A Go string is a read-only slice of bytes β€” a header containing a pointer to byte data and a length. Strings are immutable. To modify a string, you convert it to a []byte or []rune, modify, and convert back. For UTF-8 text, []rune handles multi-byte code points correctly.

func main() {
    s := "Hello, δΈ–η•Œ"
    fmt.Println(len(s))         // 13 (bytes, not runes)
    fmt.Println(len([]rune(s))) // 9 (runes)
}

8. What is the difference between string and []byte?

Strings are immutable; byte slices are mutable. Converting between them copies data (with an optimization that avoids copying in certain compiler-recognized cases). Use []byte when you need to modify bytes; use string for textual identifiers, map keys, and immutability guarantees.

Section 2: Concurrency

9. What is a goroutine, and how does it differ from an OS thread?

A goroutine is a lightweight user-space thread managed by the Go runtime. Goroutines start with a small stack (typically 2KB) that grows and shrinks dynamically. The Go scheduler multiplexes goroutines onto a smaller number of OS threads using M:N scheduling. This makes it practical to run millions of goroutines, whereas thousands of OS threads would exhaust resources.

func main() {
    for i := 0; i < 100000; i++ {
        go func(id int) {
            // do work
        }(i)
    }
    time.Sleep(time.Second)
}

10. Explain the Go scheduler (GMP model).

The Go scheduler uses three core abstractions: G (goroutine), M (machine, an OS thread), and P (processor, a logical processor holding a local run queue). The number of Ps is controlled by GOMAXPROCS. Each P has a local queue of runnable goroutines. When a P's queue is empty, it steals work from other Ps (work stealing). This design minimizes lock contention and maximizes CPU utilization.

11. What are channels, and what are buffered vs. unbuffered channels?

Channels are typed conduits for communication between goroutines. An unbuffered channel synchronizes sender and receiver β€” the send blocks until a receiver is ready. A buffered channel allows sends up to the buffer size without blocking.

func main() {
    // Unbuffered: synchronous
    ch1 := make(chan int)

    // Buffered: asynchronous up to capacity
    ch2 := make(chan int, 3)
    ch2 <- 1
    ch2 <- 2
    fmt.Println(len(ch2), cap(ch2)) // 2 3
}

12. What happens when you send on a closed channel?

Sending on a closed channel causes a panic. Receiving from a closed channel returns the zero value immediately (with ok returning false). This is why the convention is that the sender closes the channel, never the receiver.

func main() {
    ch := make(chan int, 1)
    close(ch)
    v, ok := <-ch
    fmt.Println(v, ok) // 0 false

    // ch <- 1 // PANIC: send on closed channel
}

13. How do you gracefully shut down goroutines?

The idiomatic approach is to use a context.Context with cancellation, or a done channel. Goroutines select on the cancellation signal and exit cleanly.

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("worker %d shutting down\n", id)
            return
        default:
            // do work
            time.Sleep(100 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    for i := 0; i < 3; i++ {
        go worker(ctx, i)
    }
    time.Sleep(500 * time.Millisecond)
    cancel()
    time.Sleep(100 * time.Millisecond)
}

14. What is a goroutine leak, and how do you prevent it?

A goroutine leak occurs when a goroutine blocks forever (e.g., waiting on a channel nobody will send to) and cannot exit. Over time, leaked goroutines accumulate, consuming memory and scheduler resources. Prevent leaks by always providing a cancellation path via context, ensuring every channel send has a corresponding receiver, and using tools like pprof to inspect goroutine counts.

15. What is the select statement, and how does it work?

select lets a goroutine wait on multiple channel operations simultaneously. It blocks until one case is ready, then executes it. If multiple cases are ready, one is chosen at random. A default case makes it non-blocking.

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"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        }
    }
}

16. What is the difference between sync.Mutex and sync.RWMutex?

Mutex provides exclusive locking β€” only one goroutine can hold the lock at a time. RWMutex allows multiple concurrent readers or one exclusive writer. Use RWMutex when reads vastly outnumber writes and the critical section is non-trivial. For short critical sections, a plain Mutex is often faster due to lower overhead.

type SafeCounter struct {
    mu    sync.RWMutex
    count int
}

func (c *SafeCounter) Get() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.count
}

func (c *SafeCounter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

17. When should you use sync.WaitGroup?

Use WaitGroup when you need to wait for a collection of goroutines to finish. Always call Add before launching the goroutine, and Done inside it (typically via defer).

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("task %d done\n", id)
        }(i)
    }
    wg.Wait()
}

18. What is sync.Once, and when would you use it?

sync.Once guarantees a function executes exactly once, even under concurrent access. It is the idiomatic way to implement lazy initialization of singletons.

var (
    instance *Database
    once     sync.Once
)

func GetDB() *Database {
    once.Do(func() {
        instance = &Database{conn: connect()}
    })
    return instance
}

19. What is sync.Pool, and what are its use cases?

sync.Pool is a set of temporary objects that can be reused to reduce GC pressure. It is useful for short-lived, allocation-heavy objects like buffers. However, pool entries can be reclaimed at any GC, so you cannot rely on persistence. Always reset state before returning objects to the pool.

var bufPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func Process(data []byte) string {
    buf := bufPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()
        bufPool.Put(buf)
    }()
    buf.Write(data)
    return buf.String()
}

20. What is a data race, and how do you detect it?

A data race occurs when two goroutines access the same variable concurrently, and at least one is a write. Go provides the -race flag, which instruments the binary to detect races at runtime. Always test concurrent code with the race detector enabled.

// Run with: go test -race ./...
// or: go run -race main.go

func main() {
    counter := 0
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter++ // DATA RACE
        }()
    }
    wg.Wait()
    fmt.Println(counter)
}

Section 3: Interfaces and Type System

21. How are interfaces implemented in Go?

Go interfaces are satisfied implicitly β€” a type implements an interface if it has all the required methods, with no explicit declaration. This enables structural typing and decoupling. Internally, an interface value is a two-word structure: a type pointer and a data pointer (the "iface" or "eface" structure).

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }

func (d Dog) Speak() string { return d.Name + " says woof" }

func main() {
    var s Speaker = Dog{Name: "Rex"}
    fmt.Println(s.Speak())
}

22. What is the difference between a nil interface and an interface holding a nil value?

This is a classic Go gotcha. A nil interface has both its type and value pointers set to nil. An interface holding a nil pointer has a non-nil type pointer but a nil value pointer β€” the interface itself is not nil. This causes surprising behavior when returning typed nil pointers as interface values.

type MyError struct{}

func (e *MyError) Error() string { return "error" }

func doSomething() error {
    var err *MyError = nil
    return err // interface is NOT nil!
}

func main() {
    err := doSomething()
    fmt.Println(err == nil) // false
}

23. What is type assertion, and what is type switch?

A type assertion extracts the concrete value from an interface. A type switch is a construct that tests an interface value against multiple types.

func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("int: %d\n", v)
    case string:
        fmt.Printf("string: %s\n", v)
    default:
        fmt.Printf("unknown: %T\n", v)
    }
}

func main() {
    var i interface{} = "hello"
    s, ok := i.(string) // safe assertion
    fmt.Println(s, ok)
}

24. Should interfaces accept pointer or value receivers?

A method with a value receiver can be called on both values and pointers of that type. A method with a pointer receiver can only be called on pointers (or addressable values). For interface satisfaction, this means a type with pointer-receiver methods only satisfies the interface via its pointer. As a rule of thumb, be consistent: if one method has a pointer receiver, all should.

25. What is the empty interface interface{} (or any)?

The empty interface has no methods, so every type satisfies it. It was the pre-generics way to handle values of any type. In Go 1.18+, any is an alias for interface{}. Overusing it defeats type safety β€” prefer generics or specific interfaces.

26. What are generics in Go, and how do you use them?

Introduced in Go 1.18, generics allow parameterized types and functions using type parameters with constraints. This enables type-safe, reusable data structures and algorithms without code duplication or runtime reflection.

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
}

func main() {
    nums := []int{1, 2, 3}
    doubled := Map(nums, func(n int) int { return n * 2 })
    fmt.Println(doubled) // [2 4 6]
}

27. What are type constraints and the constraints package?

Type constraints define what types a type parameter can accept. The standard library golang.org/x/exp/constraints (and cmp in Go 1.21) provides common constraints like Ordered. You can also define custom constraint interfaces using union (|) and tilde (~) operators.

type Number interface {
    ~int | ~int64 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

Section 4: Error Handling

28. How does Go's error handling differ from exceptions?

Go treats errors as values, not control-flow mechanisms. Functions return errors explicitly, and callers check them. This makes error handling visible in the code path, avoids hidden stack unwinding, and keeps performance predictable. The trade-off is verbosity, which Go embraces for clarity.

29. What is errors.Is and errors.As?

errors.Is checks if an error (or any error in its chain) matches a target sentinel error. errors.As extracts a specific error type from the chain. Both traverse the Unwrap chain introduced in Go 1.13.

var ErrNotFound = errors.New("not found")

func find(id int) error {
    return fmt.Errorf("find %d: %w", id, ErrNotFound)
}

func main() {
    err := find(42)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("item not found")
    }
}

30. How do you wrap errors, and why use %w?

Use fmt.Errorf with the %w verb to wrap an error while adding context. This preserves the original error for errors.Is and errors.As. Using %v instead would flatten the error into a string, losing the chain.

func loadConfig(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        return fmt.Errorf("reading config %s: %w", path, err)
    }
    // ...
    return nil
}

31. What is a custom error type, and when should you create one?

A custom error type is a struct implementing the Error() string method. Create one when callers need to inspect structured error details (status codes, retryability, fields) beyond a simple message. This pairs well with errors.As.

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

func validate(email string) error {
    if !strings.Contains(email, "@") {
        return &ValidationError{Field: "email", Message: "invalid format"}
    }
    return nil
}

func main() {
    err := validate("bad")
    var ve *ValidationError
    if errors.As(err, &ve) {
        fmt.Println(ve.Field) // email
    }
}

32. What is panic and recover, and when should you use them?

panic stops the current goroutine and begins unwinding, running deferred functions. recover (only valid inside a deferred function) stops the panicking sequence and returns the panic value. Use them for truly unrecoverable conditions (programming errors, invariant violations), not for normal error handling. A common pattern is recovering in HTTP handlers to prevent one request from crashing the server.

func safeHandler() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    panic("something went wrong")
}

Section 5: Memory Management & Performance

33. How does Go's garbage collector work?

Go uses a concurrent, tri-color mark-and-sweep garbage collector. It runs concurrently with the application, aiming for sub-millisecond pause times. The GC is triggered based on a growth ratio relative to live heap (controlled by GOGC, default 100%). The collector marks reachable objects starting from roots (goroutine stacks, globals), then sweeps unreachable memory. Go 1.19+ also supports a soft memory limit via GOMEMLIMIT.

34. What is escape analysis, and how does it affect performance?

Escape analysis is a compiler pass that determines whether a variable can be allocated on the stack or must "escape" to the heap. Stack allocations are essentially free (no GC involvement). Variables escape when their address is returned, stored in a heap object, or captured by a closure that outlives the function. Use go build -gcflags="-m" to inspect escape decisions.

//go:noinline
func newInt() *int {
    x := 42  // escapes to heap because address is returned
    return &x
}

func main() {
    p := newInt()
    fmt.Println(*p)
}

35. What is the difference between stack and heap allocation in Go?

Stack allocation is fast and automatically freed when the function returns. Heap allocation involves the GC for reclamation. Go developers do not explicitly choose β€” the compiler decides via escape analysis. Writing allocation-friendly code (avoiding unnecessary pointer returns, pre-allocating slices) reduces GC pressure.

36. How do you reduce allocations in hot paths?

Key techniques include pre-allocating slices with make([]T, 0, n), reusing buffers with sync.Pool, passing structs by value when small, using strings.Builder for concatenation, and avoiding fmt.Sprintf in hot loops. Benchmark with testing.B and inspect with pprof.

func concatSlow(parts []string) string {
    s := ""
    for _, p := range parts {
        s += p // allocates each iteration
    }
    return s
}

func concatFast(parts []string) string {
    var sb strings.Builder
    for _, p := range parts {
        sb.WriteString(p)
    }
    return sb.String()
}

37. How do you profile a Go application?

Go has built-in pprof support. For HTTP servers, import _ "net/http/pprof" and register pprof endpoints. For any program, use runtime/pprof to write profiles to files. Analyze with go tool pprof.

import _ "net/http/pprof"

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    // your app runs here
    // Visit http://localhost:6060/debug/pprof/
}

38. What is runtime.GOMAXPROCS, and when would you change it?

GOMAXPROCS sets the number of OS threads that can execute Go code simultaneously. It defaults to the number of CPU cores. You rarely need to change it manually. One edge case: in containerized environments with CPU limits, Go 1.25+ automatically respects cgroup CPU limits; in older versions, you may need to set it explicitly to avoid over-subscription.

39. How do you write effective benchmarks in Go?

Use testing.B with functions named BenchmarkXxx. Use b.ResetTimer() after setup and b.ReportAllocs() to track allocations. Run with go test -bench=. -benchmem.

func BenchmarkProcess(b *testing.B) {
    data := make([]byte, 1024)
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Process(data)
    }
}

40. What are the performance implications of interface dispatch?

Calling a method through an interface involves an indirect call through the itable, which prevents inlining and can be slower than direct calls. For hot paths, consider concrete types or generics. However, the overhead is usually negligible compared to the work done inside the method. Profile before optimizing.

Section 6: Standard Library & Idioms

41. How does context.Context work, and why is it important?

context.Context carries deadlines, cancellation signals, and request-scoped values across API boundaries and goroutines. It is the standard way to propagate cancellation in Go. The root context is context.Background() or context.TODO(). Derive child contexts with WithCancel, WithTimeout, WithDeadline, or WithValue.

func fetchData(ctx context.Context, url string) ([]byte, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    data, err := fetchData(ctx, "https://example.com")
    fmt.Println(len(data), err)
}

42. What is the difference between context.WithValue and passing arguments?

WithValue is for request-scoped data that crosses API boundaries (e.g., trace IDs, auth tokens). It should not be used for required function parameters β€” that makes the API implicit and hard to test. Use typed keys to avoid collisions, and keep values in context minimal.

type ctxKey string

const userIDKey ctxKey = "userID"

func WithUserID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, userIDKey, id)
}

func UserIDFrom(ctx context.Context) string {
    v, _ := ctx.Value(userIDKey).(string)
    return v
}

43. How do you structure a Go project idiomatically?

The community has converged on layouts like the standard Go project layout. Key principles: keep main.go thin, separate packages by domain/responsibility, internal packages under internal/ to restrict imports, and keep cmd/ for entry points. Avoid over-nesting and circular dependencies.

myapp/
β”œβ”€β”€ cmd/
β”‚   └── server/
β”‚       └── main.go
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ handler/
β”‚   β”œβ”€β”€ service/
β”‚   └── repository/
β”œβ”€β”€ pkg/
β”‚   └── logger/
β”œβ”€β”€ go.mod
└── go.sum

44. What is the difference between init() functions and package-level variable initialization?

Package-level variables are initialized in dependency order before init() runs. init() functions execute after variable initialization, in the order their source files are presented to the compiler. Multiple init() functions per file are allowed. Overusing init() for complex setup (database connections, config loading) makes code harder to test β€” prefer explicit initialization in main.

45. How do defer, argument evaluation, and LIFO ordering work?

Deferred function calls are pushed onto a stack and executed in LIFO order when the enclosing function returns. Arguments to a deferred call are evaluated immediately at the defer statement, not at execution time. This is a common source of bugs when deferring with loop variables.

func main() {
    for i := 0; i < 3; i++ {
        defer func(i int) {
            fmt.Println(i) // pass i as argument
        }(i)
    }
    // Prints: 2, 1, 0
}

Section 7: Advanced Topics & System Design

46. How do you implement a worker pool in Go?

A worker pool limits concurrency by spawning a fixed number of worker goroutines that pull jobs from a channel. This bounds resource usage and prevents goroutine explosions.

func workerPool(jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for j := range jobs {
        results <- j * j
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    numWorkers := 4
    for w := 0; w < numWorkers; w++ {
        wg.Add(1)
        go workerPool(jobs, results, &wg)
    }

    for j := 1; j <= 10; j++ {
        jobs <- j
    }
    close(jobs)

    go func() {
        wg.Wait()
        close(results)
    }()

    for r := range results {
        fmt.Println(r)
    }
}

47. How do you implement a rate limiter in Go?

Use golang.org/x/time/rate for a token bucket limiter, or implement a simple ticker-based limiter for basic needs. Rate limiting protects downstream services and enforces fair usage.

import "golang.org/x/time/rate"

func main() {
    limiter := rate.NewLimiter(rate.Limit(10), 5) // 10/sec, burst 5
    ctx := context.Background()

    for i := 0; i < 20; i++ {
        if err := limiter.Wait(ctx); err != nil {
            log.Fatal(err)
        }
        fmt.Println("request", i, "at", time.Now().Format("15:04:05.000"))
    }
}

48. How do you implement graceful shutdown for an HTTP server?

Use http.Server.Shutdown with a context timeout. Listen for OS signals (SIGINT, SIGTERM), then call Shutdown to drain in-flight requests before exiting.

func main() {
    srv := &http.Server{Addr: ":8080", Handler: mux()}

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatal(err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("server shutdown:", err)
    }
    fmt.Println("server exited gracefully")
}

49. How do you test concurrent code in Go?

Use the race detector, sync.WaitGroup for synchronization, and table-driven tests. For timing-sensitive logic, use testing.T.Helper and avoid time.Sleep in tests when possible β€” prefer channels or synchronization primitives. For mocking, use interfaces and dependency injection.

func TestConcurrentIncrement(t *testing.T) {
    var counter int64
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt64(&counter, 1)
        }()
    }
    wg.Wait()

    if counter != 1000 {
        t.Errorf("expected 1000, got %d", counter)
    }
}

50. What are

β€” Ad β€”

Google AdSense will appear here after approval

← Back to all articles