Interview prep
Go-Specific Interview Questions
Kumpulan soal Go yang sering keluar di interview backend: goroutine leak, race condition, context cancellation, channel patterns, error wrapping β lengkap dengan kode dan penjelasan.
1. Goroutine Leak Detection
Soal
"Temukan goroutine leak di kode ini dan perbaiki."
func fetchURLs(urls []string) []string {
results := []string{}
for _, url := range urls {
go func(u string) {
resp, _ := http.Get(u)
body, _ := io.ReadAll(resp.Body)
results = append(results, string(body)) // race condition juga
}(url)
}
return results
}Masalah
- Goroutine leak:
mainreturn sebelum goroutine selesai - Race condition:
resultsdi-append dari multiple goroutine tanpa mutex - No error handling:
resp.Bodygak di-close β connection leak - Unbounded concurrency: kalau
urlsada 10K, spawn 10K goroutine β DOS diri sendiri
Solusi
func fetchURLs(ctx context.Context, urls []string, maxConcurrency int) ([]string, error) {
urlsCh := make(chan string, len(urls))
resultsCh := make(chan fetchResult, len(urls))
// Fan-out: spawn worker pool
var wg sync.WaitGroup
for i := 0; i < maxConcurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for url := range urlsCh {
resp, err := fetchOne(ctx, url)
resultsCh <- fetchResult{url: url, body: resp, err: err}
}
}()
}
// Send work
for _, url := range urls {
urlsCh <- url
}
close(urlsCh)
// Wait for workers
go func() { wg.Wait(); close(resultsCh) }()
// Collect results
results := make([]string, 0, len(urls))
for r := range resultsCh {
if r.err != nil {
return nil, r.err
}
results = append(results, r.body)
}
return results, nil
}
type fetchResult struct {
url string
body string
err error
}
func fetchOne(ctx context.Context, url string) (string, error) {
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("read body %s: %w", url, err)
}
return string(body), nil
}2. Channel Patterns
Fan-Out / Fan-In
// Fan-out: satu input channel β banyak worker
func fanOut(ctx context.Context, input <-chan Task, workers int) []<-chan Result {
channels := make([]<-chan Result, workers)
for i := 0; i < workers; i++ {
channels[i] = worker(ctx, input)
}
return channels
}
// Fan-in: banyak channel β satu output channel
func fanIn(ctx context.Context, channels []<-chan Result) <-chan Result {
output := make(chan Result)
var wg sync.WaitGroup
wg.Add(len(channels))
for _, ch := range channels {
go func(c <-chan Result) {
defer wg.Done()
for r := range c {
select {
case output <- r:
case <-ctx.Done():
return
}
}
}(ch)
}
go func() { wg.Wait(); close(output) }()
return output
}Pipeline Pattern
// Generate numbers β Square β Print
func generator(ctx context.Context, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-ctx.Done():
return
}
}
}()
return out
}
func square(ctx context.Context, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-ctx.Done():
return
}
}
}()
return out
}3. Context Cancellation & Timeout
Interview Question
"Sebutkan best practices untuk context di Go."
Jawaban
- Context selalu jadi parameter PERTAMA:
func Process(ctx context.Context, data Data) error - Jangan simpan context di struct β pass sebagai parameter
- Turunkan context, jangan buat dari
context.Background()di dalam function - Selalu
defer cancel()setelahcontext.WithTimeout/WithCancel - Check
ctx.Err()di loop untuk early return
func processWithTimeout(ctx context.Context, items []Item) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
for _, item := range items {
select {
case <-ctx.Done():
return fmt.Errorf("process interrupted: %w", ctx.Err())
default:
// Process item
if err := processOne(ctx, item); err != nil {
return err
}
}
}
return nil
}4. Error Wrapping
// β
Good: wrap dengan context
func GetUser(ctx context.Context, id string) (*User, error) {
user, err := db.QueryUser(ctx, id)
if err != nil {
return nil, fmt.Errorf("GetUser(%s): %w", id, err)
}
return user, nil
}
// β
Good: custom error type untuk sentinel checking
var ErrInsufficientBalance = errors.New("insufficient balance")
func Transfer(ctx context.Context, from, to string, amount int64) error {
balance, err := GetBalance(ctx, from)
if err != nil {
return fmt.Errorf("Transfer: get balance: %w", err)
}
if balance < amount {
return fmt.Errorf("Transfer: %w (balance=%d, need=%d)",
ErrInsufficientBalance, balance, amount)
}
// ...
}
// β
Good: errors.Is + errors.As
if errors.Is(err, ErrInsufficientBalance) {
// Handle specific error
}5. Race Condition Detection
# Run tests with race detector
go test -race ./...
# Run binary with race detector
go build -race && ./binaryCommon Race Condition Patterns
// β BAD: closure captures loop variable
for _, item := range items {
go func() {
process(item) // item is the SAME variable β race!
}()
}
// β
GOOD: pass as parameter
for _, item := range items {
go func(i Item) {
process(i)
}(item)
}
// β BAD: write to map without mutex
var cache = map[string]Result{}
func Set(k string, v Result) {
cache[k] = v // fatal error: concurrent map writes
}
// β
GOOD: sync.Map atau mutex
var cache sync.Map
func Set(k string, v Result) {
cache.Store(k, v)
}Edit on GitHub
Last updated on