Top 50 Go Interview Questions for Entry-Level Developers
Go (Golang) has become one of the most popular programming languages for backend development, cloud infrastructure, and microservices. Created by Google in 2009, Go emphasizes simplicity, performance, and built-in concurrency support. For entry-level developers preparing for interviews, mastering the fundamentals is essential. This tutorial covers the top 50 Go interview questions, complete with practical code examples, explanations, and best practices.
Why Go Matters
Go matters because it combines the performance of compiled languages like C++ with the simplicity of dynamic languages like Python. Its goroutines and channels make concurrent programming approachable, while its fast compilation times and single-binary deployment model make it ideal for modern cloud-native applications. Companies like Google, Uber, Twitch, and Dropbox rely heavily on Go in production.
Section 1: Go Basics (Questions 1–10)
1. What is Go, and what are its key features?
Go is a statically typed, compiled programming language designed for simplicity and efficiency. Key features include garbage collection, goroutines for lightweight concurrency, channels for communication, fast compilation, a rich standard library, and cross-platform compilation.
2. How do you declare variables in Go?
Go offers multiple ways to declare variables:
package main
import "fmt"
func main() {
var a int // zero value 0
var b string = "hi" // explicit type and value
var c = 3.14 // type inferred
d := 42 // short declaration (function scope only)
fmt.Println(a, b, c, d)
}
3. What is the difference between var and :=?
var can be used at package and function level, while := (short declaration) is only available inside functions. The short form also infers the type automatically and cannot be used for package-level declarations.
4. What are zero values in Go?
Variables declared without an explicit initial value are given their zero value: 0 for numeric types, false for booleans, "" for strings, and nil for pointers, slices, maps, channels, functions, and interfaces.
5. What is the difference between const and var?
const declares a value that cannot change after compilation, while var declares a mutable variable. Constants must be assigned at declaration and can only be of character, string, boolean, or numeric types.
const Pi = 3.14159
const Greeting = "Hello, Go"
var counter = 0
counter = 5 // valid
// Pi = 3.0 // compile error
6. How does Go handle multiple return values?
Functions in Go can return multiple values, commonly used for returning results and errors:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
result, err := divide(10, 2)
if err != nil {
fmt.Println(err)
}
fmt.Println(result)
7. What is a package in Go?
A package is a collection of Go files in the same directory that are compiled together. Every Go program starts with a package declaration. The main package is special—it defines an executable program with a main() function.
8. What is the difference between exported and unexported identifiers?
Identifiers starting with an uppercase letter are exported (public) and accessible from other packages. Identifiers starting with a lowercase letter are unexported (private) and only accessible within the same package.
9. How do you import packages in Go?
import (
"fmt"
"strings"
"math/rand"
)
You can also use aliased imports (f "fmt"), dot imports (not recommended), and blank imports (_ "package") for side effects like registering drivers.
10. What is go mod?
go mod is Go's module management system. A go.mod file defines the module path and dependencies. You initialize a module with go mod init module-name and add dependencies with go get.
Section 2: Data Types and Structures (Questions 11–20)
11. What are the basic data types in Go?
Go has boolean (bool), string (string), numeric types (int, int8, int16, int32, int64, uint variants, float32, float64, complex64, complex128), and byte (alias for uint8) and rune (alias for int32, represents a Unicode code point).
12. What is the difference between an array and a slice?
Arrays have a fixed size defined at compile time, while slices are dynamic, reference-based views into arrays. Slices are far more common in Go because of their flexibility.
var arr [3]int = [3]int{1, 2, 3} // fixed-size array
s := []int{1, 2, 3} // slice
s = append(s, 4) // dynamic growth
fmt.Println(arr, s)
13. How do slices work internally?
A slice is a struct containing a pointer to the underlying array, a length, and a capacity. When you append beyond capacity, Go allocates a new larger array and copies elements over.
14. What is a map in Go?
A map is an unordered collection of key-value pairs. Keys must be comparable types. Maps are reference types.
m := map[string]int{
"apple": 5,
"banana": 3,
}
m["cherry"] = 8
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
15. How do you check if a key exists in a map?
m := map[string]int{"a": 1}
value, ok := m["a"]
if ok {
fmt.Println("Found:", value)
}
The ok boolean tells you whether the key exists.
16. What is a struct in Go?
A struct is a user-defined type that groups related fields. Go structs do not support inheritance but support composition through embedding.
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name)
17. What is struct embedding?
Embedding promotes the fields and methods of an embedded type into the outer struct, achieving composition:
type Address struct {
City string
}
type Employee struct {
Address
Name string
}
e := Employee{Name: "Bob"}
e.City = "NYC" // promoted field
fmt.Println(e)
18. What is the difference between a pointer and a value?
A value is a copy of the data, while a pointer holds the memory address of the data. Pointers allow you to modify the original value and avoid copying large structs.
func increment(n *int) {
*n++
}
x := 5
increment(&x)
fmt.Println(x) // 6
19. How do you create a pointer in Go?
Use the & operator to get an address, or the new function to allocate and return a pointer. Go also has no pointer arithmetic (unlike C).
20. What is a string in Go?
A string in Go is an immutable sequence of bytes. Strings are UTF-8 encoded by convention. To work with Unicode characters, convert to a []rune slice.
s := "Hello, 世界"
for i, r := range s {
fmt.Printf("%d: %c\n", i, r)
}
Section 3: Functions and Methods (Questions 21–28)
21. How do you define a function in Go?
func add(a int, b int) int {
return a + b
}
// shorthand for same-type params
func multiply(a, b int) int {
return a * b
}
22. What are variadic functions?
Variadic functions accept a variable number of arguments, treated as a slice inside the function:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println(sum(1, 2, 3, 4)) // 10
23. What are named return values?
Named returns are declared in the function signature and can be used as variables. A naked return statement returns them automatically:
func swap(a, b int) (x, y int) {
x = b
y = a
return // naked return
}
24. What is the difference between a function and a method?
A method is a function with a receiver argument, allowing it to be called on a value of a specific type:
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
rect := Rectangle{Width: 5, Height: 3}
fmt.Println(rect.Area())
25. What is the difference between value receivers and pointer receivers?
Value receivers operate on a copy of the value, while pointer receivers operate on the original. Use pointer receivers when you need to modify the receiver or to avoid copying large structs.
26. Are functions first-class citizens in Go?
Yes. Functions can be assigned to variables, passed as arguments, and returned from other functions. This makes Go support functional programming patterns:
func apply(f func(int) int, x int) int {
return f(x)
}
result := apply(func(n int) int { return n * n }, 5)
fmt.Println(result) // 25
27. What is a closure in Go?
A closure is a function value that references variables from outside its body. The function "closes over" those variables and retains access to them:
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c(), c(), c()) // 1 2 3
28. What is defer in Go?
defer schedules a function call to run when the surrounding function returns. Deferred calls are executed in LIFO order, making them ideal for cleanup tasks like closing files or releasing locks.
func readFile() {
file, err := os.Open("data.txt")
if err != nil {
return
}
defer file.Close()
// read file
}
Section 4: Interfaces and Polymorphism (Questions 29–34)
29. What is an interface in Go?
An interface is a type that defines a set of method signatures. Any type that implements those methods satisfies the interface implicitly—no explicit declaration is required.
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof"
}
var s Speaker = Dog{}
fmt.Println(s.Speak())
30. How is Go's interface different from interfaces in Java or C#?
Go interfaces are implemented implicitly. You don't need to declare that a type implements an interface. This enables loose coupling and the definition of interfaces after the types exist.
31. What is the empty interface?
The empty interface interface{} (or any in Go 1.18+) has no methods, so every type satisfies it. It's used for values of unknown type, similar to Object in Java:
func printAny(v any) {
fmt.Println(v)
}
printAny(42)
printAny("hello")
32. What is type assertion?
Type assertion extracts the underlying concrete value from an interface:
var i any = "hello"
s, ok := i.(string)
if ok {
fmt.Println(s)
}
33. What is a type switch?
A type switch allows you to test an interface value against multiple types:
func describe(i any) {
switch v := i.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Println("unknown type")
}
}
34. What is the difference between a nil interface and an interface holding a nil value?
A nil interface has no underlying type. An interface holding a nil pointer is not nil itself because it has a type. This is a common source of bugs:
var p *int = nil
var i any = p
fmt.Println(i == nil) // false
Section 5: Concurrency (Questions 35–42)
35. What is a goroutine?
A goroutine is a lightweight thread managed by the Go runtime. You create one with the go keyword. Goroutines are cheaper than OS threads, with small stack sizes that grow as needed.
func sayHello(name string) {
fmt.Println("Hello,", name)
}
func main() {
go sayHello("Alice")
go sayHello("Bob")
time.Sleep(time.Second)
}
36. What is a channel?
A channel is a typed conduit for sending and receiving values between goroutines, enabling safe communication and synchronization:
ch := make(chan int)
go func() {
ch <- 42
}()
value := <-ch
fmt.Println(value)
37. What is the difference between buffered and unbuffered channels?
Unbuffered channels block the sender until a receiver is ready. Buffered channels (make(chan int, n)) allow sending up to n values without a receiver.
ch := make(chan int, 2)
ch <- 1
ch <- 2
// ch <- 3 // would block
fmt.Println(<-ch, <-ch)
38. How do you close a channel, and why?
Use close(ch) to indicate no more values will be sent. Receivers can detect closure with a second return value. Closing is the sender's responsibility, and sending on a closed channel panics.
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for v := range ch {
fmt.Println(v)
}
39. What is the select statement?
select lets a goroutine wait on multiple channel operations simultaneously, choosing the first one that becomes ready:
select {
case msg := <-ch1:
fmt.Println("from ch1:", msg)
case msg := <-ch2:
fmt.Println("from ch2:", msg)
case <-time.After(time.Second):
fmt.Println("timeout")
}
40. What is a race condition, and how do you detect it?
A race condition occurs when multiple goroutines access shared data without synchronization. Use the -race flag to detect races: go run -race main.go. The race detector reports unsafe concurrent access.
41. What is sync.WaitGroup?
WaitGroup waits for a collection of goroutines to finish. You call Add before starting each goroutine, Done when it finishes, and Wait to block until all complete:
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println("worker", n)
}(i)
}
wg.Wait()
42. What is sync.Mutex?
A mutex (mutual exclusion lock) protects shared data from concurrent access. Use Lock and Unlock (often with defer) to guard critical sections:
var (
mu sync.Mutex
count int
)
func increment() {
mu.Lock()
defer mu.Unlock()
count++
}
Section 6: Error Handling (Questions 43–46)
43. How does Go handle errors?
Go handles errors as values. Functions return an error interface value, and the caller checks it. There are no exceptions in Go (except panic/recover for truly exceptional cases).
file, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
44. How do you create a custom error?
Use fmt.Errorf or implement the error interface (a single Error() string method):
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("error %d: %s", e.Code, e.Msg)
}
func doSomething() error {
return &MyError{Code: 404, Msg: "not found"}
}
45. What is the difference between panic and error?
An error is a normal return value for expected failures. A panic is for unrecoverable, unexpected conditions (like programming errors). Panics unwind the stack and run deferred functions, and can be caught with recover.
46. How do panic and recover work together?
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
return a / b, nil
}
res, err := safeDiv(1, 0)
fmt.Println(res, err)
Section 7: Best Practices and Tooling (Questions 47–50)
47. What is gofmt and why is it important?
gofmt automatically formats Go source code according to standard style rules. It eliminates style debates and ensures consistency across codebases. Most editors run it on save. Related tools include goimports, which also manages imports.
48. How do you write tests in Go?
Go has a built-in testing package. Test functions start with Test and accept a *testing.T argument. Run tests with go test:
package main
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2,3) = %d; want %d", got, want)
}
}
49. What are some Go best practices for entry-level developers?
- Always check errors immediately after the call that may produce them.
- Use
deferfor cleanup operations like closing files and unlocking mutexes. - Prefer composition over inheritance using struct embedding and interfaces.
- Keep interfaces small; define them where they are consumed, not where types are defined.
- Avoid global state; pass dependencies explicitly.
- Use meaningful, idiomatic names:
camelCasefor locals,PascalCasefor exported identifiers. - Run
go vetandgofmtregularly. - Document exported functions, types, and packages with comments starting with the identifier name.
- Prefer channels for communication between goroutines; use mutexes for protecting shared state.
- Write table-driven tests for thorough coverage.
50. What is the difference between make and new?
new(T) allocates memory for a value of type T, zero-initializes it, and returns a pointer (*T). make is used only for slices, maps, and channels—it initializes and returns the value itself (not a pointer), with proper internal structure:
p := new(int) // *int, value 0
s := make([]int, 5) // []int with len 5
m := make(map[string]int)
c := make(chan int)
Conclusion
Mastering these 50 Go interview questions gives entry-level developers a strong foundation in the language's core concepts, from basic syntax and data structures to concurrency and error handling. Go's design philosophy rewards simplicity and clarity, so focus on writing idiomatic code, checking errors diligently, and leveraging the standard library before reaching for third-party packages. Practice by building small projects—such as a CLI tool, a REST API, or a concurrent worker pool—and write tests for everything. With consistent practice and a deep understanding of these fundamentals, you'll be well-prepared to demonstrate your Go knowledge in any entry-level interview and contribute confidently to production Go codebases.