DEV Community

Cover image for 📝 100 COMMON GOLANG INTERVIEW QUESTIONS 🐹
Truong Phung
Truong Phung

Posted on • Edited on

📝 100 COMMON GOLANG INTERVIEW QUESTIONS 🐹

100 Common Golang Interview Questions and Answers

1. What is Golang?

Go, or Golang, is an open-source programming language developed by Google. It is statically typed, compiled, and designed for building scalable and high-performance applications.

2. What are the key features of Go?

  • Concurrency support using Goroutines.
  • Garbage collection.
  • Statically typed with dynamic behavior.
  • Simple syntax.
  • Fast compilation.

3. What are Goroutines and how to create them?

Goroutines are lightweight threads managed by the Go runtime. They are functions or methods that run concurrently with other functions or methods.

To create them use the go keyword before a function call:

   go myFunction()

   // With anonymous function
   go func(){
   }()
Enter fullscreen mode Exit fullscreen mode

4. What is a channel in and how to declare it?

Channels are a way for Goroutines to communicate with each other and synchronize their execution. They allow sending and receiving values.

   ch := make(chan int)
Enter fullscreen mode Exit fullscreen mode

5. What is a buffered channel?

A buffered channel has a specified capacity and allows sending of values until the buffer is full. It doesn't require a receiver to be ready to receive.

 func main(){
     ch1 := make(chan int,1)
     ch1<-1 //this will not block, thanks to buffer

     ch2 := make(chan int)
     ch2 <- 1 // this will block main because no other goroutine will read it
 }
Enter fullscreen mode Exit fullscreen mode

6. How do you close a channel?

Use the close() function:

   close(ch)
Enter fullscreen mode Exit fullscreen mode

And to check if a channel has been closed

      //Case 1
    value, ok := <-ch
    if !ok {
        // Channel is closed
    }

    //Case 2
    for value := range ch {
    // Process value
    }
    // Channel is closed when the loop exits
Enter fullscreen mode Exit fullscreen mode

7. What is a struct in Go and how to define it?

A struct is a user-defined type that allows grouping fields of different data types into a single entity.

   type Person struct {
    Name string
    Age  int
   }

    //or
   func someFunc(){
         arr := []struct{ UserId int }{{UserId: 200}}

        var m map[string]struct {
                           UserId string
        }
        fmt.Println("Hello ", arr, m)
   }
Enter fullscreen mode Exit fullscreen mode

8. What is an interface in Go and how to implement it?

An interface in Go is a type that specifies a set of method signatures. It allows polymorphism by defining behavior.

A type implements an interface by implementing all its methods:

    type Animal interface {
       Speak() string
    }

    type Dog struct{}

    func (d Dog) Speak() string {
        return "Woof!"
    }
Enter fullscreen mode Exit fullscreen mode

9. What are the performance implications of using large struct types as function parameters in Go? How would you optimize it?

Passing large structs by value results in a copy being created, which can be expensive in terms of memory and performance. To optimize, pass pointers to structs (*Struct) instead of copying the entire struct. This avoids duplicating data and reduces the overhead of copying large amounts of memory.

10. What is interface{} in Go, and how does it relate to empty interfaces?

interface{} is an empty interface, meaning it can hold values of any type since all types implement zero methods. It is often used for generic data structures or when working with functions that can accept any type. Type assertions (value.(type)) and type switches allow you to determine the dynamic type of a value stored in an interface{}.

11. What are some strategies to reduce GC overhead?

  • Using object pooling to reuse memory.
  • Reducing the allocation of short-lived objects.
  • Preallocating slices or structs to avoid frequent allocations.

12. How would you handle error handling and logging in a large Go project?

  • For error handling, use custom error types to add context to errors, and wrap errors using fmt.Errorf for better error messages.
  • Structured logging using libraries like zap or logrus helps capture detailed logs with fields for easier searching and analysis.
  • Use defer to close resources and recover in critical sections to handle unexpected panics gracefully.
  • Centralize error handling logic for common operations like database queries to ensure consistency.

13. How do you manage circular dependencies in Go packages?

  • Refactor common functionality into a separate package to avoid mutual dependency.
  • Use interfaces to decouple dependencies, allowing one package to depend only on the interface rather than the implementation.
  • Reorganize code to reduce the number of dependencies, ensuring that the dependency graph remains acyclic.

14. What is the defer keyword and how does it work?

defer is used to postpone the execution of a function until the surrounding function returns.

Deferred functions are executed in LIFO (Last In, First Out) order:

   defer fmt.Println("world")
   defer fmt.Println("hi")
   fmt.Println("hello")
   // Output: hello hi world
Enter fullscreen mode Exit fullscreen mode

15.How do you implement graceful shutdown of a Go HTTP server?

Answer: To implement a graceful shutdown, use a context with http.Server's Shutdown method to stop accepting new connections while allowing in-flight requests to complete. You can use a signal.Notify to listen for interrupt signals (e.g., SIGINT) and call Shutdown when the signal is received. This ensures the server shuts down cleanly without interrupting active requests.

   srv := &http.Server{Addr: ":8080"}
   go func() {
   if err := srv.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatalf("Server error: %v", err)
    }
   }()
   c := make(chan os.Signal, 1)
   signal.Notify(c, os.Interrupt)
   <-c
   srv.Shutdown(context.Background())

Enter fullscreen mode Exit fullscreen mode

16. What is a pointer in Go, how to declare it?

A pointer holds the memory address of a value. It is used to pass references instead of copying values.

   x := 5
   var p *int
   p = &x // & sign generates a pointer to x, assign it to p
   *p // get the value that pointer p pointing to
Enter fullscreen mode Exit fullscreen mode

17. What is the difference between new and make?

  • new allocates memory but doesn't initialize the value.

    user := new(User)// equivalent to &User{}
    p := new(int) // p is *int, points to a zero-initialized int (0)
    
  • make allocates and initializes memory for slices, maps, and channels.

    m := make(map[string]int) // m is a map[string]int, initialized and ready for use
    s := make([]int, 5)       // s is a slice of int with length 5 and zeroed values
    ch := make(chan int)     // ch is a channel for int
    

18. What is a slice in Go?

A slice is a dynamically-sized array that provides a more flexible way to work with sequences of elements.

19. How do you create a slice?

   s := make([]int, 0) // len 0 cap 0 value []
   s2 := make([]int, 3,5) // len 3 cap 5 value [0 0 0]
   s3 := []int{} // len 0 cap 0 value []
   var s4 []int = []int{} // len 0 cap 0 value []
Enter fullscreen mode Exit fullscreen mode

20. What is a map in Go?

A map is a collection of key-value pairs.

21. How do you create a map?

   m := make(map[string]int)
   m2 := map[string]int{}
   m["hi"]=1
   if val,ok:=m["hello"];ok {
   }
Enter fullscreen mode Exit fullscreen mode

22. What is the select statement?

select allows a Goroutine to wait on multiple communication operations.

23. How do you use select?

   select {
    case msg := <-ch:
        fmt.Println(msg)
    case <-time.After(time.Millisecond*500):
        fmt.Println("do something after a period amount of time")
    default:
        fmt.Println("No message received")
    }
Enter fullscreen mode Exit fullscreen mode

24. What is a nil channel?

A nil channel blocks both sending and receiving operations.

func main() {
    var ch chan int // nil channel

    select {
    case ch <- 1: // This will block because ch is nil
        fmt.Println("Sent to channel")
    case <-time.After(1 * time.Second): // This ensures that after 1 second, it times out
        fmt.Println("Timed out")
    }
}
Enter fullscreen mode Exit fullscreen mode

25. What is the init function?

init is a special function that initializes package-level variables. It is executed before main.

26. Can you have multiple init functions?

Yes, but they will be executed in the order they appear.

27. What is an empty struct {}?

An empty struct consumes zero bytes of storage.Usually use for signaling, synchronization, or as a placeholder.

//{} can be used as values in a map when you only care about the existence of a key.
visited := map[string]struct{}{}
visited["example"] = struct{}{}

//Used to signal events without sending actual data.
done := make(chan struct{})
go func() {
    // Do some work...
    close(done) // Signal completion
}()
Enter fullscreen mode Exit fullscreen mode

28. How do you perform error handling in Go?

By returning an error type and checking it using:

   if err != nil {
    return err
   }
Enter fullscreen mode Exit fullscreen mode

29. What is type assertion?

Type assertion is used to extract the underlying value of an interface:

   value, ok := x.(string)
Enter fullscreen mode Exit fullscreen mode

30. What is the go fmt command?

go fmt formats Go source code according to the standard style.

31. What is the purpose of go mod, go mod tidy, go clean -modcache?

  • go mod: It is a command used to manage Go modules, which include dependencies for a Go project. It allows you to initialize a new module (go mod init), add or update dependencies (go mod tidy, go get), and manage the go.mod and go.sum files.

  • go mod tidy: This command cleans up the go.mod and go.sum files by adding any missing module requirements and removing any unused ones. It ensures that only the necessary dependencies are included in the module files, helping to keep them tidy and free of unnecessary entries.

  • go clean -modcache: This command deletes the contents of the module cache, located in the GOPATH/pkg/mod directory. It is useful when you want to clear out cached modules, often to fix issues with corrupt module data or to free up space.

32. How do you create a module?

  go mod init module-name
Enter fullscreen mode Exit fullscreen mode

33. What is a package in Go?

A package is a way to group related Go files together.

34. How do you import a package?

  import "fmt"
Enter fullscreen mode Exit fullscreen mode

35. What are the visibility rules in Go?

  • Exported identifiers start with an uppercase letter.
  • Unexported identifiers start with a lowercase letter.

36. What is the difference between var and :=?

  • var is used for variable declaration with explicit types.
  • := is used for short variable declaration with inferred types.

37. What is a panic in Go?

panic is used to terminate the program immediately when an error occurs.

38. What is recover?

recover is used to regain control after a panic.

39. How do you use recover?

It is used inside a deferred function:

  defer func() {
    if r := recover(); r != nil {
        fmt.Println("Recovered:", r)
    }
   }()
Enter fullscreen mode Exit fullscreen mode

40. What is a constant in Go?

Constants are immutable values declared using the const keyword.

41. How do you declare a constant?

  const Pi = 3.14
Enter fullscreen mode Exit fullscreen mode

42. What are iota in Go?

iota is a constant generator that increments by 1 automatically.

43. What is go test?

go test is used to run unit tests written in Go.

44. How do you write a test function?

Test functions must start with Test:

  func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("expected 5, got %d", result)
    }
  }
Enter fullscreen mode Exit fullscreen mode

45. What is benchmarking in Go?

Benchmarking is used to measure the performance of a function using go test.

46. How do you write a benchmark function?

Benchmark functions must start with Benchmark:

    func BenchmarkAdd(b *testing.B) {
        for i := 0; i < b.N; i++ {
            Add(2, 3)
        }
    }
Enter fullscreen mode Exit fullscreen mode

47. What is a build constraint?

Build constraints are used to include or exclude files from the build process based on conditions.

48. How do you set a build constraint?

Place the constraint in a comment at the top of the file:

    // +build linux
Enter fullscreen mode Exit fullscreen mode

49. What are slices backed by arrays?

Slices are built on top of arrays and provide a dynamic view over the array.

50. What is garbage collection in Go?

Go automatically manages memory using garbage collection, which frees up memory that is no longer in use.

51. What is the context package in Go?

The context package is used for managing deadlines, cancellation signals, and request-scoped values. It helps in controlling the flow of Goroutines and resources.

52. How do you use context in Go?

   ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   defer cancel()
Enter fullscreen mode Exit fullscreen mode

53. What is sync.WaitGroup?

sync.WaitGroup is used to wait for a collection of Goroutines to finish executing.

54. How do you use sync.WaitGroup?

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        // Do some work
    }()
    wg.Wait()

Enter fullscreen mode Exit fullscreen mode

55. What is sync.Mutex?

sync.Mutex provides a lock mechanism to protect shared resources from concurrent access.

56. How do you use sync.Mutex?

   var mu sync.Mutex
    mu.Lock()
    // critical section
    mu.Unlock()
Enter fullscreen mode Exit fullscreen mode

57. What is select used for with channels?

select is used to handle multiple channel operations simultaneously, allowing a Goroutine to wait for multiple communication operations.

58. What is go generate?

go generate is a command for generating code. It reads special comments within the source code to execute commands.

59. What are method receivers in Go?

Method receivers specify the type the method is associated with, either by value or pointer:

    func (p *Person) GetName() string {
        return p.Name
    }
Enter fullscreen mode Exit fullscreen mode

60. What is the difference between value and pointer receivers?

  • Value receivers get a copy of the original value.
  • Pointer receivers get a reference to the original value, allowing modifications.

61. What are variadic functions?

Variadic functions accept a variable number of arguments:

    func sum(nums ...int) int {
        total := 0
        for _, num := range nums {
            total += num
        }
        return total
   }
Enter fullscreen mode Exit fullscreen mode

62. What is a rune in Go?

A rune is an alias for int32 and represents a Unicode code point.

63. What is a select block without a default case?

A select block without a default will block until one of its cases can proceed.

64. What is a ticker in Go?

A ticker sends events at regular intervals:

    ticker := time.NewTicker(time.Second)
Enter fullscreen mode Exit fullscreen mode

65. How do you handle JSON in Go?

Use the encoding/json package to marshal and unmarshal JSON:

    jsonData, _ := json.Marshal(structure)
    json.Unmarshal(jsonData, &structure)
Enter fullscreen mode Exit fullscreen mode

66. What is go vet?

go vet examines Go source code and reports potential errors, focusing on issues that are not caught by the compiler.

67. What is an anonymous function in Go?

An anonymous function is a function without a name and can be defined inline:

    func() {
        fmt.Println("Hello")
    }()
Enter fullscreen mode Exit fullscreen mode

68. What is the difference between == and reflect.DeepEqual()?

  • == checks equality for primitive types.
  • reflect.DeepEqual() compares deep equality of complex types like slices, maps, and structs.

69. What is a time.Duration in Go?

time.Duration represents the elapsed time between two points and is a type of int64.

70. How do you handle timeouts with context?

Use context.WithTimeout to set a timeout:

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
Enter fullscreen mode Exit fullscreen mode

71. What is a pipeline in Go?

A pipeline is a series of stages connected by channels, where each stage is a collection of Goroutines that receive values from upstream and send values downstream.

72. What is pkg directory convention in Go?

pkg is a directory used to place reusable packages. It is a common convention but not enforced by Go.

73. How do you debug Go code?

Use tools like dlv (Delve), print statements, or the log package.

74. What is type alias in Go?

type aliasing allows you to create a new name for an existing type:

  type MyInt = int
Enter fullscreen mode Exit fullscreen mode

75. What is the difference between Append and Copy in slices?

  • append adds elements to a slice and returns a new slice.
  • copy copies elements from one slice to another.
slice1 := []int{1, 2}
slice2 := []int{3, 4}
copy(slice2, slice1) // [1, 2]
Enter fullscreen mode Exit fullscreen mode

76. What is the purpose of go doc?

go doc is used to display documentation for a Go package, function, or variable.

77. How do you handle panics in production code?

Use recover to gracefully handle panics and log them for debugging:

defer func() {
    if r := recover(); r != nil {
        log.Println("Recovered from:", r)
    }
}()
Enter fullscreen mode Exit fullscreen mode

78. What is the difference between map and struct?

  • map is a dynamic data structure with key-value pairs.
  • struct is a static data structure with fixed fields.

79. What is unsafe package?

The unsafe package allows low-level memory manipulation. It is not recommended for regular use.

80. How do you achieve dependency injection in Go?

Use interfaces and constructor functions to pass dependencies, allowing easy mocking and testing.

type HttpClient interface{}

func NewService(client HttpClient) *Service {
    return &Service{client: client}
}
Enter fullscreen mode Exit fullscreen mode

81. How does Goroutine differ from a thread?

A Goroutine is a lightweight thread managed by the Go runtime. It differs from OS threads as it uses a smaller initial stack (2KB) and is multiplexed onto multiple OS threads. This makes Goroutines more efficient for handling concurrency.

82. How does the Go scheduler work?

The Go scheduler uses a work-stealing algorithm with M:N scheduling, where M represents OS threads and N represents Goroutines. It schedules Goroutines across available OS threads and CPUs, aiming to balance workload for optimal performance.

83. What is a memory leak, and how do you prevent it in Go?

A memory leak occurs when allocated memory is not released. In Go, it can happen if Goroutines are not terminated or references to objects are kept unnecessarily. Use defer for cleanup and proper cancellation of Goroutines to prevent leaks.

84. How does garbage collection work in Go?

Go uses a concurrent, mark-and-sweep garbage collector. It identifies reachable objects during the mark phase and collects the unreachable ones during the sweep phase, allowing other Goroutines to continue running during collection.

85. Explain differences between sync.Mutex and sync.RWMutex.

  • sync.Mutex is used to provide exclusive access to a shared resource.
  • sync.RWMutex allows multiple readers or one writer at a time, providing better performance for read-heavy workloads.

86. What are race conditions, and how do you detect them in Go?

Race conditions occur when multiple Goroutines access a shared variable concurrently without proper synchronization. Use go run -race to detect race conditions in Go programs.

87. What is a struct tag, and how is it used?

Struct tags provide metadata for struct fields, often used for JSON serialization:

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
Enter fullscreen mode Exit fullscreen mode

88. How do you create a custom error in Go?

Create a custom error by implementing the error interface:

type MyError struct {
    Msg string
}
func (e *MyError) Error() string {
    return e.Msg
}
Enter fullscreen mode Exit fullscreen mode

89. What is a nil pointer dereference, and how do you avoid it?

A nil pointer dereference occurs when you attempt to access the value a nil pointer points to. Avoid this by checking for nil before using pointers.

90. Explain the difference between sync.Pool and garbage collection.

sync.Pool is used for reusing objects and reducing GC pressure. It provides a way to cache reusable objects, unlike the GC which automatically frees unused memory.

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

Use channels to distribute tasks and manage worker Goroutines:

jobs := make(chan int, 100)
for w := 1; w <= 3; w++ {
    go worker(w, jobs)
}
Enter fullscreen mode Exit fullscreen mode

92. What is reflect in Go?

The reflect package allows runtime inspection of types and values. It is used for dynamic operations like inspecting struct fields or methods.

93. What is the difference between buffered and unbuffered channels?

  • A buffered channel has a capacity, allowing Goroutines to send data without blocking until the buffer is full.
  • An unbuffered channel has no capacity and blocks until the receiver is ready.

94. How do you avoid Goroutine leaks?

Ensure Goroutines are terminated using context for cancellation or using timeouts with channels.

95. What are the key differences between panic and error?

  • error is used for handling expected conditions and can be returned.
  • panic is used for unexpected conditions and stops the normal flow of execution.

96. Explain the io.Reader and io.Writer interfaces.

io.Reader has a Read method for reading data, while io.Writer has a Write method for writing data. They form the basis of Go's I/O abstractions.

97. What is a nil value interface, and why is it problematic?

A nil value interface is an interface with a nil underlying value. It can cause unexpected behavior when check nil, as an interface with a nil underlying value is not equal to nil.

type MyInterface interface{}
var i MyInterface
var m map[string]int
i = m // This case, m is nil but i not nil
Enter fullscreen mode Exit fullscreen mode

To handle above case, we could use interface assertion as following

    if v, ok := i.(map[string]int); ok && v != nil {
        fmt.Printf("value not nil: %v\n", v)
    }
Enter fullscreen mode Exit fullscreen mode

98. How do you prevent deadlocks in concurrent Go programs?

To prevent deadlocks, ensure that:

  • Locks are always acquired in the same order across all Goroutines.
  • Use defer to release locks.
  • Avoid holding a lock while calling another function that might acquire the same lock.
  • Limit the use of channels within locked sections.

99. How do you optimize the performance of JSON encoding/decoding in Go?

  • Use jsoniter or easyjson libraries for faster encoding/decoding than the standard encoding/json.
  • Predefine struct fields using json:"field_name" tags to avoid reflection costs.
  • Use sync.Pool to reuse json.Encoder or json.Decoder instances when encoding/decoding large JSON data repeatedly.

100. What is the difference between GOMAXPROCS and runtime.Gosched()?

  • GOMAXPROCS controls the maximum number of OS threads that can execute Goroutines concurrently. It allows adjusting the parallelism level.
  • runtime.Gosched() yields the processor, allowing other Goroutines to run. It does not suspend the current Goroutine but instead gives a chance for the Go scheduler to run other Goroutines.

If you found this helpful, let me know by leaving a 👍 or a comment!, or if you think this post could help someone, feel free to share it! Thank you very much! 😃

Top comments (0)