Golang Generics Quiz
35 comprehensive questions on Go's generics system introduced in Go 1.18, covering type parameters, generic functions, generic types, type constraints, and their benefits and limitations — with 15 code examples demonstrating practical generic programming patterns.
Question 1
What are generics in Go?
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Print(v, " ")
}
fmt.Println()
}
func main() {
PrintSlice([]int{1, 2, 3})
PrintSlice([]string{"a", "b", "c"})
}Question 2
How do you define a generic function with type parameters?
func Max[T comparable](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(10, 20)) // 20
fmt.Println(Max("abc", "def")) // def
}Question 3
What is the 'any' constraint in Go generics?
func PrintValue[T any](v T) {
fmt.Printf("Value: %v, Type: %T\n", v, v)
}
func main() {
PrintValue(42)
PrintValue("hello")
PrintValue([]int{1, 2, 3})
}Question 4
How do you define a generic type (struct)?
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 {
if len(s.items) == 0 {
var zero T
return zero
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item
}
func main() {
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
fmt.Println(intStack.Pop()) // 2
}Question 5
What is the 'comparable' constraint used for?
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item {
return true
}
}
return false
}
func IndexOf[T comparable](slice []T, item T) int {
for i, v := range slice {
if v == item {
return i
}
}
return -1
}
func main() {
nums := []int{1, 2, 3, 4, 5}
fmt.Println(Contains(nums, 3)) // true
fmt.Println(IndexOf(nums, 3)) // 2
}Question 6
How do you create custom type constraints?
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
func Sum[T Number](slice []T) T {
var sum T
for _, v := range slice {
sum += v
}
return sum
}
func main() {
ints := []int{1, 2, 3, 4, 5}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(ints)) // 15
fmt.Println(Sum(floats)) // 6.6
}Question 7
How do you implement a generic slice utility function?
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
func Map[T, U any](slice []T, transform func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = transform(v)
}
return result
}
func main() {
nums := []int{1, 2, 3, 4, 5}
// Filter even numbers
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4]
// Map to strings
strs := Map(nums, func(n int) string { return fmt.Sprintf("%d", n) })
fmt.Println(strs) // [1 2 3 4 5]
}Question 8
How do you handle multiple type parameters?
func Zip[T, U any](a []T, b []U) []Pair[T, U] {
length := len(a)
if len(b) < length {
length = len(b)
}
result := make([]Pair[T, U], length)
for i := 0; i < length; i++ {
result[i] = Pair[T, U]{First: a[i], Second: b[i]}
}
return result
}
type Pair[T, U any] struct {
First T
Second U
}
func main() {
names := []string{"Alice", "Bob", "Charlie"}
ages := []int{25, 30, 35}
pairs := Zip(names, ages)
for _, p := range pairs {
fmt.Printf("%s is %d years old\n", p.First, p.Second)
}
}Question 9
How do you implement a generic Set type?
type Set[T comparable] map[T]bool
func NewSet[T comparable]() Set[T] {
return make(Set[T])
}
func (s Set[T]) Add(item T) {
s[item] = true
}
func (s Set[T]) Contains(item T) bool {
return s[item]
}
func (s Set[T]) Remove(item T) {
delete(s, item)
}
func (s Set[T]) Size() int {
return len(s)
}
func main() {
intSet := NewSet[int]()
intSet.Add(1)
intSet.Add(2)
intSet.Add(1) // duplicate
fmt.Println(intSet.Contains(1)) // true
fmt.Println(intSet.Size()) // 2
}Question 10
How do you use generics with interfaces?
type Container[T any] interface {
Add(item T)
Get(index int) T
Size() int
}
type SliceContainer[T any] struct {
items []T
}
func (sc *SliceContainer[T]) Add(item T) {
sc.items = append(sc.items, item)
}
func (sc *SliceContainer[T]) Get(index int) T {
if index < 0 || index >= len(sc.items) {
var zero T
return zero
}
return sc.items[index]
}
func (sc *SliceContainer[T]) Size() int {
return len(sc.items)
}
func ProcessContainer[T any](c Container[T]) {
c.Add(*new(T)) // This won't work as expected
fmt.Println("Size:", c.Size())
}Question 11
How do you implement a generic sorting function?
func Sort[T any](slice []T, less func(a, b T) bool) {
for i := 0; i < len(slice); i++ {
for j := i + 1; j < len(slice); j++ {
if less(slice[j], slice[i]) {
slice[i], slice[j] = slice[j], slice[i]
}
}
}
}
func main() {
// Sort integers
nums := []int{3, 1, 4, 1, 5}
Sort(nums, func(a, b int) bool { return a < b })
fmt.Println(nums) // [1 1 3 4 5]
// Sort strings by length
words := []string{"a", "bbb", "cc"}
Sort(words, func(a, b string) bool { return len(a) < len(b) })
fmt.Println(words) // [a cc bbb]
}Question 12
How do you handle type inference with generics?
func Max[T comparable](a, b T) T {
if a > b {
return a
}
return b
}
func MakePair[T, U any](first T, second U) Pair[T, U] {
return Pair[T, U]{First: first, Second: second}
}
type Pair[T, U any] struct {
First T
Second U
}
func main() {
// Type inferred from arguments
result1 := Max(10, 20) // int
result2 := Max("abc", "def") // string
// Type inferred from assignment
var p Pair[int, string] = MakePair(42, "hello")
// Type parameters can be inferred
p2 := MakePair("world", 3.14) // Pair[string, float64]
fmt.Println(result1, result2, p, p2)
}Question 13
How do you implement a generic cache?
type Cache[K comparable, V any] struct {
data map[K]V
}
func NewCache[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{
data: make(map[K]V),
}
}
func (c *Cache[K, V]) Set(key K, value V) {
c.data[key] = value
}
func (c *Cache[K, V]) Get(key K) (V, bool) {
value, exists := c.data[key]
return value, exists
}
func (c *Cache[K, V]) Delete(key K) {
delete(c.data, key)
}
func main() {
// String to int cache
stringCache := NewCache[string, int]()
stringCache.Set("answer", 42)
if val, ok := stringCache.Get("answer"); ok {
fmt.Println("Cached value:", val)
}
// Int to string cache
intCache := NewCache[int, string]()
intCache.Set(1, "one")
if val, ok := intCache.Get(1); ok {
fmt.Println("Cached value:", val)
}
}Question 14
How do you implement method constraints?
type Stringer interface {
String() string
}
func PrintAll[T Stringer](items []T) {
for _, item := range items {
fmt.Println(item.String())
}
}
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}
func main() {
people := []Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
PrintAll(people)
}Question 15
How do you implement a generic linked list?
type Node[T any] struct {
Value T
Next *Node[T]
}
type LinkedList[T any] struct {
Head *Node[T]
Size int
}
func (ll *LinkedList[T]) Append(value T) {
newNode := &Node[T]{Value: value}
if ll.Head == nil {
ll.Head = newNode
} else {
current := ll.Head
for current.Next != nil {
current = current.Next
}
current.Next = newNode
}
ll.Size++
}
func (ll *LinkedList[T]) ToSlice() []T {
result := make([]T, 0, ll.Size)
current := ll.Head
for current != nil {
result = append(result, current.Value)
current = current.Next
}
return result
}
func main() {
list := &LinkedList[string]{}
list.Append("Hello")
list.Append("World")
fmt.Println(list.ToSlice()) // [Hello World]
}Question 16
What are the benefits of using generics over interface{}?
Question 17
How do you implement a generic binary tree?
type TreeNode[T any] struct {
Value T
Left *TreeNode[T]
Right *TreeNode[T]
}
func Insert[T any](root *TreeNode[T], value T, less func(a, b T) bool) *TreeNode[T] {
if root == nil {
return &TreeNode[T]{Value: value}
}
if less(value, root.Value) {
root.Left = Insert(root.Left, value, less)
} else {
root.Right = Insert(root.Right, value, less)
}
return root
}
func InOrder[T any](root *TreeNode[T], visit func(T)) {
if root == nil {
return
}
InOrder(root.Left, visit)
visit(root.Value)
InOrder(root.Right, visit)
}
func main() {
var root *TreeNode[int]
root = Insert(root, 5, func(a, b int) bool { return a < b })
root = Insert(root, 3, func(a, b int) bool { return a < b })
root = Insert(root, 7, func(a, b int) bool { return a < b })
InOrder(root, func(v int) { fmt.Print(v, " ") }) // 3 5 7
}Question 18
How do you handle generics with channels?
func Producer[T any](ch chan<- T, values []T) {
for _, v := range values {
ch <- v
}
close(ch)
}
func Consumer[T any](ch <-chan T, process func(T)) {
for v := range ch {
process(v)
}
}
func Pipeline[T, U any](input <-chan T, transform func(T) U) <-chan U {
output := make(chan U)
go func() {
defer close(output)
for v := range input {
output <- transform(v)
}
}()
return output
}
func main() {
input := make(chan int, 5)
go Producer(input, []int{1, 2, 3, 4, 5})
doubled := Pipeline(input, func(x int) int { return x * 2 })
Consumer(doubled, func(v int) {
fmt.Println(v)
})
}Question 19
How do you implement a generic priority queue?
type PriorityQueue[T any] struct {
items []T
less func(a, b T) bool
}
func NewPriorityQueue[T any](less func(a, b T) bool) *PriorityQueue[T] {
return &PriorityQueue[T]{
items: make([]T, 0),
less: less,
}
}
func (pq *PriorityQueue[T]) Push(item T) {
pq.items = append(pq.items, item)
// Simple bubble up (not efficient)
for i := len(pq.items) - 1; i > 0; i-- {
if pq.less(pq.items[i], pq.items[i-1]) {
pq.items[i], pq.items[i-1] = pq.items[i-1], pq.items[i]
} else {
break
}
}
}
func (pq *PriorityQueue[T]) Pop() T {
if len(pq.items) == 0 {
var zero T
return zero
}
item := pq.items[0]
pq.items = pq.items[1:]
return item
}
func main() {
pq := NewPriorityQueue(func(a, b int) bool { return a < b }) // min-heap
pq.Push(3)
pq.Push(1)
pq.Push(4)
fmt.Println(pq.Pop()) // 1
fmt.Println(pq.Pop()) // 3
}Question 20
What are the limitations of Go generics?
Question 21
How do you implement a generic graph data structure?
type Graph[T comparable] struct {
vertices map[T][]T
}
func NewGraph[T comparable]() *Graph[T] {
return &Graph[T]{
vertices: make(map[T][]T),
}
}
func (g *Graph[T]) AddVertex(vertex T) {
if _, exists := g.vertices[vertex]; !exists {
g.vertices[vertex] = []T{}
}
}
func (g *Graph[T]) AddEdge(from, to T) {
g.AddVertex(from)
g.AddVertex(to)
g.vertices[from] = append(g.vertices[from], to)
}
func (g *Graph[T]) GetNeighbors(vertex T) []T {
return g.vertices[vertex]
}
func main() {
g := NewGraph[string]()
g.AddEdge("A", "B")
g.AddEdge("A", "C")
g.AddEdge("B", "C")
fmt.Println("A neighbors:", g.GetNeighbors("A")) // [B C]
}Question 22
How do you implement a generic result type (like Rust's Result)?
type Result[T any] struct {
value T
err error
}
func Ok[T any](value T) Result[T] {
return Result[T]{value: value}
}
func Err[T any](err error) Result[T] {
return Result[T]{err: err}
}
func (r Result[T]) IsOk() bool {
return r.err == nil
}
func (r Result[T]) IsErr() bool {
return r.err != nil
}
func (r Result[T]) Unwrap() T {
if r.err != nil {
panic(r.err)
}
return r.value
}
func (r Result[T]) UnwrapOr(defaultValue T) T {
if r.err != nil {
return defaultValue
}
return r.value
}
func Divide(a, b float64) Result[float64] {
if b == 0 {
return Err[float64](errors.New("division by zero"))
}
return Ok(a / b)
}
func main() {
result1 := Divide(10, 2)
if result1.IsOk() {
fmt.Println("Result:", result1.Unwrap())
}
result2 := Divide(10, 0)
if result2.IsErr() {
fmt.Println("Error:", result2.err)
}
}Question 23
How do you implement a generic singleton pattern?
type Singleton[T any] struct {
instance *T
once sync.Once
}
func (s *Singleton[T]) GetInstance() *T {
s.once.Do(func() {
s.instance = new(T)
})
return s.instance
}
// Global instances
var (
intSingleton = &Singleton[int]{}
stringSingleton = &Singleton[string]{}
)
func main() {
// Get int singleton
intInstance1 := intSingleton.GetInstance()
intInstance2 := intSingleton.GetInstance()
fmt.Println("Same instance:", intInstance1 == intInstance2) // true
*intInstance1 = 42
fmt.Println("Value:", *intInstance2) // 42
}Question 24
How do you implement a generic observer pattern?
type Observer[T any] interface {
OnNotify(data T)
}
type Subject[T any] struct {
observers []Observer[T]
}
func (s *Subject[T]) Subscribe(observer Observer[T]) {
s.observers = append(s.observers, observer)
}
func (s *Subject[T]) Notify(data T) {
for _, observer := range s.observers {
observer.OnNotify(data)
}
}
type Printer struct{}
func (p *Printer) OnNotify(data int) {
fmt.Println("Received:", data)
}
func main() {
subject := &Subject[int]{}
printer := &Printer{}
subject.Subscribe(printer)
subject.Notify(42) // Received: 42
}Question 25
How do you implement a generic factory pattern?
type Factory[T any] func() T
func Register[T any](name string, factory Factory[T]) {
// In real implementation, use a map[string]interface{}
// and type assertions or reflection
}
func Create[T any](name string) T {
// Lookup factory and call it
var zero T
return zero // placeholder
}
// Better implementation
type FactoryMap struct {
factories map[string]interface{}
}
func (fm *FactoryMap) Register(name string, factory interface{}) {
if fm.factories == nil {
fm.factories = make(map[string]interface{})
}
fm.factories[name] = factory
}
func (fm *FactoryMap) Create(name string) interface{} {
if factory, exists := fm.factories[name]; exists {
// Use reflection to call factory
// This is simplified
return factory
}
return nil
}
func main() {
fm := &FactoryMap{}
// Register factories
fm.Register("int", func() int { return 42 })
fm.Register("string", func() string { return "hello" })
// Create instances
intFactory := fm.factories["int"].(func() int)
fmt.Println(intFactory()) // 42
}Question 26
How do you implement a generic decorator pattern?
type Handler[T, U any] func(T) U
func LoggingDecorator[T, U any](handler Handler[T, U]) Handler[T, U] {
return func(input T) U {
start := time.Now()
fmt.Printf("Calling handler with input: %v\n", input)
result := handler(input)
duration := time.Since(start)
fmt.Printf("Handler returned: %v (took %v)\n", result, duration)
return result
}
}
func CachingDecorator[T comparable, U any](handler Handler[T, U]) Handler[T, U] {
cache := make(map[T]U)
return func(input T) U {
if result, exists := cache[input]; exists {
fmt.Println("Cache hit for:", input)
return result
}
result := handler(input)
cache[input] = result
return result
}
}
func processNumber(n int) string {
time.Sleep(100 * time.Millisecond) // simulate work
return fmt.Sprintf("processed_%d", n)
}
func main() {
handler := LoggingDecorator(CachingDecorator(processNumber))
fmt.Println(handler(1))
fmt.Println(handler(1)) // cache hit
}Question 27
How do you implement a generic visitor pattern?
type Visitor[T any] interface {
Visit(T)
}
type Element[T any] interface {
Accept(Visitor[T])
}
type ConcreteElement[T any] struct {
Value T
}
func (ce *ConcreteElement[T]) Accept(visitor Visitor[T]) {
visitor.Visit(ce.Value)
}
func (ce *ConcreteElement[T]) GetValue() T {
return ce.Value
}
type PrintingVisitor[T any] struct{}
func (pv *PrintingVisitor[T]) Visit(value T) {
fmt.Printf("Visiting: %v\n", value)
}
func main() {
element := &ConcreteElement[int]{Value: 42}
visitor := &PrintingVisitor[int]{}
element.Accept(visitor) // Visiting: 42
}Question 28
How do you implement a generic state machine?
type State[T any] func(T) State[T]
type StateMachine[T any] struct {
currentState State[T]
context T
}
func NewStateMachine[T any](initialState State[T], context T) *StateMachine[T] {
return &StateMachine[T]{
currentState: initialState,
context: context,
}
}
func (sm *StateMachine[T]) Process() {
for sm.currentState != nil {
sm.currentState = sm.currentState(sm.context)
}
}
func (sm *StateMachine[T]) SendEvent(event T) {
if sm.currentState != nil {
sm.currentState = sm.currentState(event)
}
}
func main() {
// Simple counter state machine
count := 0
incrementState := func(event int) State[int] {
count += event
fmt.Println("Count:", count)
return incrementState
}
sm := NewStateMachine(incrementState, 0)
sm.SendEvent(1)
sm.SendEvent(2)
sm.SendEvent(3)
}Question 29
How do you implement a generic event system?
type EventHandler[T any] func(T)
type EventBus[T any] struct {
handlers map[string][]EventHandler[T]
}
func NewEventBus[T any]() *EventBus[T] {
return &EventBus[T]{
handlers: make(map[string][]EventHandler[T]),
}
}
func (eb *EventBus[T]) Subscribe(eventType string, handler EventHandler[T]) {
eb.handlers[eventType] = append(eb.handlers[eventType], handler)
}
func (eb *EventBus[T]) Publish(eventType string, data T) {
if handlers, exists := eb.handlers[eventType]; exists {
for _, handler := range handlers {
handler(data)
}
}
}
func main() {
bus := NewEventBus[string]()
bus.Subscribe("message", func(msg string) {
fmt.Println("Received:", msg)
})
bus.Publish("message", "Hello World")
}Question 30
How do you implement a generic configuration system?
type Config[T any] struct {
data map[string]T
}
func NewConfig[T any]() *Config[T] {
return &Config[T]{
data: make(map[string]T),
}
}
func (c *Config[T]) Set(key string, value T) {
c.data[key] = value
}
func (c *Config[T]) Get(key string) (T, bool) {
value, exists := c.data[key]
return value, exists
}
func (c *Config[T]) GetOrDefault(key string, defaultValue T) T {
if value, exists := c.Get(key); exists {
return value
}
return defaultValue
}
// Typed configurations
type AppConfig struct {
Port Config[int]
Host Config[string]
Timeout Config[time.Duration]
}
func main() {
config := &AppConfig{
Port: *NewConfig[int](),
Host: *NewConfig[string](),
Timeout: *NewConfig[time.Duration](),
}
config.Port.Set("server_port", 8080)
config.Host.Set("server_host", "localhost")
port := config.Port.GetOrDefault("server_port", 3000)
host := config.Host.GetOrDefault("server_host", "127.0.0.1")
fmt.Printf("Server: %s:%d\n", host, port)
}Question 31
How do you implement a generic validation system?
type Validator[T any] func(T) error
type ValidationRule[T any] struct {
Name string
Validator Validator[T]
}
type ValidatorSet[T any] struct {
rules []ValidationRule[T]
}
func NewValidatorSet[T any]() *ValidatorSet[T] {
return &ValidatorSet[T]{}
}
func (vs *ValidatorSet[T]) AddRule(name string, validator Validator[T]) {
vs.rules = append(vs.rules, ValidationRule[T]{
Name: name,
Validator: validator,
})
}
func (vs *ValidatorSet[T]) Validate(value T) []error {
var errors []error
for _, rule := range vs.rules {
if err := rule.Validator(value); err != nil {
errors = append(errors, fmt.Errorf("%s: %w", rule.Name, err))
}
}
return errors
}
func main() {
stringValidator := NewValidatorSet[string]()
stringValidator.AddRule("not_empty", func(s string) error {
if s == "" {
return errors.New("string cannot be empty")
}
return nil
})
stringValidator.AddRule("min_length", func(s string) error {
if len(s) < 3 {
return errors.New("string must be at least 3 characters")
}
return nil
})
errors := stringValidator.Validate("hi")
for _, err := range errors {
fmt.Println("Validation error:", err)
}
}Question 32
How do you implement a generic serialization system?
type Serializer[T any] interface {
Serialize(T) ([]byte, error)
Deserialize([]byte) (T, error)
}
type JSONSerializer[T any] struct{}
func (js *JSONSerializer[T]) Serialize(value T) ([]byte, error) {
return json.Marshal(value)
}
func (js *JSONSerializer[T]) Deserialize(data []byte) (T, error) {
var value T
err := json.Unmarshal(data, &value)
return value, err
}
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
serializer := &JSONSerializer[Person]{}
person := Person{Name: "Alice", Age: 30}
data, err := serializer.Serialize(person)
if err != nil {
fmt.Println("Serialize error:", err)
return
}
fmt.Println("JSON:", string(data))
restored, err := serializer.Deserialize(data)
if err != nil {
fmt.Println("Deserialize error:", err)
return
}
fmt.Printf("Restored: %+v\n", restored)
}Question 33
How do you implement a generic dependency injection container?
type Factory[T any] func() T
type Container struct {
factories map[string]interface{}
}
func NewContainer() *Container {
return &Container{
factories: make(map[string]interface{}),
}
}
func (c *Container) Register[T any](name string, factory Factory[T]) {
c.factories[name] = factory
}
func (c *Container) Resolve[T any](name string) T {
if factory, exists := c.factories[name]; exists {
if typedFactory, ok := factory.(Factory[T]); ok {
return typedFactory()
}
}
var zero T
return zero
}
func main() {
container := NewContainer()
// Register services
container.Register("logger", func() *Logger { return &Logger{} })
container.Register("database", func() *Database { return &Database{} })
// Resolve services
logger := container.Resolve[*Logger]("logger")
db := container.Resolve[*Database]("database")
fmt.Printf("Logger: %p, DB: %p\n", logger, db)
}
// Placeholder types
type Logger struct{}
type Database struct{}Question 34
How do you implement a generic middleware system?
type Middleware[T any] func(T) T
type Chain[T any] struct {
middlewares []Middleware[T]
}
func NewChain[T any]() *Chain[T] {
return &Chain[T]{}
}
func (c *Chain[T]) Use(middleware Middleware[T]) {
c.middlewares = append(c.middlewares, middleware)
}
func (c *Chain[T]) Execute(input T) T {
result := input
for _, middleware := range c.middlewares {
result = middleware(result)
}
return result
}
func main() {
chain := NewChain[string]()
// Add logging middleware
chain.Use(func(s string) string {
fmt.Println("Processing:", s)
return s
})
// Add uppercase middleware
chain.Use(func(s string) string {
return strings.ToUpper(s)
})
result := chain.Execute("hello world")
fmt.Println("Final result:", result)
}Question 35
What is the most important consideration when using Go generics?
