Go Programming Interview Questions

50 questions and answers covering Go Programming, from fundamentals through practical, debugging, and design-level topics.

50 of 50 interview questions

  1. Is Go a compiled or interpreted language, and what does that mean for deployment?beginnerGo Fundamentals

    Go is compiled -- `go build` produces a single, statically-linked native executable with no external runtime dependency required on the target machine, which makes deployment simple (copy one binary) compared to languages requiring a separate installed runtime/interpreter.

  2. What is the difference between `:=` and `var` for declaring a variable in Go?beginnerGo Fundamentals

    `:=` (short variable declaration) declares and infers the type from the assigned value in one step, only usable inside a function body; `var name Type = value` explicitly declares the type (or lets it be inferred) and is usable at package level too -- `:=` is the more common shorthand inside functions.

    count := 5          // short declaration
    var count int = 5   // explicit var
  3. Why does Go not have exceptions, and how does it handle errors instead?intermediateGo Fundamentals

    Go deliberately omits exception-based error handling in favor of returning an explicit `error` value as an additional return value from any function that can fail -- callers must explicitly check `if err != nil` rather than errors silently propagating up an invisible exception path.

  4. What does Go's `gofmt` tool do, and why is Go's tooling-enforced formatting notable compared to most languages?intermediateGo Fundamentals

    `gofmt` automatically reformats Go source code to the language's one canonical style -- Go treats formatting as essentially non-negotiable and tool-enforced, eliminating the code-style debates and inconsistency common in languages with multiple accepted formatting conventions.

  5. Why does Go treat an unused imported package or an unused local variable as a compile ERROR, not just a warning?intermediateGo Fundamentals

    Go's designers deliberately chose to enforce this strictly to prevent dead code and accidental leftover imports/variables from accumulating silently -- forcing developers to either use or explicitly remove them keeps the codebase clean by construction, at the cost of occasionally being annoying during quick prototyping.

    Common mistake: Leaving an unused variable or import in Go code and expecting it to just be a warning, when it actually fails compilation.

  6. What is Go's zero-value concept, and why does it matter for variables you declare without an explicit initial value?advancedGo Fundamentals

    Every type has a well-defined default 'zero value' (0 for numbers, "" for strings, `false` for booleans, `nil` for pointers/slices/maps/interfaces) -- a variable declared with `var x int` is never left as truly uninitialized/garbage memory, it's immediately usable as 0, which eliminates a whole class of uninitialized-variable bugs common in some other languages.

  7. What does it mean that Go is a statically-typed language, and how does that differ from JavaScript/Python's dynamic typing?beginnerGo Fundamentals

    In Go, every variable's type is fixed at compile time and checked by the compiler before the program ever runs -- a type mismatch is caught immediately as a compile error, rather than potentially surfacing only at runtime the way it would in a dynamically-typed language.

  8. What is Go's naming convention for controlling whether a function, type, or variable is exported (public) from a package?intermediateGo Fundamentals

    Capitalization determines visibility -- an identifier starting with an uppercase letter (`ProcessOrder`) is exported and accessible from other packages; a lowercase-starting identifier (`processOrder`) is package-private, only accessible within the same package. No separate `public`/`private` keyword exists.

  9. What does Go's `const` keyword let you declare, and how does it differ from a `var`?beginnerGo Fundamentals

    `const` declares a compile-time constant value that can never be reassigned and (for numeric/string constants) is evaluated at compile time -- unlike `var`, which declares a normal, mutable runtime variable, even if you never actually reassign it.

  10. Why is Go often described as favoring simplicity and explicitness over language feature richness, compared to languages like C++ or Scala?advancedGo Fundamentals

    Go deliberately omits many features other languages have (classical inheritance, exceptions, generics were notably absent for years, operator overloading) in favor of a small, easy-to-learn core language -- a deliberate design tradeoff favoring readability and consistency across a large codebase/team over expressive power for any individual piece of code.

  11. What does it mean that Go functions can return multiple values, and how is this commonly used for error handling?beginnerFunctions, Slices & Maps

    A Go function can declare more than one return type (`func divide(a, b int) (int, error)`), most commonly used to return both a result and an error value together -- the idiomatic pattern is `result, err := divide(a, b); if err != nil { ... }`.

    func divide(a, b int) (int, error) {
      if b == 0 {
        return 0, errors.New("division by zero")
      }
      return a / b, nil
    }
  12. What is a Go slice, and how does it differ from a plain array?intermediateFunctions, Slices & Maps

    An array has a fixed size baked into its type (`[5]int`); a slice is a flexible, resizable view over an underlying array, with its own length and capacity that can grow -- slices are what's actually used in idiomatic Go code for nearly all list-like data, with arrays used only rarely for fixed-size cases.

  13. What is the difference between a slice's length and its capacity?advancedFunctions, Slices & Maps

    Length is how many elements the slice currently contains; capacity is how many elements the underlying array CAN hold before a new, larger array must be allocated -- appending beyond current capacity triggers Go to allocate a new, larger underlying array and copy the existing elements over.

  14. Why can appending to a slice that's a 'view' into a larger array sometimes produce surprising shared-mutation behavior?advancedFunctions, Slices & Maps

    Two slices can share the same underlying array if one was created by slicing another (`s2 := s1[1:3]`) -- modifying an element through one slice can be visible through the other, since they point at the same backing storage, until an append operation forces a new, separately-allocated array.

    Common mistake: Assuming two slices derived from the same underlying array are fully independent, then being surprised when mutating one affects the other.

  15. What is a Go map, and what happens if you read a key that doesn't exist?intermediateFunctions, Slices & Maps

    A map is Go's built-in hash-table-backed key-value collection -- reading a nonexistent key returns the value type's zero value (not an error/exception), so `m["missing"]` on a `map[string]int` returns `0`, not a crash. The two-value form (`v, ok := m["key"]`) is used to distinguish 'key missing' from 'key present with a zero value.'

    v, ok := m["missing"]
    if !ok {
      fmt.Println("key not found")
    }
  16. Why is map iteration order in Go explicitly randomized/unspecified, unlike some other languages' insertion-ordered maps?advancedFunctions, Slices & Maps

    Go deliberately randomizes map iteration order specifically to prevent developers from accidentally relying on an order that was never actually guaranteed -- code that needs a specific order must sort keys explicitly rather than depending on incidental map iteration behavior.

  17. What is a variadic function parameter in Go (`func sum(nums ...int) int`), and how do you call it with an existing slice?advancedFunctions, Slices & Maps

    A variadic parameter lets the function accept any number of arguments of that type, collected into a slice inside the function -- to pass an EXISTING slice's elements as variadic arguments, you must spread it explicitly with `...` (`sum(myNums...)`), rather than passing the slice directly.

  18. What is a closure in Go, and how does it capture variables from its enclosing scope?advancedFunctions, Slices & Maps

    A closure is a function value that references variables from outside its own body -- Go closures capture those variables BY REFERENCE, meaning if the enclosing variable changes after the closure is created, the closure sees the updated value, not a snapshot from creation time.

  19. Why does Go pass function arguments strictly by value, even for structs, and what does that mean for a large struct passed to a function?advancedFunctions, Slices & Maps

    Every argument is copied when passed -- for a large struct, this means copying its entire contents, which can be a real performance cost; passing a POINTER to the struct instead avoids the copy and also lets the function actually mutate the caller's original struct if needed.

  20. What does `defer` do in a Go function, and what is a very common use case?intermediateFunctions, Slices & Maps

    `defer` schedules a function call to run right before the enclosing function returns, regardless of how it returns (normal return or panic) -- extremely commonly used for guaranteed cleanup, like `defer file.Close()` immediately after successfully opening a file.

    f, err := os.Open(path)
    if err != nil {
      return err
    }
    defer f.Close()
  21. What is a Go struct, and how does it compare conceptually to a class in an object-oriented language?intermediateStructs, Interfaces & Error Handling

    A struct is a composite type grouping named fields together -- Go has no classes at all; behavior is attached to structs via separately-defined methods (functions with a receiver), and there's no inheritance, only composition (embedding one struct inside another).

  22. What is the difference between a value receiver and a pointer receiver on a Go method?advancedStructs, Interfaces & Error Handling

    A value receiver (`func (u User) Greet()`) operates on a COPY of the struct, so it can't modify the original; a pointer receiver (`func (u *User) SetName(name string)`) operates on the original struct via its address, letting the method actually mutate the caller's struct.

    Common mistake: Using a value receiver for a method meant to mutate the struct, silently modifying only a local copy that the caller never sees.

  23. How does Go's interface satisfaction work, and how does it differ from explicit `implements` declarations in Java?advancedStructs, Interfaces & Error Handling

    Go uses structural (implicit) interface satisfaction -- any type that happens to implement all the methods an interface requires automatically satisfies that interface, with no explicit `implements` keyword or declaration needed -- a type can satisfy an interface without even knowing that interface exists.

  24. What is the significance of the empty interface `interface{}` (or its alias `any`)?advancedStructs, Interfaces & Error Handling

    Since it requires zero methods, EVERY type automatically satisfies it -- it's Go's way of representing 'a value of any type,' historically used before generics for things like a generic container, though modern Go generics now offer a more type-safe alternative for many such cases.

  25. What is the idiomatic Go error-handling pattern, and why does it look repetitive compared to try/catch-based languages?intermediateStructs, Interfaces & Error Handling

    `if err != nil { return err }` (or wrapped with more context) repeated after every fallible call -- it looks repetitive because errors are explicit, ordinary values checked at each step, a deliberate design choice trading conciseness for making every possible failure point visible directly in the code's control flow.

  26. What is error wrapping in Go (`fmt.Errorf("failed to load config: %w", err)`), and what does the `%w` verb specifically enable?advancedStructs, Interfaces & Error Handling

    `%w` wraps the original error inside a new one while preserving a reference to it, letting callers later use `errors.Is`/`errors.As` to check whether a specific underlying error occurred, even through several layers of wrapping -- preserves diagnostic context without losing the ability to programmatically inspect the root cause.

  27. What is the difference between a `panic` and a regular returned `error` in Go, and when is `panic` actually appropriate?advancedStructs, Interfaces & Error Handling

    A returned `error` represents an expected, recoverable failure mode that calling code is meant to handle; `panic` represents an unrecoverable programmer error or truly exceptional condition, unwinding the call stack -- idiomatic Go reserves `panic` for genuinely exceptional situations, not routine error handling, which should use returned errors instead.

    Common mistake: Using panic for routine, expected error conditions instead of returning an error value, going against Go's idiomatic error-handling convention.

  28. What does `recover()` do, and in what context must it be called to actually work?advancedStructs, Interfaces & Error Handling

    `recover()` stops a panicking goroutine's unwinding and returns the value passed to `panic` -- it only has an effect when called directly inside a `deferred` function; calling it anywhere else does nothing.

  29. What is struct embedding, and how does it let one struct 'inherit' another's fields/methods without classical inheritance?advancedStructs, Interfaces & Error Handling

    Embedding a struct (or interface) inside another struct with no field name promotes its fields and methods to be accessible directly on the outer struct -- a form of composition that achieves some of what inheritance provides in other languages, without an actual is-a inheritance relationship or method-overriding polymorphism.

  30. Why might a small, focused interface (like Go's standard `io.Reader` with just one method) be preferred over a large interface with many methods?advancedStructs, Interfaces & Error Handling

    Small interfaces are easier for many different concrete types to satisfy naturally, and easier to mock/fake in tests -- Go's standard library convention favors small, composable interfaces ('accept interfaces, return structs') over large ones that force implementers to provide many methods they may not all need.

  31. What is a goroutine, and how is it different from an OS thread?intermediateGoroutines, Channels & Concurrency

    A goroutine is a lightweight, Go-runtime-managed concurrent function execution -- much cheaper to create than an OS thread (goroutines start with a small, growable stack and the Go runtime multiplexes many goroutines onto a smaller number of actual OS threads), so a program can comfortably run thousands of goroutines.

    go doSomething()  // starts a new goroutine
  32. What is a channel in Go, and what is it used for?intermediateGoroutines, Channels & Concurrency

    A typed conduit for goroutines to send and receive values safely, providing built-in synchronization -- the idiomatic Go concurrency philosophy is often summarized as 'don't communicate by sharing memory; share memory by communicating,' with channels being the primary mechanism for that communication.

    ch := make(chan int)
    go func() { ch <- 42 }()
    value := <-ch
  33. What is the difference between an unbuffered and a buffered channel?advancedGoroutines, Channels & Concurrency

    An unbuffered channel (`make(chan int)`) blocks the sender until a receiver is ready to receive (synchronous handoff); a buffered channel (`make(chan int, 5)`) lets the sender proceed without blocking until the buffer is full, decoupling the timing of sends and receives somewhat.

  34. What happens if you send on a channel that no goroutine will ever receive from, and no buffer capacity remains?advancedGoroutines, Channels & Concurrency

    The sending goroutine blocks forever, waiting for a receiver that never comes -- if this is the ONLY thing keeping the program running, Go's runtime can detect this specific 'all goroutines are asleep, deadlock' situation and crash the program with a deadlock error rather than hanging silently forever.

    Common mistake: Sending on an unbuffered channel with no corresponding receiver ready, causing the goroutine (and potentially the whole program) to deadlock.

  35. What does `sync.WaitGroup` let you do, and why is it commonly used alongside goroutines?advancedGoroutines, Channels & Concurrency

    It lets a goroutine wait for a collection of OTHER goroutines to finish before proceeding -- `Add(n)` registers how many to wait for, each goroutine calls `Done()` when finished, and `Wait()` blocks until the count reaches zero, a common pattern for 'launch N concurrent tasks, then wait for all of them.'

    var wg sync.WaitGroup
    for _, url := range urls {
      wg.Add(1)
      go func(u string) {
        defer wg.Done()
        fetch(u)
      }(url)
    }
    wg.Wait()
  36. What is a race condition, and what tool does Go provide specifically to help detect one?advancedGoroutines, Channels & Concurrency

    A race condition occurs when multiple goroutines access shared memory concurrently, with at least one write, without proper synchronization -- Go's built-in `-race` flag (`go test -race`, `go run -race`) instruments the program to detect and report actual data races encountered during execution.

  37. Why does a classic 'launching goroutines in a loop that each capture the loop variable' pattern historically produce a bug in Go?advancedGoroutines, Channels & Concurrency

    In older Go versions, the loop variable was reused across iterations (one shared variable), so a closure capturing it by reference could see the FINAL value once the loop finished, not the value at the time the goroutine was launched -- Go 1.22 changed loop variable semantics to give each iteration its own variable, fixing this specific footgun, but it remains an important thing to understand for reading older code.

  38. What is a `select` statement in Go used for?advancedGoroutines, Channels & Concurrency

    It lets a goroutine wait on multiple channel operations simultaneously, proceeding with whichever one becomes ready first -- useful for patterns like waiting on either a result channel or a timeout/cancellation channel, whichever happens first.

  39. What is `context.Context` commonly used for in concurrent Go code, especially in server applications?advancedGoroutines, Channels & Concurrency

    It carries cancellation signals, deadlines, and request-scoped values across API boundaries and between goroutines -- e.g. a canceled HTTP request's context propagates down through every downstream operation started on its behalf, letting them stop work early instead of continuing pointlessly after the client has already disconnected.

  40. Why is 'just launching a goroutine for everything' not automatically a performance win?advancedGoroutines, Channels & Concurrency

    Goroutines are cheap but not free -- excessive, unbounded goroutine creation (e.g. one per item in an unbounded input stream) can still exhaust memory or overwhelm downstream resources (like a database connection pool); a worker-pool pattern with a bounded number of goroutines is often more appropriate than unlimited concurrent goroutines.

  41. What is a Go module (`go.mod`), and what does it manage?intermediateProject Structure, Testing & Tooling

    A module defines a project's root import path and tracks its external dependencies and their exact versions -- `go.mod` is the manifest file (similar in role to `package.json`), and `go.sum` records cryptographic checksums of dependencies for reproducible, verifiable builds.

  42. What does the Go testing convention `func TestXxx(t *testing.T)` in a `_test.go` file establish?intermediateProject Structure, Testing & Tooling

    Go's built-in testing framework auto-discovers any function matching that naming pattern in a file ending `_test.go` as a test case -- no separate test framework installation is required; `go test` runs them directly using the standard library's `testing` package.

    func TestAdd(t *testing.T) {
      if Add(2, 3) != 5 {
        t.Errorf("expected 5")
      }
    }
  43. What is table-driven testing, a very common Go testing pattern?advancedProject Structure, Testing & Tooling

    Defining a slice/table of input-and-expected-output test cases, then looping over them within a single test function -- a Go-idiomatic way to cover many similar cases without duplicating near-identical test function bodies for each one.

  44. What does `go vet` do, and how does it differ from the compiler's own type checking?advancedProject Structure, Testing & Tooling

    `go vet` performs additional static analysis beyond basic type checking, catching suspicious constructs that compile fine but are very likely bugs (like a `Printf` format string that doesn't match its arguments) -- a complementary safety net the compiler itself doesn't provide.

  45. What is the conventional Go project layout for the entry point of an executable command?intermediateProject Structure, Testing & Tooling

    A `main` package with a `func main()`, commonly placed under a `cmd/<binary-name>/main.go` path for projects with multiple executables, or directly at the repo root for a single-binary project -- `package main` is what tells `go build` this package produces a runnable executable rather than an importable library.

  46. Why does Go's standard library include a built-in HTTP server/client, and what does that mean for how many Go web projects avoid heavy external frameworks?advancedProject Structure, Testing & Tooling

    The `net/http` package provides a genuinely capable, production-usable HTTP server and client out of the box -- many Go web services are built directly on it (or a thin routing layer over it) rather than requiring a large external framework, reflecting Go's broader philosophy of a capable standard library reducing dependency needs.

  47. What does Go's built-in benchmarking support (`func BenchmarkXxx(b *testing.B)`) let you measure?advancedProject Structure, Testing & Tooling

    It measures a function's performance (time per operation) directly using the standard toolchain (`go test -bench=.`), without needing an external benchmarking library -- consistent with Go's philosophy of batteries-included tooling for common development needs.

  48. Why might a Go project deliberately avoid adding many small, single-purpose external dependencies compared to the norm in some other language ecosystems?advancedProject Structure, Testing & Tooling

    Go's cultural norm (and a capable standard library) tends to favor fewer, more deliberate dependencies -- each added dependency is a piece of code you don't control that could introduce a vulnerability, a breaking change, or added attack surface, so many idiomatic Go projects lean on the standard library and a small number of well-vetted packages rather than many small ones.

  49. What does `go mod tidy` do, and why is running it periodically good practice?intermediateProject Structure, Testing & Tooling

    It adds any missing dependencies actually used in the code and removes unused ones from `go.mod`/`go.sum` -- keeps the module's declared dependencies accurate and minimal, catching the case where a dependency was removed from code but its entry was never cleaned up.

  50. Why is fast compilation often cited as one of Go's practical strengths for large codebases and CI pipelines?advancedProject Structure, Testing & Tooling

    Go's compiler was specifically designed for speed, and Go's package dependency model avoids some of the compilation-time costs (like C++'s header-inclusion model) that slow down large builds in other languages -- fast, reliable builds directly translate to faster CI feedback loops and a better day-to-day developer experience on large projects.