🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Go Interview Questions 2026: Goroutines, Channels, Interfaces and Errors

⏱️4 min read  ·  813 words

Go (Golang) interview questions in 2026 cover goroutines, channels, interfaces, memory management, and production patterns. Go is used at Google, Cloudflare, Docker, Kubernetes, and most cloud-native companies. This guide covers the most commonly asked Go interview questions.

Core Go Concepts

1. What makes Go different from other languages?

  • Goroutines: Lightweight threads (2KB stack vs 1MB OS threads)
  • Channels: First-class communication primitives (“don’t communicate by sharing memory; share memory by communicating”)
  • Fast compilation: Compiles in seconds
  • Single binary: Statically linked, no runtime dependencies
  • Garbage collected: But with very low GC pauses
  • No inheritance: Composition via embedding and interfaces

2. Explain goroutines vs OS threads

package main

import (
	"fmt"
	"runtime"
	"sync"
)

func main() {
	// OS threads: ~1MB stack, managed by OS
	// Goroutines: ~2KB stack (grows as needed), managed by Go runtime

	// Can run millions of goroutines on a few OS threads
	runtime.GOMAXPROCS(runtime.NumCPU())  // use all CPU cores

	var wg sync.WaitGroup
	for i := 0; i < 1_000_000; i++ {  // 1 million goroutines!
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			// Each goroutine does minimal work
		}(i)
	}
	wg.Wait()
	fmt.Println("Done")
}

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

// Unbuffered: sender blocks until receiver ready
ch := make(chan int)
go func() { ch <- 42 }()  // blocks until receive
val := <-ch

// Buffered: sender blocks only when buffer full
buffered := make(chan int, 5)
buffered <- 1  // doesn't block (buffer has space)
buffered <- 2
buffered <- 3
// buffered <- ... (6th send would block)

// Use buffered for:
// - Known number of items to send
// - Decoupling sender/receiver speeds
// - Preventing goroutine leaks in producer patterns

// Use unbuffered for:
// - Guaranteed synchronization
// - Signaling (done channels)

4. How do you avoid goroutine leaks?

// LEAK: goroutine blocks forever on channel
func badWorker() {
	ch := make(chan int)
	go func() {
		// This goroutine will block forever if no one sends to ch!
		val := <-ch
		process(val)
	}()
	// ch goes out of scope, goroutine is stuck
}

// FIX: use context for cancellation
func goodWorker(ctx context.Context) {
	ch := make(chan int, 1)
	go func() {
		select {
		case val := <-ch:
			process(val)
		case <-ctx.Done():  // cancelled — exit cleanly
			return
		}
	}()
}

// Always ensure goroutines have a way to exit
// Rules:
// 1. Goroutine started → must have guaranteed exit path
// 2. Use context.Context for cancellation
// 3. Use done channels for signaling completion
// 4. goleak package in tests catches leaks

5. Explain Go interfaces

// Interface: implicit implementation (no "implements" keyword)
type Writer interface {
	Write(data []byte) (n int, err error)
}

// Any type with Write method satisfies Writer
type FileWriter struct{ path string }
func (f *FileWriter) Write(data []byte) (int, error) {
	// write to file
	return len(data), nil
}

type NetworkWriter struct{ conn net.Conn }
func (n *NetworkWriter) Write(data []byte) (int, error) {
	return n.conn.Write(data)
}

// Function works with any Writer
func sendData(w Writer, data []byte) error {
	_, err := w.Write(data)
	return err
}

// Empty interface (any type)
var anything interface{} = 42
anything = "now a string"
anything = struct{ x int }{x: 1}

// Type assertion
str, ok := anything.(string)
if ok { fmt.Println(str) }

// Type switch
switch v := anything.(type) {
case int:   fmt.Println("int:", v)
case string: fmt.Println("string:", v)
default:    fmt.Printf("unknown: %T
", v)
}

6. What are defer, panic, and recover?

// defer: runs at function exit (LIFO order)
func readFile(path string) ([]byte, error) {
	f, err := os.Open(path)
	if err != nil { return nil, err }
	defer f.Close()  // always closes, even on error/panic
	return io.ReadAll(f)
}

// panic: unrecoverable error (like exception)
// recover: catch panic in deferred function
func safeDiv(a, b int) (result int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("recovered from panic: %v", r)
		}
	}()
	return a / b, nil  // panics if b == 0
}

result, err := safeDiv(10, 0)
// err = "recovered from panic: runtime error: integer divide by zero"

// Rule: use panic only for truly unrecoverable errors
// Use errors for expected error cases

7. How does Go handle errors?

// Go errors are values, not exceptions
func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, fmt.Errorf("cannot divide by zero")
	}
	return a / b, nil
}

// Always check errors
result, err := divide(10, 0)
if err != nil {
	log.Printf("error: %v", err)
	return err
}

// Custom errors
type ValidationError struct {
	Field   string
	Message string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

// Error wrapping (Go 1.13+)
if err != nil {
	return fmt.Errorf("processing user %d: %w", userID, err)  // %w wraps
}

// Unwrap
var valErr *ValidationError
if errors.As(err, &valErr) {
	fmt.Println("Field:", valErr.Field)
}

Go interview success: understand goroutines and channels deeply (the core differentiator from other languages), know when to use buffered vs unbuffered channels, explain interface satisfaction implicitly, and demonstrate clean error handling with wrapping. Senior Go roles also test concurrency patterns, performance profiling with pprof, and production deployment.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা