Golang Interview Questions and Answers

Golang, or Go, is one of the most sought-after programming languages in the market today. This is largely due to its simplicity and incredible performance capabilities. Its concurrency features make “Go” to programming language for cloud services, microservices, and applications where performance is of utmost importance.

Table of Contents:

In this guide, we have tried to compile a complete set of Golang interview questions, from basic to more advanced and even real-world scenario questions, all designed to freshen your Golang coding skills before your big interview. Let’s begin.

Golang Interview Questions for Freshers

1. What is Go (Golang), and why has it become so popular?

Go (Golang) is a programming language that was developed by Google in 2009. It was designed to combine the efficiency of low-level languages such as C, and the simplicity and readability of more modern languages like Python.

Here are some of the main reasons why Golang has become so popular in recent years.

  • Its concurrency features, such as goroutines and channels, make it an ideal choice for cloud and distributed systems.
  • Fast compilation and execution speed.
  • Strong standard library that supports networking, file handling, and web servers.
  • Reduced runtime errors facilitated by memory safety and garbage collection.

2. What is Go (Golang), and why has it become so popular?

Yes, this is one of the USPs of Golang. Unlike most high-level programming languages available today, a function in Go can return more than one value.

Example:

package main

import "fmt"

func divide(a, b int) (int, error) {

    if b == 0 {

        return 0, fmt.Errorf("division by zero not allowed")

    }

    return a / b, nil

}

func main() {

    result, err := divide(10, 2)

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Result:", result)

    }

}

Output:

what-is-go-lang

  • Function returns two values: result and error.
  • If b == 0, an error is returned.
  • If no error, the division result is returned.
  • Makes error handling simple and explicit.

This allows error handling in Go to be clean and explicit.

3. What is the difference between arrays and slices in Go?

  • Arrays in Go have a fixed length, which is defined at declaration.
  • Slices are the dynamic version of arrays, designed to support resizing and powerful operations.
arr := [3]int{1, 2, 3}       // Fixed size array

slice := []int{1, 2, 3, 4}   // Slice (dynamic)

slice = append(slice, 5)     // Now slice = [1 2 3 4 5]

Due to its flexibility, slice is the preferred approach in real-world applications.

4. What are goroutines in Go?

Goroutines in Go are lightweight threads managed by the runtime. They allow functions to be able to run concurrently without sacrificing performance by optimizing memory usage.

Example:

package main

import (

    "fmt"

    "time"

)

func printMessage(msg string) {

    for i := 0; i < 3; i++ {

        fmt.Println(msg)

        time.Sleep(500 * time.Millisecond)

    }

}

func main() {

    go printMessage("Hello from goroutine!")

    printMessage("Hello from main!")

}

Output:

go_routlines

  • In the example, printMessage runs both in main and as a goroutine.
  • Output interleaves, showing concurrent execution.

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

When you send a value to an unbuffered channel, the operation pauses until a goroutine is ready to receive it. This facilitates synchronization as both parties need to be ready at the same time. 

Buffered channels, on the other hand, allow you to specify a capacity during creation. You can send values into the channel until it is full without needing to wait for a receiver. Once the buffer is full, further sends will be blocked until space is freed up.

Example:

ch1 := make(chan int)        // unbuffered

ch2 := make(chan int, 3)     // buffered with size 3

6. How do pointers work in Go?

A pointer stores the memory address of a variable. Unlike C, Go does not allow pointer arithmetic, which makes it safer.

x := 42

p := &x

fmt.Println(*p) // prints 42

7. What are zero values in Go?

In Go, when variables are declared but not initialized, a default value is assigned to them based on the data type.

  • 0 for numbers
  • “” for strings
  • false for booleans
  • nil for pointers, slices, maps, and interfaces

Example:

var s string

var n int

var b bool

fmt.Println(s, n, b) // "" 0 false

8. What are string literals, runes, and bytes in Go?

  • String literals: These are the normal text you type in quotes, like “Hello”. Strings in Go are made up of a sequence of characters.
  • Bytes (uint8): Each character in a string has an underlying numeric value called a byte. Bytes are perfect for representing ASCII characters (basic English letters, numbers, and symbols).
  • Runes (int32): A rune represents a single Unicode character, which can be a letter, symbol, or emoji from any language. Run the program below and you’ll see how Go distinguishes bytes and runes:

Example:

str := "Go!"

fmt.Println([]byte(str))  // [71 111 33]

fmt.Println([]rune(str))  // [71 111 33]

9. What is the difference between var, short declaration (:=), and const in Go?

  • var: Used for explicit declaration.
  • :=: Short declaration inside functions.
  • const: Immutable values known at compile time.

Golang Interview Questions For Experienced Developers

1. How does error handling in Go differ from exceptions in other languages?

Unlike Python or Java, Go does not use exceptions. It instead encourages explicit error handling by returning an error value as the second return type.

func readFile(name string) ([]byte, error) {

    data, err := os.ReadFile(name)

    if err != nil {

        return nil, err

    }

    return data, nil

}

This makes error handling more robust and easier to predict.

2. What are type assertions and type switches in Go?

A type assertion is used to let you access the underlying value of an interface:

var i interface{} = "hello"

s, ok := i.(string)   // ok = true

A type switch checks multiple types at once:

switch v := i.(type) {

case string:

    fmt.Println("string:", v)

case int:

    fmt.Println("int:", v)

default:

    fmt.Println("unknown type")

}

3. What is variable shadowing in Go?

When a variable declared in an inner scope hides another variable with the same name in an outer scope, it is referred to as shadowing.

x := 10

if true {

    x := 20   // shadows outer x

    fmt.Println(x) // 20

}

fmt.Println(x) // 10

Inside the if block, a new x is created, hiding the outer x. Outside the block, the original x = 10 is still intact.

Note: Shadowing can cause bugs if not carefully managed.

4. What are variadic functions in Go?

A variadic function accepts zero or more arguments of the same type.

Example:

func sum(nums ...int) int {

    total := 0

    for _, v := range nums {

        total += v

    }

    return total

}

fmt.Println(sum(1, 2, 3, 4)) // 10

5. What is the use of an empty struct in Go?

An empty struct (struct{}) takes zero bytes of memory. It’s often used:

  • As a signal in channels.
  • For set-like data structures using maps.

Example:

seen := make(map[string]struct{})

seen["item1"] = struct{}{}

6. What is the difference between GOPATH and GOROOT?

  • GOROOT: The directory where Go is installed (compiler, standard library).
  • GOPATH: The workspace for your Go projects and third-party packages.

7. What are the best practices when working with maps and slices in Go?

  • Always check if a key exists before using like so:
val, ok := myMap["key"]

if ok { fmt.Println(val) }

Initialize slices/maps properly before use.

  • Prefer make slices and maps with an expected size to improve performance.
  • Avoid modifying slices while iterating.

8. How does Go handle nil slices and nil maps? 

  • A nil slice behaves like an empty slice. It can be appended to, and Go will allocate storage automatically when needed.
  • A nil map, however, cannot be written to. You can safely read from it, but writing will cause a runtime panic unless it’s initialized with make.

Example:

var s []int   // nil slice

fmt.Println(len(s)) // 0

var m map[string]int // nil map

fmt.Println(m["a"])  // 0 (safe read)

m["a"] = 1           // panic: assignment to entry in nil map

9. How do defer statements work in Go? 

The defer statement postpones function execution until the surrounding function returns. It’s often used for cleanup.

func main() {

    f, _ := os.Open("file.txt")

    defer f.Close() // ensures resource cleanup

}

10. How does Go’s pass-by-value affect functions that modify slices and maps?

All values in Go are passed by value. However, slices and maps are reference types, so their headers are copied but still point to the same underlying data.

func modifySlice(s []int) {

    s[0] = 99

} 

nums := []int{1, 2, 3}

modifySlice(nums)

fmt.Println(nums) // [99 2 3]

Even though Go passes everything by value, a slice is just a small header (pointer, length, capacity). When copied, both slices still point to the same array, so changes inside the function reflect outside.

Advanced Golang Interview Questions

1. What are race conditions in Go, and how do you fix them?

When a goroutine tries to read and write the same variable at the same time, it leads to unpredictable results. This is known as a race condition.

Example (race):

var counter int
func main() {

    for i := 0; i < 1000; i++ {

        go func() { counter++ }()

    }

    time.Sleep(time.Second)

    fmt.Println("Counter:", counter) // unpredictable

}

Fix: Use sync.Mutex for locking:

var mu sync.Mutex

mu.Lock()

counter++

mu.Unlock()

2. How does the select statement help in concurrency?

The select statement in Go lets a goroutine wait on multiple channel operations at once. The channel operation that finishes first is picked.

select {

case msg := <-ch1:

    fmt.Println("Received from ch1:", msg)

case msg := <-ch2:

    fmt.Println("Received from ch2:", msg)

default:

    fmt.Println("No channel ready")

}
  • If ch1 has data, the first case runs.
  • If ch2 has data, the second case runs.
  • If neither is ready, the default branch executes immediately.

3. How is the context package used for cancellation and timeouts?

context.Context allows you to cancel goroutines or set timeouts across API calls

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)

defer cancel()

ch := make(chan string)

go func() {

    time.Sleep(3 * time.Second)

    ch <- "done"

}()

select {

case <-ctx.Done():

    fmt.Println("Timeout:", ctx.Err())

case res := <-ch:

    fmt.Println("Result:", res)

}
  • A context with a 2-second timeout is created.
  • A goroutine simulates a task that takes 3 seconds.
  • The select waits:
    • Since the context expires before the goroutine sends, the ctx.Done() case triggers.
    • Output: Timeout: context deadline exceeded.

4. How would you implement a worker pool in Go?

A worker pool allows you to process multiple jobs concurrently without having to spawn thousands of goroutines.

Example:

func worker(id int, jobs <-chan int, results chan<- int) {

    for j := range jobs {

        results <- j * 2

    }

}

func main() {

    jobs := make(chan int, 5)

    results := make(chan int, 5)

    for w := 1; w <= 3; w++ {

        go worker(w, jobs, results)

    }

    for j := 1; j <= 5; j++ {

        jobs <- j

    }

    close(jobs)

    for r := 1; r <= 5; r++ {

        fmt.Println(<-results)

    }

}
  • 3 workers are launched, all waiting to receive from the jobs channel.
  • 5 jobs (numbers 1–5) are sent into the channel.
  • Each worker takes jobs, processes them (multiplies by 2), and sends the results back.
  • The main goroutine collects the results and prints them.

5. What is the difference between defer, panic, and recover in Go?

  • defer: Runs a function after the surrounding function returns (often cleanup).
  • panic: Stops normal execution (similar to an exception).
  • recover: Regains control inside a deferred function after a panic.

6. How does Go’s garbage collector work?

The garbage collection in Go is concurrent and non-generational, which is optimal for low latency. It runs alongside goroutines, reclaims unused memory, and minimizes pause times.

  • Unlike Java’s GC, Go focuses on short, frequent collections instead of long pauses.
  • Developers can optimize memory by reusing slices, avoiding large allocations, and reducing pointer-heavy structures.

7. How are generics used in Go 1.23+?

Generics let you write functions and types that work with any type, instead of repeating code.

Example:

func Sum[T int | float64](a, b T) T {

    return a + b

}

fmt.Println(Sum(3, 4))       // int

fmt.Println(Sum(3.5, 4.5))   // float64
  • T is a type parameter, constrained to int or float64.
  • You can call Sum with either integers or floats, and it works without duplicating code.

As of Go 1.23, generics support is more stable, with better performance optimizations and type inference improvements.

8. How do you detect and fix memory leaks in Go using pprof?

pprof is Go’s built-in profiling tool.

Steps:

  • Import net/http/pprof in your service.
  • Run the program and access profiling endpoints: http://localhost:6060/debug/pprof/heap.
  • Use CLI:

go tool pprof http://localhost:6060/debug/pprof/heap

Analyze heap usage, goroutines, and memory allocations to spot leaks.

9. How do you gracefully shut down a Go server?

Use http.Server.Shutdown with a context to stop new requests but allow ongoing ones to finish.

srv := &http.Server{Addr: ":8080"}

go func() { srv.ListenAndServe() }()

stop := make(chan os.Signal, 1)

signal.Notify(stop, os.Interrupt)

<-stop

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

defer cancel()

srv.Shutdown(ctx)
  • The server starts in a goroutine so the main function can keep running.
  • signal.Notify waits for an interrupt signal (e.g., Ctrl+C).
  • Once received, a context with a 5-second timeout is created.
  • srv.Shutdown(ctx) tells the server to:
    • Stop accepting new requests immediately.
    • Allow in-progress requests up to 5 seconds to finish.
  • If requests don’t finish in time, they’re forcefully closed.

10. What is the difference between Goroutines and OS threads?

  • Goroutines are lightweight (KB stack) and multiplexed onto OS threads by Go’s scheduler.
  • Threads are heavier (MB stack) and managed by the OS.

This allows Go to run thousands of goroutines with much lower memory overhead than native threads.

Golang Coding Interview Problems

1. Swap two variables without using a temporary variable.

The easiest way to achieve this is by using Go’s multiple assignment feature.

package main

import "fmt"

func main() {

    a, b := 10, 20

    fmt.Println("Before swap:", a, b)

    a, b = b, a // swap happens here

    fmt.Println("After swap:", a, b)

}

Output:

swap-variables

2. Write a Go program to find the factorial of a number.

A classic problem, often given to test recursion.

package main

import "fmt"

func factorial(n int) int {

    if n == 0 {

        return 1

    }

    return n * factorial(n-1)

}

func main() {

    num := 5

    fmt.Printf("Factorial of %d is %dn", num, factorial(num))

}

Output:

factorial

3. Write a Go program to generate the nth Fibonacci number.

You can do it recursively, but iteration is the better approach for performance.

package main

import "fmt"

func fibonacci(n int) int {

    if n <= 1 {

        return n

    }

    a, b := 0, 1

    for i := 2; i <= n; i++ {

        a, b = b, a+b

    }

    return b

}

func main() {

    n := 10

    fmt.Printf("The %dth Fibonacci number is %dn", n, fibonacci(n))

}

Output:

fibonacci

 

4. Check if a given string is a palindrome.

This tests string manipulation in Go.

package main

import (

    "fmt"

    "strings"

)

func isPalindrome(s string) bool {

    s = strings.ToLower(s)

    i, j := 0, len(s)-1

    for i < j {

        if s[i] != s[j] {

            return false

        }

        i++

        j--

    }

    return true

}

func main() {

    word := "Level"

    fmt.Println(word, "is palindrome?", isPalindrome(word))

}

Output:

palimdrome

5. Compare two slices for equality.

Go doesn’t have built-in deep comparison for slices. So let’s build one manually.

package main

import "fmt"

func equalSlices(a, b []int) bool {

    if len(a) != len(b) {

        return false

    }

    for i := range a {

        if a[i] != b[i] {

            return false

        }

    }

    return true

}

func main() {

    s1 := []int{1, 2, 3}

    s2 := []int{1, 2, 3}

    s3 := []int{1, 2, 4}

    fmt.Println("s1 == s2?", equalSlices(s1, s2))

    fmt.Println("s1 == s3?", equalSlices(s1, s3))

}

Output:

compare_two_slices

Golang MCQ Interview Questions

1. Which of the following is true about goroutines?

  1. A) Goroutines are heavier than OS threads.
  2. B) Goroutines are multiplexed onto OS threads by the Go runtime.
  3. C) Goroutines cannot run concurrently.
  4. D) Goroutines require manual memory allocation.

Answer: B

Explanation: Goroutines are lightweight threads managed by Go’s runtime. Thousands can run concurrently with minimal memory overhead.

2. What will the following Go code print?

package main

import "fmt"

func main() {

    var a []int

    fmt.Println(a == nil)

}
  1. A) true
  2. B) false
  3. C) Compilation error
  4. D) Runtime panic

Answer: A

Explanation: A slice declared but not initialized is nil in Go. Nil slices can be read safely, but cannot be written to without allocation.

3. What does the defer keyword do in Go?

  1. A) Executes a function immediately.
  2. B) Schedules a function to run after the surrounding function returns.
  3. C) Stops execution of a function.
  4. D) Replaces a panic with an error.

Answer: B

Explanation: defer is commonly used for cleanup operations like closing files or unlocking resources.

4. Consider this code snippet:

package main

import "fmt"

func main() {

    x := 10

    if true {

        x := 20

        fmt.Println(x)

    }

    fmt.Println(x)

}

5. What will be printed?

  1. A) 10 10
  2. B) 20 20
  3. C) 20 10
  4. D) Compilation error

Answer: C

Explanation: The inner x shadows the outer x inside the if block. After the block, the outer x is still 10.

6. Which of the following statements is true about channels?

  1. A) Unbuffered channels block the sender until a receiver is ready.
  2. B) Buffered channels always block the sender.
  3. C) Channels can only be used to send integers.
  4. D) Channels are automatically thread-safe maps.

Answer: A

Unbuffered channels make the sender wait until a receiver is ready, ensuring proper sync. Buffered channels don’t always block; they allow values to sit in the buffer until it’s full. Channels can carry any type, not just integers, and they’re not maps.

7. Which Go feature allows a function to accept a variable number of arguments?

  1. A) Variadic functions
  2. B) Type assertions
  3. C) Interfaces
  4. D) Goroutines

Answer: A

Explanation: Variadic functions are declared with … and can accept zero or more arguments of the same type.

8. What will happen if you write to a nil map?

  1. A) It creates a new map automatically.
  2. B) Causes a runtime panic.
  3. C) Writes are ignored silently.
  4. D) Map automatically converts to a slice.

Answer: B

Writing to a nil map triggers a runtime panic. You need to initialize the map with make before adding any key-value pairs.

Real-World Scenario-Based Golang Interview Questions

1. Your Go program hangs indefinitely, and goroutines seem stuck. How would you debug and fix a deadlock?

More often than not, the reason for deadlocks occurring in Go is that goroutines are waiting on each other via channels, and no goroutine can proceed. We can debug this by:

  • Use go run -race to detect race conditions.
  • Use pprof or runtime. Stack to see blocked goroutines.
  • Check if channels are unbuffered and waiting for a receiver.

Fix:

  • Ensure every send has a corresponding receiver.
  • Use buffered channels where appropriate.
  • Consider selecting a default case to avoid permanent blocking.

2. Your Go service gradually consumes more memory and eventually crashes. How do you identify and fix memory leaks?

  • Use Go’s pprof to profile heap usage and identify memory growth.
  • Check for slices or maps that grow unbounded or are never cleared.
  • Close channels, file handles, and network connections properly.
  • Reuse buffers instead of allocating new ones repeatedly.

Even though Go has garbage collection, unreferenced memory held by global variables or lingering slices can still leak.

3. If you needed to download thousands of files concurrently without crashing the system, how would you do it using Go?

  • By using a worker pool pattern. Create a fixed number of goroutines to handle the file downloads.
  • Using buffered channels, queue the download jobs.
  • You can use context.Context for cancellation or timeouts.

For example:

jobs := make(chan string, 100)    // channel for incoming work (URLs)

results := make(chan string, 100) // channel for processed results

// worker function: each worker keeps picking jobs until the channel is closed

func worker(id int, jobs <-chan string, results chan<- string) {

    for url := range jobs {

        // pretend we're downloading a file here

        results <- url // send the completed "result" back

    }

}

// spin up 5 workers running in the background

for w := 1; w <= 5; w++ {

    go worker(w, jobs, results)

}
  • Jobs is a channel carrying work items (like URLs).
  • results is a channel carrying processed outputs.
  • Each worker goroutine continuously reads from jobs, processes, and sends results.
  • By launching 5 workers, multiple jobs can be handled concurrently without creating a goroutine per job.

4. What Go pattern would you use to handle millions of API requests efficiently?

  • Use goroutines with channels to process requests concurrently.
  • Employ connection pooling to reuse network resources.
  • Use caching to reduce repeated computations.
  • Apply rate limiting and context timeouts to prevent overload.
  • Gracefully shut down servers with http.Server.Shutdown to finish ongoing requests.

5. Two or more goroutines need to read/write the same map. How do you prevent race conditions or panics?

  • Go maps are not thread-safe for concurrent writes.
  • Use sync.Mutex or sync.RWMutex to lock the map during writes.
  • Alternatively, use sync.Map for a concurrent-safe map implementation.

Example:

var mu sync.Mutex

myMap := make(map[string]int)

mu.Lock()

myMap["key"] = 42

mu.Unlock()

Conclusion

Go’s simplicity, strong concurrency model, and powerful standard library make it ideal for modern software development, from cloud services to high-performance APIs. This is why companies are paying a whopping 12 LPA for the right candidate.
Preparing for your Golang interview can be difficult, especially if it’s your first time. Completing the exercises and understanding the questions in our Golang interview questions guide will help you gain confidence in your skills and help you clear your technical round with ease.

About the Author

Technical Research Analyst - Full Stack Development

Kislay is a Technical Research Analyst and Full Stack Developer with expertise in crafting Mobile applications from inception to deployment. Proficient in Android development, IOS development, HTML, CSS, JavaScript, React, Angular, MySQL, and MongoDB, he’s committed to enhancing user experiences through intuitive websites and advanced mobile applications.

Full Stack Developer Course Banner