10 most frequent questions for Golang Job Interview with code examples (Part 2)
This is the second part of “Frequent questions”. You can read Part 1 here.
Following up on the previous article, here are 10 more popular Golang questions that mid-level developers often encounter. Understanding these will further solidify your Go programming knowledge and prepare you for more complex coding challenges.
- How Does Go Handle Memory Management?
Answer: Go uses a garbage collector (GC) to manage memory automatically, which frees up unused memory without requiring explicit deallocation by the programmer. Go’s GC is optimized for low-latency applications, and it uses techniques like concurrent marking and sweeping.
2. What is a Slice, and How Does it Differ from an Array in Go?
Answer: A slice is a dynamically-sized, flexible view into the elements of an array. Unlike arrays, slices can grow and shrink, and they are more commonly used in Go. Slices have a pointer to the underlying array, a length, and a capacity.
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5} // Array
s := arr[1:4] // Slice
fmt.Println(s) // Output: [2 3 4]
}
3. How Do You Implement a Custom Stringer Interface in Go?
Answer: The fmt.Stringer
interface is used to define how a type should be formatted as a string. It has a single method String() string
. Implementing this interface allows your types to have custom string representations when printed.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}
func main() {
person := Person{"Alice", 30}
fmt.Println(person) // Output: Alice (30 years old)
}
4. What are Go’s Method Receivers, and What’s the Difference Between Value and Pointer Receivers?
Answer: Method receivers define which type a method is associated with. A value receiver operates on a copy of the value, while a pointer receiver allows the method to modify the original value.
package main
import "fmt"
type Counter struct {
value int
}
// Value receiver
func (c Counter) Increment() {
c.value++
}
// Pointer receiver
func (c *Counter) IncrementByPointer() {
c.value++
}
func main() {
c := Counter{10}
c.Increment()
fmt.Println(c.value) // Output: 10 (unchanged)
c.IncrementByPointer()
fmt.Println(c.value) // Output: 11 (modified)
}
5. What is the Purpose of the init()
Function in Go?
Answer: The init()
function is a special function that runs automatically when a package is initialized. It’s used to set up initial states, such as registering types, setting default values, or initializing resources. Every package can have multiple init()
functions.
Also we need to mention the ordering in which init()
functions are executed. Imagine we have main package that imports package 1 and 2, and the package 1 imports pacakge 3. As the result we will have this swquence: Pacakge 3 > Package 1 > > Package 2 > main.
NOTE: usage of init()
function kind of “breaks” the normal flow and it’s usually considered as a bad practice to use it frequently.
package main
import "fmt"
var initialized bool
func init() {
initialized = true
fmt.Println("Initialized:", initialized)
}
func main() {
fmt.Println("Main function")
}
6. How Does Type Assertion Work in Go?
Answer: Type assertion is a way to extract the concrete value from an interface. It allows you to access the underlying value if the interface holds the expected type.
package main
import "fmt"
func main() {
var i interface{} = "Hello"
// Type assertion
s, ok := i.(string)
if ok {
fmt.Println(s) // Output: Hello
} else {
fmt.Println("Type assertion failed")
}
}
7. How Do You Use Go’s panic
and recover
?
Answer: panic
is used to cause a program to crash and produce a runtime error, typically in situations where the program cannot continue. recover
is used to handle a panic gracefully, allowing the program to regain control and potentially recover from the error.
package main
import "fmt"
func mayPanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from:", r)
}
}()
panic("Something went wrong!")
}
func main() {
mayPanic()
fmt.Println("Program continues")
}
8. What is the unsafe
Package, and When Should It Be Used?
Answer: The unsafe
package in Go allows you to perform low-level memory manipulation. It provides functions for converting pointers to different types, accessing memory addresses, and more. However, it bypasses Go’s type safety and should be used with caution.
package main
import (
"fmt"
"unsafe"
)
func main() {
var i int = 42
var f float64 = *(*float64)(unsafe.Pointer(&i))
fmt.Println(f) // Undefined behavior, use with caution
}
9. How Do You Implement a Concurrent Worker Pool in Go?
Answer: A worker pool is a common concurrency pattern in Go, where you spawn multiple Goroutines to process jobs from a shared queue.
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Println("Worker", id, "started job", j)
results <- j * 2
}
}
func main() {
const numWorkers = 3
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
for r := range results {
fmt.Println("Result:", r)
}
}
10. What are Go’s Built-in Functions, and How Are They Used?
Answer: Go has several built-in functions, such as len()
, cap()
, make()
, new()
, copy()
, append()
, and panic()
. These functions provide essential utilities for handling basic operations on slices, arrays, channels, and more.
package main
import "fmt"
func main() {
s := make([]int, 0, 5) // Slice with capacity 5
s = append(s, 1, 2, 3)
fmt.Println("Slice length:", len(s)) // Output: 3
fmt.Println("Slice capacity:", cap(s)) // Output: 5
}
In the next part we will cover some extra questions that are a bit more complicated.