25 pertanyaan wawancara Go teratas: panduan lengkap developer

Kuasai wawancara Go dengan 25 pertanyaan paling sering ditanyakan. Goroutine, channel, interface, dan pola konkurensi dengan contoh kode.

Pertanyaan wawancara Go - Panduan persiapan lengkap

Wawancara teknis Go menilai pemahaman terhadap konsep inti bahasa: konkurensi, manajemen memori, dan pola idiomatis. Panduan ini merangkum 25 pertanyaan yang paling sering ditanyakan beserta jawaban rinci dan contoh kode.

Saran wawancara

Go mengedepankan kesederhanaan dan keterbacaan. Pewawancara lebih menyukai jawaban ringkas yang menunjukkan pemahaman mendalam ketimbang solusi yang terlalu rumit.

Dasar-dasar bahasa Go

1. Apa perbedaan antara var dan :=?

Deklarasi var memungkinkan penentuan tipe secara eksplisit dan dapat dipakai di tingkat package. Operator := menyimpulkan tipe secara otomatis, tetapi hanya berlaku di dalam fungsi.

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
    )
}

Deklarasi singkat := lebih disukai di dalam fungsi karena ringkas, sementara var tetap dibutuhkan untuk variabel di tingkat package.

2. Bagaimana sistem tipe Go bekerja?

Go menggunakan typing statis dengan inferensi tipe. Bahasa ini membedakan tipe nilai (disalin saat diberikan) dari tipe referensi (yang berbagi struktur dasar).

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
}

Array adalah tipe nilai, sedangkan slice, map, dan channel termasuk tipe referensi.

3. Jelaskan perbedaan antara array dan slice

Array memiliki ukuran tetap yang ditentukan saat kompilasi. Slice merupakan tampilan dinamis di atas array dasar dengan tiga komponen: pointer, panjang, dan kapasitas.

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)
}

Slice adalah tipe pilihan utama untuk koleksi dinamis di Go.

4. Bagaimana cara kerja pernyataan defer?

defer menjadwalkan pemanggilan fungsi untuk dieksekusi pada akhir fungsi pembungkusnya. Pemanggilan yang ditunda diletakkan dalam tumpukan dan dijalankan dengan urutan LIFO (terakhir masuk, pertama keluar).

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 menjamin eksekusi bahkan saat panic, sehingga ideal untuk membersihkan resource.

5. Apa itu interface di Go?

Interface mendefinisikan sekumpulan method. Tipe apa pun yang mengimplementasikan method tersebut secara otomatis memenuhi interface tanpa deklarasi eksplisit.

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)
    }
}

Implementasi interface secara implisit memungkinkan pemisahan kuat antar package.

Konkurensi dan goroutine

6. Apa itu goroutine dan apa bedanya dengan thread?

Goroutine adalah thread ringan yang dikelola oleh runtime Go. Ia menggunakan beberapa KB stack (dibanding beberapa MB pada thread sistem) dan scheduler Go memultipleksikan ribuan goroutine ke sejumlah kecil thread sistem.

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")
}
Jebakan umum

Variabel loop sebaiknya selalu diteruskan ke goroutine secara berdasar nilai. Bila tidak, semua goroutine bisa menangkap nilai akhir yang sama.

7. Jelaskan cara kerja channel

Channel memungkinkan komunikasi dan sinkronisasi antar goroutine. Channel dapat berbuffer (memiliki kapasitas) atau tanpa buffer (sinkron).

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"
}

Channel tanpa buffer menjamin sinkronisasi, sedangkan channel berbuffer memungkinkan dekopling waktu.

8. Bagaimana memakai select dengan beberapa channel?

select menunggu beberapa operasi channel secara bersamaan. Operasi pertama yang siap dieksekusi, dengan pilihan acak bila ada lebih dari satu yang siap.

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 adalah alat utama untuk mengelola konkurensi di Go dengan elegan.

9. Bagaimana mencegah race condition?

Race condition terjadi saat beberapa goroutine mengakses data bersama tanpa sinkronisasi. Go menyediakan beberapa mekanisme perlindungan.

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)
}

Flag kompilator -race mendeteksi race condition saat program berjalan.

10. Jelaskan pola worker pool

Pola worker pool membatasi konkurensi dengan membuat sejumlah goroutine tetap yang memproses tugas dari sebuah antrian.

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)
    }
}

Pola ini menghindari beban memori dan CPU akibat membuat terlalu banyak goroutine.

Siap menguasai wawancara Go Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Penanganan error dan panic/recover

11. Bagaimana menangani error di Go?

Go memakai nilai pengembalian eksplisit untuk error, tanpa exception. Sesuai konvensi, error adalah parameter terakhir yang dikembalikan.

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)
}

Pembungkusan dengan %w merangkai error tanpa kehilangan kemampuan untuk memeriksa error aslinya.

12. Kapan menggunakan panic dan recover?

panic menghentikan alur normal dan menggulung balik stack. recover menangkap panic di dalam defer sehingga eksekusi dapat dilanjutkan.

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")
}
Aturan emas

Panic sebaiknya hanya digunakan untuk error pemrograman (invariant yang dilanggar). Untuk error yang diharapkan (file hilang, masalah jaringan), lebih aman selalu mengembalikan error.

Struct, method, dan embedding

13. Apa beda receiver berdasar nilai dan berdasar pointer?

Receiver berdasar nilai menerima salinan struct, sedangkan receiver berdasar pointer menerima referensi dan dapat mengubah aslinya.

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
}

Aturan: jika satu method memakai receiver berdasar pointer, semua method pada tipe tersebut sebaiknya memakai receiver berdasar pointer demi konsistensi.

14. Bagaimana embedding bekerja di Go?

Embedding menyertakan satu tipe ke dalam tipe lain, mewarisi method dan field-nya. Ini bukan pewarisan klasik melainkan komposisi.

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 menghadirkan komposisi yang fleksibel sekaligus menghindari kekakuan pewarisan.

15. Bagaimana menerapkan pola singleton di Go?

Package sync menyediakan sync.Once untuk menjamin inisialisasi tunggal walau dengan goroutine yang berjalan paralel.

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 aman untuk konkurensi dan lebih elegan ketimbang mutex dengan double-check locking.

Context dan cancellation

16. Untuk apa package context digunakan?

Package context mengelola deadline, sinyal pembatalan, serta nilai yang terkait dengan request di sepanjang pohon panggilan.

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)
    // ...
}

Setiap fungsi yang berpotensi berjalan lama sebaiknya menerima context.Context sebagai parameter pertama.

17. Bagaimana mengelola graceful shutdown program?

Sinyal sistem seperti SIGINT dan SIGTERM dapat ditangkap untuk memungkinkan penutupan yang bersih.

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")
}

Pola ini memastikan koneksi aktif selesai dengan benar sebelum program berhenti.

Test dan benchmark

18. Bagaimana menulis test di Go?

Package bawaan testing menyediakan fungsi dasar. Test berada di file *_test.go.

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 test merupakan pola idiomatis Go untuk menguji banyak kasus.

19. Bagaimana menulis benchmark?

Benchmark memakai testing.B dan dijalankan dengan 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

Benchmark mengungkap perbedaan performa antar implementasi.

Generics (Go 1.18+)

20. Bagaimana memakai generics di Go?

Go 1.18 memperkenalkan parameter tipe yang memungkinkan kode generik dengan tetap menjaga keamanan tipe.

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 menghilangkan kebutuhan menduplikasi kode atau memakai interface{}.

Modul dan dependensi

21. Bagaimana sistem modul Go bekerja?

Modul Go mengelola dependensi dengan versioning semantik. File go.mod mendefinisikan modul dan dependensinya.

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

File go.sum berisi checksum kriptografis untuk menjaga integritas dependensi.

22. Bagaimana menyusun proyek Go?

Struktur standar mengikuti konvensi komunitas tanpa memaksakan aturan kaku.

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

Folder internal istimewa: isinya tidak dapat diimpor oleh modul lain.

Pertanyaan tingkat lanjut

23. Bagaimana cara kerja garbage collector di Go?

Go memakai garbage collector mark-and-sweep tiga warna yang konkuren dan dioptimalkan untuk latensi rendah.

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

Variabel lingkungan GODEBUG=gctrace=1 menampilkan jejak GC.

24. Jelaskan scheduler Go

Scheduler Go memakai model M:N yang memetakan N goroutine ke M thread sistem dengan tiga entitas: G (goroutine), M (thread), dan P (prosesor logis).

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
}

Scheduler bersifat preemptif sejak Go 1.14, sehingga satu goroutine tidak bisa memonopoli sebuah P.

25. Bagaimana mengoptimalkan performa di Go?

Optimasi dimulai dengan profiling untuk menemukan bottleneck.

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
Aturan optimasi

Ukur dulu sebelum mengoptimalkan. Profiling kerap memunculkan kejutan tentang bottleneck yang sebenarnya.

Kesimpulan

Kedua puluh lima pertanyaan ini mencakup konsep dasar yang dievaluasi dalam wawancara Go:

Daftar persiapan:

  • ✅ Penguasaan goroutine dan channel
  • ✅ Pemahaman interface implisit
  • ✅ Penanganan error secara idiomatis
  • ✅ Penggunaan context yang tepat
  • ✅ Pola konkurensi (mutex, worker pool)
  • ✅ Test dan benchmark
  • ✅ Pengetahuan generics di Go 1.18+

Kunci sukses dalam wawancara Go: menunjukkan pemahaman terhadap trade-off antara kesederhanaan dan performa serta tahu kapan menerapkan setiap pola konkurensi.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tag

#go
#golang
#interview
#concurrency
#goroutines

Bagikan

Artikel terkait