Go Generics di 2026: Type Parameters, Constraints, dan Pertanyaan Interview

Kuasai Go generics untuk interview teknis dengan materi type parameters, constraints, operator tilde, dan implementasi praktis seperti generic cache.

Diagram type parameters dan constraints Go generics

Go 1.18 memperkenalkan generics ke bahasa Go pada Maret 2022, dan sejak itu fitur ini terus berkembang melalui Go 1.21, 1.22, dan seterusnya. Artikel ini membahas pertanyaan interview Go generics yang penting untuk tahun 2026, mulai dari type parameters dasar hingga pola constraint tingkat lanjut yang sering ditanyakan oleh hiring manager.

Apa Itu Go Generics?

Go generics memungkinkan penulisan fungsi dan tipe yang dapat bekerja dengan tipe data apapun sambil tetap menjaga keamanan tipe pada saat kompilasi. Berbeda dengan interface yang menggunakan type assertion saat runtime, generics menyelesaikan tipe pada saat kompilasi, memberikan performa yang lebih baik dan deteksi error yang lebih awal.

Memahami Type Parameters di Go

Type parameters merupakan fondasi dari Go generics. Type parameter adalah placeholder untuk tipe yang ditentukan saat fungsi atau tipe generik digunakan.

generic.gogo
// Basic generic function with type parameter T
func PrintSlice[T any](items []T) {
    for _, item := range items {
        fmt.Println(item)
    }
}

// Usage - type inference determines T automatically
func main() {
    PrintSlice([]int{1, 2, 3})        // T is int
    PrintSlice([]string{"a", "b"})     // T is string
}

Sintaks kurung siku [T any] mendeklarasikan type parameter T yang dibatasi oleh any. Kompiler Go menyimpulkan tipe konkret dari argumen yang diberikan, sehingga menghilangkan kebutuhan untuk spesifikasi tipe eksplisit dalam sebagian besar kasus.

Pertanyaan Interview Umum: Apa Perbedaan Antara any dan comparable?

Pewawancara sering bertanya tentang built-in constraints any dan comparable. Memahami perbedaan keduanya menunjukkan pengetahuan generics yang solid.

constraints.gogo
// any: accepts all types (alias for interface{})
func Process[T any](value T) T {
    return value
}

// comparable: only types that support == and !=
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {  // This comparison requires comparable
            return true
        }
    }
    return false
}

// This compiles
Contains([]int{1, 2, 3}, 2)

// This fails: slices are not comparable
// Contains([][]int{{1}, {2}}, []int{1})

Constraint comparable membatasi type parameters hanya pada tipe yang mendukung operator kesetaraan. Slice, map, dan fungsi dikecualikan karena Go tidak mendefinisikan kesetaraan untuk tipe-tipe tersebut pada level bahasa.

Membuat Type Constraints Kustom dengan Interface

Go menggunakan interface untuk mendefinisikan constraints kustom, memperluas apa yang dapat diterima oleh fungsi generik. Spesifikasi Go mendefinisikan bagaimana elemen tipe bekerja dalam constraint interface.

number.gogo
// Custom constraint using type union
type Number interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64
}

// Generic function constrained to numeric types
func Sum[T Number](values []T) T {
    var total T
    for _, v := range values {
        total += v  // + operator works because all Number types support it
    }
    return total
}

func main() {
    fmt.Println(Sum([]int{1, 2, 3}))         // 6
    fmt.Println(Sum([]float64{1.5, 2.5}))    // 4.0
}

Type union dengan | menentukan tipe-tipe mana yang memenuhi constraint. Pendekatan ini memberikan kontrol lebih dibandingkan any sambil menghindari type assertion saat runtime.

Pertanyaan Interview: Jelaskan Operator Tilde ~ dalam Constraints

Operator tilde ~ dalam constraints mencocokkan baik tipe maupun semua tipe yang memiliki tipe tersebut sebagai underlying type. Pertanyaan ini menguji pemahaman tentang sistem tipe Go.

underlying.gogo
// ~int matches int and any type with int as underlying type
type Signed interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

// Custom type with int as underlying type
type UserID int
type Temperature int64

func Abs[T Signed](value T) T {
    if value < 0 {
        return -value
    }
    return value
}

func main() {
    var id UserID = -42
    var temp Temperature = -10
    
    fmt.Println(Abs(id))    // 42 - works because ~int matches UserID
    fmt.Println(Abs(temp))  // 10 - works because ~int64 matches Temperature
}

Tanpa ~, constraint int hanya akan mencocokkan tipe int secara eksak, bukan tipe kustom seperti UserID. Tilde memperluas constraint untuk menyertakan tipe turunan, membuat kode generik lebih fleksibel.

Siap menguasai wawancara Go Anda?

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

Tipe Generik: Struct dan Method

Tipe generik tidak hanya terbatas pada fungsi, tetapi juga mencakup struct dan method. Pola ini sering muncul dalam implementasi struktur data.

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

// Push adds an element to the stack
func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

// Pop removes and returns the top element
func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T  // Zero value for type T
        return zero, false
    }
    index := len(s.items) - 1
    item := s.items[index]
    s.items = s.items[:index]
    return item, true
}

func main() {
    intStack := Stack[int]{}
    intStack.Push(10)
    intStack.Push(20)
    
    val, ok := intStack.Pop()  // val=20, ok=true
}

Perlu diperhatikan bahwa method pada tipe generik harus mengulangi type parameter dalam kurung siku tetapi tidak dapat memperkenalkan type parameter baru. Receiver (s *Stack[T]) mengikat method ke instansiasi spesifik dari Stack.

Pertanyaan Interview: Mengapa Method Tidak Bisa Memiliki Type Parameters Sendiri?

Go melarang type parameters tambahan pada method, keputusan desain yang mengejutkan developer yang datang dari bahasa seperti Java atau C#. Proposal Go generics menjelaskan bahwa batasan ini ada untuk menjaga sistem tipe tetap dapat dikelola.

go
// This is NOT valid Go code
type Container[T any] struct {
    value T
}

// ERROR: methods cannot have type parameters
// func (c *Container[T]) Transform[U any](fn func(T) U) U {
//     return fn(c.value)
// }

// Valid alternative: use a standalone function
func Transform[T, U any](c *Container[T], fn func(T) U) U {
    return fn(c.value)
}

Solusinya adalah menggunakan fungsi top-level dengan beberapa type parameters alih-alih method. Desain ini menjaga method dispatch tetap sederhana dan menghindari interaksi kompleks antara tipe receiver dan type parameters method.

Type Inference dan Constraint Inference

Type inference Go mengurangi verbositas saat memanggil fungsi generik. Memahami kapan argumen tipe eksplisit diperlukan membantu menulis kode yang lebih bersih.

inference.gogo
func Map[T, R any](input []T, transform func(T) R) []R {
    result := make([]R, len(input))
    for i, v := range input {
        result[i] = transform(v)
    }
    return result
}

func main() {
    numbers := []int{1, 2, 3}
    
    // Type inference: T=int, R=string inferred from arguments
    strings := Map(numbers, func(n int) string {
        return fmt.Sprintf("%d", n)
    })
    
    // Explicit types sometimes required for complex cases
    // Map[int, string](numbers, converter)
}

Type inference bekerja dari argumen fungsi ke type parameters. Ketika kompiler tidak dapat menyimpulkan tipe dari argumen saja, argumen tipe eksplisit dalam kurung siku menyelesaikan ambiguitas.

Package Constraints: cmp dan slices di Standard Library

Go 1.21 menambahkan package cmp dengan constraint Ordered dan fungsi perbandingan. Package slices mendemonstrasikan kode generik idiomatik di standard library.

stdlib.gogo
import (
    "cmp"
    "slices"
)

func main() {
    numbers := []int{3, 1, 4, 1, 5, 9}
    
    // slices.Sort uses cmp.Ordered constraint internally
    slices.Sort(numbers)  // [1, 1, 3, 4, 5, 9]
    
    // Binary search on sorted slice
    index, found := slices.BinarySearch(numbers, 4)
    
    // cmp.Compare returns -1, 0, or 1
    result := cmp.Compare(3, 5)  // -1
    
    // cmp.Or returns first non-zero value
    value := cmp.Or(0, 0, 42, 100)  // 42
}

Dokumentasi standard library menunjukkan bagaimana package-package ini memanfaatkan generics untuk operasi yang type-safe pada tipe terurut. Familiaritas dengan package-package ini mendemonstrasikan pengetahuan generics praktis di luar pemahaman teoritis.

Pertanyaan Interview: Implementasi Generic Cache

Latihan interview yang umum meminta kandidat untuk mengimplementasikan cache generik yang thread-safe. Ini menguji pengetahuan generics, konkurensi dengan package sync, dan desain API.

cache.gogo
import (
    "sync"
    "time"
)

type Cache[K comparable, V any] struct {
    mu    sync.RWMutex
    items map[K]cacheItem[V]
}

type cacheItem[V any] struct {
    value      V
    expiration time.Time
}

func NewCache[K comparable, V any]() *Cache[K, V] {
    return &Cache[K, V]{
        items: make(map[K]cacheItem[V]),
    }
}

func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[key] = cacheItem[V]{
        value:      value,
        expiration: time.Now().Add(ttl),
    }
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    
    item, exists := c.items[key]
    if !exists || time.Now().After(item.expiration) {
        var zero V
        return zero, false
    }
    return item.value, true
}

Implementasi ini menggunakan comparable untuk key (persyaratan map) dan any untuk value. Struct cacheItem yang terpisah menunjukkan bagaimana tipe generik dapat bersarang satu sama lain. Thread safety berasal dari sync.RWMutex, pola yang dibahas dalam interview konkurensi Go.

Zero Values dan Type Constraints

Penanganan zero values dalam kode generik memerlukan pemahaman tentang bagaimana Go menginisialisasi variabel dari tipe berparameter.

zero.gogo
// Return zero value when slice is empty
func First[T any](slice []T) T {
    if len(slice) == 0 {
        var zero T  // Zero value: 0 for int, "" for string, nil for pointers
        return zero
    }
    return slice[0]
}

// Alternative: return pointer to avoid ambiguity
func FirstOrNil[T any](slice []T) *T {
    if len(slice) == 0 {
        return nil
    }
    return &slice[0]
}

Pola var zero T membuat zero value untuk tipe T apapun. Untuk tipe di mana zero adalah nilai yang valid (seperti 0 untuk integer), varian yang mengembalikan pointer membedakan antara "tidak ditemukan" dan "ditemukan zero."

Lanjutan: Menggabungkan Beberapa Constraints

Fungsi generik yang kompleks mungkin memerlukan tipe untuk memenuhi beberapa constraints. Go menangani ini melalui interface embedding.

combined.gogo
// Constraint requiring both ordering and string conversion
type Stringable interface {
    String() string
}

type OrderedStringable interface {
    cmp.Ordered
    Stringable
}

// Alternative: use type parameters with multiple constraints inline
func PrintSorted[T interface{ cmp.Ordered; fmt.Stringer }](items []T) {
    slices.Sort(items)
    for _, item := range items {
        fmt.Println(item.String())
    }
}

Interface embedding menggabungkan constraints, mengharuskan tipe untuk mengimplementasikan semua interface yang di-embed. Sintaks inline interface{ A; B } menyediakan fungsionalitas yang sama tanpa mendeklarasikan tipe constraint bernama.

Kesimpulan

  • Type parameters dengan [T any] memungkinkan penulisan kode yang dapat digunakan ulang dan type-safe tanpa refleksi runtime
  • comparable membatasi pada tipe yang mendukung == dan !=, diperlukan untuk map keys
  • Operator tilde ~ mencocokkan tipe dengan underlying type tertentu, memperluas fleksibilitas constraint
  • Constraints kustom menggunakan type union (int | string) untuk menentukan tipe yang diizinkan secara eksak
  • Method tidak dapat memiliki type parameters sendiri; gunakan fungsi top-level sebagai alternatif
  • Package standard library cmp dan slices mendemonstrasikan pola generik idiomatik
  • Zero values melalui var zero T menangani data kosong atau hilang dalam fungsi generik
  • Struktur data generik yang thread-safe menggabungkan generics dengan primitif sync

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Bagikan

Artikel terkait