Go Generics ปี 2026: Type Parameters, Constraints และคำถามสัมภาษณ์

เตรียมความพร้อม Go generics สำหรับการสัมภาษณ์เทคนิค ครอบคลุม type parameters, constraints, ตัวดำเนินการ tilde และการใช้งานจริงเช่น generic cache

แผนภาพ type parameters และ constraints ของ Go generics

Go 1.18 ได้นำ generics เข้ามาในภาษา Go ในเดือนมีนาคม 2022 และตั้งแต่นั้นมา ฟีเจอร์นี้ได้พัฒนาผ่าน Go 1.21, 1.22 และเวอร์ชันต่อๆ ไป บทความนี้ครอบคลุมคำถามสัมภาษณ์ Go generics ที่สำคัญสำหรับปี 2026 ตั้งแต่ type parameters พื้นฐานไปจนถึง pattern constraint ขั้นสูงที่ hiring manager มักถามบ่อย

Go Generics คืออะไร?

Go generics ช่วยให้สามารถเขียนฟังก์ชันและ type ที่ทำงานกับ data type ใดก็ได้ ในขณะที่ยังคงรักษา type safety ในเวลา compile ต่างจาก interface ที่ใช้ type assertion ในเวลา runtime, generics จะแก้ไข type ในเวลา compile ให้ประสิทธิภาพที่ดีกว่าและตรวจจับ error ได้เร็วกว่า

ทำความเข้าใจ Type Parameters ใน Go

Type parameters เป็นรากฐานของ Go generics โดย type parameter คือ placeholder สำหรับ type ที่จะถูกระบุเมื่อใช้งานฟังก์ชันหรือ type ที่เป็น generic

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
}

ไวยากรณ์วงเล็บเหลี่ยม [T any] ประกาศ type parameter T ที่ถูกจำกัดด้วย any คอมไพเลอร์ของ Go จะอนุมาน type ที่เป็นรูปธรรมจาก argument โดยไม่จำเป็นต้องระบุ type อย่างชัดเจนในกรณีส่วนใหญ่

คำถามสัมภาษณ์ที่พบบ่อย: ความแตกต่างระหว่าง any และ comparable คืออะไร?

ผู้สัมภาษณ์มักถามเกี่ยวกับ built-in constraints any และ comparable การเข้าใจความแตกต่างแสดงให้เห็นถึงความรู้ generics ที่มั่นคง

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 จำกัด type parameters เฉพาะ type ที่รองรับตัวดำเนินการเท่ากับ Slice, map และ function ถูกยกเว้นเพราะ Go ไม่ได้กำหนด equality สำหรับพวกมันในระดับภาษา

การสร้าง Type Constraints แบบกำหนดเองด้วย Interface

Go ใช้ interface เพื่อกำหนด constraints แบบกำหนดเอง ขยายสิ่งที่ฟังก์ชัน generic สามารถรับได้ ข้อกำหนดของ Go กำหนดวิธีที่ type element ทำงานภายใน 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 ด้วย | ระบุว่า type ใดบ้างที่ตรงตาม constraint วิธีนี้ให้การควบคุมมากกว่า any ในขณะที่หลีกเลี่ยง type assertion ในเวลา runtime

คำถามสัมภาษณ์: อธิบายตัวดำเนินการ Tilde ~ ใน Constraints

ตัวดำเนินการ tilde ~ ใน constraints จับคู่ทั้ง type และ type ทั้งหมดที่มี type นั้นเป็น underlying type คำถามนี้ทดสอบความเข้าใจเกี่ยวกับระบบ type ของ 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
}

หากไม่มี ~, constraint int จะจับคู่เฉพาะ type int เท่านั้น ไม่ใช่ type แบบกำหนดเองเช่น UserID Tilde ขยาย constraint เพื่อรวม type ที่สืบทอดมา ทำให้โค้ด generic มีความยืดหยุ่นมากขึ้น

พร้อมที่จะพิชิตการสัมภาษณ์ Go แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Generic Types: Struct และ Method

Generic types ขยายออกไปนอกเหนือจากฟังก์ชันไปยัง struct และ method pattern นี้ปรากฏบ่อยในการใช้งานโครงสร้างข้อมูล

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
}

โปรดทราบว่า method บน generic type ต้องทำซ้ำ type parameter ในวงเล็บเหลี่ยม แต่ไม่สามารถแนะนำ type parameters ใหม่ได้ Receiver (s *Stack[T]) ผูก method กับ instantiation เฉพาะของ Stack

คำถามสัมภาษณ์: ทำไม Method ไม่สามารถมี Type Parameters ของตัวเองได้?

Go ห้าม type parameters เพิ่มเติมบน method ซึ่งเป็นการตัดสินใจออกแบบที่สร้างความประหลาดใจให้กับนักพัฒนาที่มาจากภาษาเช่น Java หรือ C# ข้อเสนอ Go generics อธิบายว่าข้อจำกัดนี้มีอยู่เพื่อให้ระบบ type สามารถจัดการได้

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

วิธีแก้ไขคือใช้ฟังก์ชันระดับบนสุดที่มีหลาย type parameters แทน method การออกแบบนี้ทำให้ method dispatch เรียบง่ายและหลีกเลี่ยงการโต้ตอบที่ซับซ้อนระหว่าง receiver type และ type parameters ของ method

Type Inference และ Constraint Inference

Type inference ของ Go ลดความยืดยาวเมื่อเรียกใช้ฟังก์ชัน generic การเข้าใจว่าเมื่อใดจำเป็นต้องใช้ type argument อย่างชัดเจนช่วยเขียนโค้ดที่สะอาดขึ้น

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 ทำงานจาก argument ของฟังก์ชันไปยัง type parameters เมื่อคอมไพเลอร์ไม่สามารถอนุมาน type จาก argument เพียงอย่างเดียว type argument ที่ชัดเจนในวงเล็บเหลี่ยมจะแก้ไขความคลุมเครือ

Constraints Package: cmp และ slices ใน Standard Library

Go 1.21 เพิ่ม package cmp พร้อม constraint Ordered และฟังก์ชันเปรียบเทียบ Package slices แสดงโค้ด generic แบบ idiomatic ใน 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
}

เอกสาร standard library แสดงให้เห็นว่า package เหล่านี้ใช้ประโยชน์จาก generics สำหรับการดำเนินการที่ type-safe บน type ที่เรียงลำดับได้ ความคุ้นเคยกับ package เหล่านี้แสดงให้เห็นถึงความรู้ generics เชิงปฏิบัตินอกเหนือจากความเข้าใจเชิงทฤษฎี

คำถามสัมภาษณ์: การใช้งาน Generic Cache

แบบฝึกหัดสัมภาษณ์ที่พบบ่อยขอให้ผู้สมัครใช้งาน cache แบบ generic ที่ thread-safe สิ่งนี้ทดสอบความรู้ generics, concurrency ด้วย package sync และการออกแบบ 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
}

การใช้งานนี้ใช้ comparable สำหรับ key (ข้อกำหนดของ map) และ any สำหรับ value Struct cacheItem แยกต่างหากแสดงให้เห็นว่า generic type สามารถซ้อนกันได้อย่างไร Thread safety มาจาก sync.RWMutex ซึ่งเป็น pattern ที่กล่าวถึงใน การสัมภาษณ์ concurrency ของ Go

Zero Values และ Type Constraints

การจัดการ zero values ในโค้ด generic ต้องการความเข้าใจว่า Go เริ่มต้นตัวแปรของ parameterized type อย่างไร

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

Pattern var zero T สร้าง zero value สำหรับ type T ใดก็ได้ สำหรับ type ที่ zero เป็นค่าที่ถูกต้อง (เช่น 0 สำหรับ integer) รูปแบบที่คืน pointer จะแยกแยะระหว่าง "ไม่พบ" และ "พบ zero"

ขั้นสูง: การรวม Constraints หลายตัว

ฟังก์ชัน generic ที่ซับซ้อนอาจต้องการให้ type ตรงตาม constraints หลายตัว Go จัดการสิ่งนี้ผ่าน 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 รวม constraints เข้าด้วยกัน ต้องการให้ type ใช้งานทุก interface ที่ถูก embed ไวยากรณ์ inline interface{ A; B } ให้ฟังก์ชันเดียวกันโดยไม่ต้องประกาศ constraint type ที่มีชื่อ

สรุป

  • Type parameters ด้วย [T any] ช่วยให้เขียนโค้ดที่ใช้ซ้ำได้และ type-safe โดยไม่ต้องใช้ runtime reflection
  • comparable จำกัดเฉพาะ type ที่รองรับ == และ != ซึ่งจำเป็นสำหรับ map keys
  • ตัวดำเนินการ tilde ~ จับคู่ type ที่มี underlying type เฉพาะ ขยายความยืดหยุ่นของ constraint
  • Constraints แบบกำหนดเองใช้ type union (int | string) เพื่อระบุ type ที่อนุญาตอย่างชัดเจน
  • Method ไม่สามารถมี type parameters ของตัวเองได้ ใช้ฟังก์ชันระดับบนสุดเป็นทางเลือก
  • Package standard library cmp และ slices แสดง pattern generic แบบ idiomatic
  • Zero values ผ่าน var zero T จัดการข้อมูลว่างหรือหายไปในฟังก์ชัน generic
  • โครงสร้างข้อมูล generic ที่ thread-safe รวม generics กับ sync primitives

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

แชร์

บทความที่เกี่ยวข้อง

Go Error Handling Patterns

Go Error Handling ในปี 2026: รูปแบบการจัดการ Error, Wrapping และแนวปฏิบัติที่ดีที่สุดสำหรับการสัมภาษณ์งาน

เจาะลึกรูปแบบการจัดการ error ใน Go ปี 2026 ครอบคลุม error interface, custom error types, error wrapping ด้วย fmt.Errorf, sentinel errors, domain errors และคำถามสัมภาษณ์งานที่พบบ่อยสำหรับนักพัฒนา Go

ภาพประกอบ microservices gRPC ของ Go แสดงการสื่อสารระหว่างเซิร์ฟเวอร์ประสิทธิภาพสูงด้วย protocol buffers

Go และ gRPC ในปี 2026: Microservices ประสิทธิภาพสูงและคำถามสัมภาษณ์

เจาะลึก gRPC กับ Go ในปี 2026 ครอบคลุม Protocol Buffers, RPC แบบ unary และ streaming, interceptor, รูปแบบระดับโปรดักชัน และคำถามสัมภาษณ์ที่พบบ่อยสำหรับ backend engineer

ภาพประกอบดีไซน์แพตเทิร์นของ Go ด้วยรูปทรงเรขาคณิตนามธรรมที่สื่อถึงสถาปัตยกรรมซอฟต์แวร์

ดีไซน์แพตเทิร์นใน Go: แพตเทิร์นสำคัญและคำถามสัมภาษณ์สำหรับนักพัฒนา Go

เชี่ยวชาญดีไซน์แพตเทิร์นของ Go ทั้ง Functional Options, Strategy, Factory และ Observer พร้อมตัวอย่างโค้ดใช้งานจริง แนวปฏิบัติที่ดีแบบ idiomatic และคำถามสัมภาษณ์ที่พบบ่อยสำหรับนักพัฒนา Go