Top 25 Go-sollicitatievragen: complete gids voor ontwikkelaars

Beheers Go-sollicitatiegesprekken met de 25 meest gestelde vragen. Goroutines, channels, interfaces en concurrency-patronen met codevoorbeelden.

Go-sollicitatievragen - Volledige voorbereidingsgids

Technische Go-sollicitatiegesprekken toetsen het begrip van de kernconcepten van de taal: concurrency, geheugenbeheer en idiomatische patronen. Deze gids behandelt de 25 meest gestelde vragen met uitgebreide antwoorden en codevoorbeelden.

Tip voor het gesprek

Go zet eenvoud en leesbaarheid centraal. Interviewers verkiezen beknopte antwoorden die diep begrip tonen boven onnodig complexe oplossingen.

Fundamenten van de taal Go

1. Wat is het verschil tussen var en :=?

De var-declaratie laat toe om het type expliciet op te geven en werkt op packageniveau. De operator := leidt het type automatisch af, maar kan alleen binnen functies worden gebruikt.

declaration.gogo
package main

// Package level - var required
var globalConfig = "production"

func main() {
    // var with explicit type
    var count int = 10

    // var with type inference
    var name = "Alice"

    // Short declaration - functions only
    age := 25

    // Multiple declarations
    var (
        host = "localhost"
        port = 8080
    )
}

De korte declaratie := heeft binnen functies de voorkeur omwille van de bondigheid, terwijl var nodig blijft voor variabelen op packageniveau.

2. Hoe werkt het typesysteem van Go?

Go hanteert statische typing met type-inferentie. De taal onderscheidt waardetypes (gekopieerd bij toewijzing) van referentietypes (die de onderliggende structuur delen).

types.gogo
package main

import "fmt"

func main() {
    // Value types - full copy
    a := [3]int{1, 2, 3}
    b := a          // Copies the array
    b[0] = 100      // Doesn't modify a
    fmt.Println(a)  // [1 2 3]

    // Reference types - share data
    slice1 := []int{1, 2, 3}
    slice2 := slice1    // Same underlying array
    slice2[0] = 100     // Also modifies slice1
    fmt.Println(slice1) // [100 2 3]

    // Maps are also references
    m1 := map[string]int{"a": 1}
    m2 := m1
    m2["a"] = 100
    fmt.Println(m1["a"]) // 100
}

Arrays zijn waardetypes, terwijl slices, maps en channels referentietypes zijn.

3. Leg het verschil tussen arrays en slices uit

Arrays hebben een vaste, op compileertijd bepaalde grootte. Slices zijn dynamische views op een onderliggende array met drie componenten: pointer, lengte en capaciteit.

arrays_slices.gogo
package main

import "fmt"

func main() {
    // Array - fixed size, value type
    arr := [5]int{1, 2, 3, 4, 5}

    // Slice - view over the array
    slice := arr[1:4]  // [2 3 4]
    fmt.Printf("len=%d, cap=%d\n", len(slice), cap(slice))
    // len=3, cap=4

    // Modifications affect original array
    slice[0] = 20
    fmt.Println(arr) // [1 20 3 4 5]

    // Direct creation with make
    dynamic := make([]int, 3, 10)
    // len=3, cap=10

    // Append may reallocate
    dynamic = append(dynamic, 1, 2, 3, 4, 5)
}

Slices zijn in Go het voorkeurstype voor dynamische collecties.

4. Hoe werkt de defer-instructie?

defer plant een functie-aanroep om aan het einde van de omsluitende functie uit te voeren. Uitgestelde aanroepen worden gestapeld en in LIFO-volgorde uitgevoerd (Last In, First Out).

defer.gogo
package main

import (
    "fmt"
    "os"
)

func main() {
    // LIFO order
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    // Prints: 3, 2, 1
}

// Typical use case: resource cleanup
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close() // Always executes

    // Read file...
    return os.ReadFile(path)
}

// Caution: arguments are evaluated immediately
func deferArgs() {
    x := 10
    defer fmt.Println(x) // Captures 10
    x = 20
    // Prints: 10
}

defer garandeert uitvoering, ook bij een panic, en is daarom ideaal voor het opruimen van resources.

5. Wat is een interface in Go?

Een interface definieert een verzameling methodes. Elk type dat die methodes implementeert voldoet impliciet aan de interface, zonder expliciete declaratie.

interfaces.gogo
package main

import "fmt"

// Interface definition
type Writer interface {
    Write([]byte) (int, error)
}

// Type that implicitly implements Writer
type FileLogger struct {
    path string
}

func (f *FileLogger) Write(data []byte) (int, error) {
    // Write to file
    fmt.Println("Writing to", f.path)
    return len(data), nil
}

// Empty interface - accepts any type
func printAny(v interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

// Type assertion
func process(w Writer) {
    // Type check
    if fl, ok := w.(*FileLogger); ok {
        fmt.Println("FileLogger with path:", fl.path)
    }
}

Impliciete interface-implementatie maakt sterke ontkoppeling tussen packages mogelijk.

Concurrency en goroutines

6. Wat is een goroutine en hoe verschilt die van een thread?

Een goroutine is een lichte thread die door de Go-runtime wordt beheerd. Hij gebruikt enkele KB stack (tegenover meerdere MB voor een systeemthread) en de Go-scheduler multiplext duizenden goroutines op een handvol systeemthreads.

goroutines.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup

    // Launch 1000 goroutines
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Goroutine %d finished\n", id)
        }(i) // Pass i by value
    }

    wg.Wait()
    fmt.Println("All goroutines completed")
}
Veelvoorkomende valkuil

Geef lusvariabelen altijd op waarde door aan goroutines. Anders kunnen alle goroutines dezelfde eindwaarde vasthouden.

7. Leg uit hoe channels werken

Channels zorgen voor communicatie en synchronisatie tussen goroutines. Ze kunnen gebufferd zijn (met capaciteit) of ongebufferd (synchroon).

channels.gogo
package main

import "fmt"

func main() {
    // Unbuffered channel - blocks until received
    ch := make(chan int)

    go func() {
        ch <- 42 // Blocks until read
    }()

    value := <-ch // Receives value
    fmt.Println(value)

    // Buffered channel - doesn't block until full
    buffered := make(chan string, 2)
    buffered <- "first"
    buffered <- "second"
    // buffered <- "third" // Would block

    fmt.Println(<-buffered) // "first"
    fmt.Println(<-buffered) // "second"
}

Ongebufferde channels garanderen synchronisatie, terwijl gebufferde channels een tijdelijke ontkoppeling toelaten.

8. Hoe gebruik je select met meerdere channels?

select wacht gelijktijdig op meerdere channelbewerkingen. De eerste klaar staande bewerking wordt uitgevoerd, met willekeurige keuze bij gelijkspel.

select.gogo
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // Wait with timeout
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Timeout")
        }
    }

    // Non-blocking select with default
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("No message available")
    }
}

select is het belangrijkste hulpmiddel om concurrency in Go elegant te beheren.

9. Hoe voorkom je race conditions?

Race conditions ontstaan wanneer meerdere goroutines zonder synchronisatie gedeelde data benaderen. Go biedt verschillende beschermingsmechanismen.

race_conditions.gogo
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// Solution 1: Mutex
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

// Solution 2: RWMutex for read-heavy workloads
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // Multiple readers allowed
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // Single writer
    defer c.mu.Unlock()
    c.data[key] = value
}

// Solution 3: atomic for simple counters
var atomicCounter int64

func incrementAtomic() {
    atomic.AddInt64(&atomicCounter, 1)
}

func main() {
    // Detection: go run -race main.go
    counter := SafeCounter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("Count:", counter.count)
}

De compilerflag -race detecteert race conditions tijdens de uitvoering.

10. Leg het worker pool-patroon uit

Het worker pool-patroon beperkt concurrency door een vast aantal goroutines taken te laten verwerken uit een wachtrij.

worker_pool.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(100 * time.Millisecond) // Simulate work
        results <- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    var wg sync.WaitGroup

    // Start workers
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Send jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    // Wait and close results
    go func() {
        wg.Wait()
        close(results)
    }()

    // Collect results
    for result := range results {
        fmt.Println("Result:", result)
    }
}

Dit patroon vermijdt geheugen- en CPU-overhead door te veel goroutines te creëren.

Klaar om je Go gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Foutafhandeling en panic/recover

11. Hoe handel je fouten af in Go?

Go gebruikt expliciete returnwaarden voor fouten, zonder excepties. Per conventie is error de laatste teruggegeven parameter.

errors.gogo
package main

import (
    "errors"
    "fmt"
)

// Sentinel errors for comparison
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("access unauthorized")
)

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

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

func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field:   "age",
            Message: "must be positive",
        }
    }
    return nil
}

func main() {
    // Basic check
    if err := validateAge(-5); err != nil {
        // Type assertion for custom error
        var valErr *ValidationError
        if errors.As(err, &valErr) {
            fmt.Printf("Field: %s\n", valErr.Field)
        }
    }

    // Sentinel error comparison
    err := findUser("unknown")
    if errors.Is(err, ErrNotFound) {
        fmt.Println("User not found")
    }
}

func findUser(id string) error {
    // Error wrapping with context
    return fmt.Errorf("findUser %s: %w", id, ErrNotFound)
}

Wrappen met %w ketent de fouten en behoudt tegelijk de mogelijkheid om de originele fout te testen.

12. Wanneer gebruik je panic en recover?

panic onderbreekt de normale uitvoering en doorloopt de stack. recover vangt de panic op in een defer en laat de uitvoering hervatten.

panic_recover.gogo
package main

import "fmt"

func safeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()

    riskyOperation()
    return nil
}

func riskyOperation() {
    // Simulates an operation that can panic
    panic("something went wrong")
}

// Legitimate use case: initialization validation
func MustCompileRegex(pattern string) *Regexp {
    r, err := regexp.Compile(pattern)
    if err != nil {
        panic(err) // Programming error
    }
    return r
}

func main() {
    err := safeOperation()
    if err != nil {
        fmt.Println("Recovered error:", err)
    }
    fmt.Println("Program continues")
}
Gouden regel

Gebruik panic alleen voor programmeerfouten (geschonden invarianten). Voor verwachte fouten (ontbrekend bestand, netwerkproblemen) is het beter altijd een error te retourneren.

Structs, methodes en embedding

13. Wat is het verschil tussen waarde- en pointer-receivers?

Een waarde-receiver krijgt een kopie van de struct, terwijl een pointer-receiver een referentie krijgt en het origineel kan wijzigen.

receivers.gogo
package main

import "fmt"

type Counter struct {
    value int
}

// Value receiver - works on copy
func (c Counter) GetValue() int {
    return c.value
}

// Pointer receiver - modifies original
func (c *Counter) Increment() {
    c.value++
}

// Pointer receiver for large structs (avoids copy)
type LargeStruct struct {
    data [1000]int
}

func (l *LargeStruct) Process() {
    // Avoids copying 8000 bytes
}

func main() {
    c := Counter{value: 0}
    c.Increment() // Go automatically converts
    fmt.Println(c.GetValue()) // 1

    // Careful with interfaces
    var _ fmt.Stringer = &c // OK if method on *Counter
}

Regel: als één methode een pointer-receiver gebruikt, gebruiken alle methodes van het type pointer-receivers omwille van consistentie.

14. Hoe werkt embedding in Go?

Embedding neemt een type op binnen een ander type en erft de methodes en velden ervan. Dit is geen klassieke overerving maar compositie.

embedding.gogo
package main

import "fmt"

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

// Embedding Logger
type Service struct {
    *Logger // Pointer embedding
    name    string
}

func NewService(name string) *Service {
    return &Service{
        Logger: &Logger{prefix: name},
        name:   name,
    }
}

func main() {
    svc := NewService("API")

    // Promoted method - direct access
    svc.Log("Starting")

    // Explicit access also works
    svc.Logger.Log("Explicit")

    // Promoted field
    fmt.Println(svc.prefix) // "API"
}

Embedding maakt flexibele composities mogelijk en vermijdt de starheid van overerving.

15. Hoe implementeer je het singleton-patroon in Go?

Het package sync biedt sync.Once om eenmalige uitvoering van de initialisatie te garanderen, ook bij gelijktijdige goroutines.

singleton.gogo
package main

import (
    "fmt"
    "sync"
)

type Database struct {
    connectionString string
}

var (
    instance *Database
    once     sync.Once
)

func GetDatabase() *Database {
    once.Do(func() {
        fmt.Println("Single initialization")
        instance = &Database{
            connectionString: "postgres://...",
        }
    })
    return instance
}

func main() {
    // Concurrent calls - single initialization
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            db := GetDatabase()
            fmt.Printf("Instance: %p\n", db)
        }()
    }
    wg.Wait()
}

sync.Once is thread-safe en eleganter dan een mutex met double-check locking.

Context en cancellation

16. Waarvoor dient het context-package?

Het package context beheert deadlines, annulatiesignalen en aan een request gebonden waarden door de hele aanroepboom.

context.gogo
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Context with timeout
    ctx, cancel := context.WithTimeout(
        context.Background(),
        2*time.Second,
    )
    defer cancel() // Always call cancel

    result := make(chan string, 1)

    go func() {
        // Simulate long operation
        time.Sleep(3 * time.Second)
        result <- "completed"
    }()

    select {
    case res := <-result:
        fmt.Println(res)
    case <-ctx.Done():
        fmt.Println("Timeout:", ctx.Err())
    }
}

// Propagation through functions
func fetchData(ctx context.Context, url string) ([]byte, error) {
    // Early check
    if ctx.Err() != nil {
        return nil, ctx.Err()
    }

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    // HTTP client respects context
    resp, err := http.DefaultClient.Do(req)
    // ...
}

Elke potentieel langlopende functie zou een context.Context als eerste parameter moeten accepteren.

17. Hoe handel je een graceful shutdown van het programma af?

Systeemsignalen zoals SIGINT en SIGTERM kunnen worden opgevangen om netjes af te sluiten.

graceful_shutdown.gogo
package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // Context cancelled on signal
    ctx, stop := signal.NotifyContext(
        context.Background(),
        syscall.SIGINT,
        syscall.SIGTERM,
    )
    defer stop()

    // Start server
    server := startServer()

    // Wait for signal
    <-ctx.Done()
    fmt.Println("\nShutting down...")

    // Timeout for graceful shutdown
    shutdownCtx, cancel := context.WithTimeout(
        context.Background(),
        5*time.Second,
    )
    defer cancel()

    if err := server.Shutdown(shutdownCtx); err != nil {
        fmt.Println("Shutdown error:", err)
    }

    fmt.Println("Shutdown complete")
}

Dit patroon zorgt ervoor dat actieve verbindingen netjes worden afgerond voor het afsluiten.

Tests en benchmarks

18. Hoe schrijf je tests in Go?

Het ingebouwde package testing biedt de basisfunctionaliteit. Tests staan in *_test.go-bestanden.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

// Table-driven tests
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -1, -2},
        {"mixed", -1, 5, 4},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Table-driven tests zijn in Go het idiomatische patroon om meerdere gevallen te toetsen.

19. Hoe schrijf je benchmarks?

Benchmarks gebruiken testing.B en worden uitgevoerd met go test -bench.

benchmark_test.gogo
package main

import (
    "strings"
    "testing"
)

func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var s string
        for j := 0; j < 100; j++ {
            s += "a"
        }
    }
}

func BenchmarkStringBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        for j := 0; j < 100; j++ {
            sb.WriteString("a")
        }
        _ = sb.String()
    }
}

// Typical results:
// BenchmarkStringConcat-8      50000    28000 ns/op
// BenchmarkStringBuilder-8   1000000     1200 ns/op

Benchmarks tonen prestatieverschillen tussen implementaties.

Generics (Go 1.18+)

20. Hoe gebruik je generics in Go?

Go 1.18 introduceerde typeparameters, waardoor generieke code mogelijk is met behoud van type-veiligheid.

generics.gogo
package main

import "fmt"

// Generic function
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Custom type constraint
type Number interface {
    int | int64 | float64
}

func Sum[T Number](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

// Generic type
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func main() {
    // Usage
    doubled := Map([]int{1, 2, 3}, func(n int) int {
        return n * 2
    })
    fmt.Println(doubled) // [2 4 6]

    fmt.Println(Sum([]int{1, 2, 3, 4, 5})) // 15

    stack := &Stack[string]{}
    stack.Push("hello")
    stack.Push("world")
    val, _ := stack.Pop()
    fmt.Println(val) // "world"
}

Generics maken het overbodig om code te dupliceren of interface{} te gebruiken.

Modules en afhankelijkheden

21. Hoe werkt het modulesysteem van Go?

Go-modules beheren afhankelijkheden met semantische versionering. Het bestand go.mod definieert de module en de afhankelijkheden ervan.

go.mod examplego
module github.com/user/myproject

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/lib/pq v1.10.9
)

// Essential commands:
// go mod init github.com/user/project
// go mod tidy        - clean dependencies
// go get package@v1.2.3 - add/update
// go mod vendor      - copy locally
bash
# Updating dependencies
go get -u ./...           # All dependencies
go get -u=patch ./...     # Patches only

Het bestand go.sum bevat cryptografische checksums om de integriteit van de afhankelijkheden te garanderen.

22. Hoe structureer je een Go-project?

De standaardstructuur volgt conventies van de community zonder strikte regels op te leggen.

text
myproject/
├── cmd/
│   └── api/
│       └── main.go      # Entry point
├── internal/            # Private to module
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                 # Reusable external code
├── go.mod
├── go.sum
└── README.md

De map internal is bijzonder: de inhoud kan niet door andere modules worden geïmporteerd.

Gevorderde vragen

23. Hoe werkt de garbage collector in Go?

Go gebruikt een gelijktijdige tricolor mark-and-sweep garbage collector, geoptimaliseerd voor lage latentie.

gc_optimization.gogo
package main

import "runtime"

func main() {
    // GC configuration
    // GOGC=100 (default) - triggers GC when heap doubles

    // Force GC
    runtime.GC()

    // Memory statistics
    var stats runtime.MemStats
    runtime.ReadMemStats(&stats)

    println("Alloc:", stats.Alloc)
    println("NumGC:", stats.NumGC)
    println("PauseTotalNs:", stats.PauseTotalNs)
}

// Optimization techniques
// 1. Reuse allocations with sync.Pool
// 2. Pre-allocate slices with make([]T, 0, cap)
// 3. Avoid repeated string/[]byte conversions
// 4. Use pointers for large structs

De omgevingsvariabele GODEBUG=gctrace=1 toont GC-traces.

24. Leg de Go-scheduler uit

De Go-scheduler gebruikt een M:N-model dat N goroutines mapt op M systeemthreads, met drie entiteiten: G (goroutine), M (thread) en P (logische processor).

scheduler.gogo
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // Number of logical processors (P)
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))

    // Number of active goroutines
    fmt.Println("NumGoroutine:", runtime.NumGoroutine())

    // Yield processor to other goroutines
    runtime.Gosched()

    // M:P:G model
    // - G: goroutine (lightweight stack ~2KB)
    // - M: OS thread (machine)
    // - P: logical processor (execution context)
    //
    // Each P has a local queue of Gs
    // Work stealing when queue is empty
}

De scheduler is sinds Go 1.14 preëmptief en voorkomt dat één goroutine een P monopoliseert.

25. Hoe optimaliseer je prestaties in Go?

Optimaliseren begint met profiling om bottlenecks te vinden.

profiling.gogo
package main

import (
    "os"
    "runtime/pprof"
)

func main() {
    // CPU profiling
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // Code to profile...

    // Memory profiling
    mf, _ := os.Create("mem.prof")
    defer mf.Close()
    pprof.WriteHeapProfile(mf)
}

// Analysis: go tool pprof cpu.prof

// Common optimization techniques:
// 1. Avoid allocations in hot loops
// 2. Use sync.Pool for reusable objects
// 3. Prefer []byte over string for mutations
// 4. Use bufio for I/O
// 5. Batch database operations
Optimalisatieregel

Eerst meten, dan optimaliseren. Profiling onthult vaak verrassende inzichten over de echte bottlenecks.

Conclusie

Deze 25 vragen behandelen de fundamentele concepten die in Go-gesprekken aan bod komen:

Voorbereidingschecklist:

  • ✅ Beheersing van goroutines en channels
  • ✅ Begrip van impliciete interfaces
  • ✅ Idiomatische foutafhandeling
  • ✅ Correct gebruik van context
  • ✅ Concurrency-patronen (mutex, worker pool)
  • ✅ Tests en benchmarks
  • ✅ Kennis van de generics in Go 1.18+

De sleutel tot succes in een Go-gesprek: aantonen dat je de afwegingen tussen eenvoud en prestaties begrijpt en weten wanneer welk concurrency-patroon past.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

#go
#golang
#interview
#concurrency
#goroutines

Delen

Gerelateerde artikelen