Learning Golang - Data Types, Struct, Interface, and Generics
Episode 5 of 19

Learning Golang - Data Types, Struct, Interface, and Generics

This episode dissects data modeling in Go: structs as data models with field tags and method receivers, interfaces with idiomatic duck typing, plus modern generics with type constraints, generic slices and maps, and reusable algorithms.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A program full of separate variables quickly becomes chaotic. That's where structs and interfaces come in: structs shape data models, and interfaces define behavior contracts. Combined with generics, stable since Go 1.18, you can write reusable algorithms without losing type safety.

Episode 5 covers all three elements in depth: structs with field tags and method receivers, interfaces with duck typing, and modern generics with constraints and generic slices/maps. This is the data modeling toolkit you will use in every episode that follows.

Structs as Data Models

Declarations and Field Tags

A struct is a collection of named fields with specific types. Field tags are metadata strings behind the field type — the most common tags are used for JSON encoding:

Struct with JSON tags
package main
 
import (
	"encoding/json"
	"fmt"
)
 
type Pengguna struct {
	Nama string `json:"nama"`
	Umur int    `json:"umur"`
}
 
func main() {
	u := Pengguna{Nama: "Arman", Umur: 30}
	data, _ := json.Marshal(u)
	fmt.Println(string(data))
}

The json:"nama" tag controls the field name when marshaling and unmarshaling. The omitempty option omits empty fields: json:"umur,omitempty". The same tag convention is used by other libraries, such as ORMs, for database columns. There are three ways to create a struct instance: the keyed literal Pengguna{Nama: "Arman"}, the positional literal, and new. The keyed literal is recommended because it is explicit.

Method Receivers

A method is a function with a receiver — the type that owns the method. The pointer receiver *Pengguna allows a method to modify fields:

Method with a pointer receiver
type Pengguna struct {
	Nama string
	Umur int
}
 
func (p *Pengguna) BertambahUmur() {
	p.Umur++
}

The rule is consistent: if one method uses a pointer receiver, all methods on that type should use a pointer receiver. Mixed usage is confusing and triggers errors like "cannot use value as pointer".

Interfaces and Duck Typing

Contracts Satisfied Implicitly

An interface is a collection of methods. A type implicitly satisfies an interface if it has all the declared methods — no implements keyword. This is the essence of idiomatic Go duck typing: as long as it behaves like a duck, it's a duck.

Implementing fmt.Stringer
package main
 
import "fmt"
 
type Pengguna struct {
	Nama string
	Umur int
}
 
func (p Pengguna) String() string {
	return fmt.Sprintf("%s berumur %d", p.Nama, p.Umur)
}
 
func main() {
	var s fmt.Stringer = Pengguna{Nama: "Arman", Umur: 30}
	fmt.Println(s)
}

By implementing String(), the Pengguna type automatically satisfies the fmt.Stringer interface from the standard library, and fmt.Println uses that representation.

Empty Interface and any

Before generics, interface{} was used for any type. Since Go 1.18, its official alias is any. Be careful: any defers type checking to runtime and hands it over to the user via type assertions:

Type assertion on any
func cetakNilai(v any) {
	if angka, ok := v.(int); ok {
		fmt.Println("integer:", angka)
	}
}

Modern Generics

Type Parameters and Constraints

Generics let a function work with many types without duplication. Type parameters are written in square brackets, and a constraint limits which types are allowed.

A simple generic function
package main
 
import "fmt"
 
func CetakDua[T any](a, b T) {
	fmt.Println(a, b)
}
 
func main() {
	CetakDua(1, 2)
	CetakDua("satu", "dua")
}

Custom Constraints and Reusable Algorithms

The standard library provides the comparable constraint for types that support ==. For specific needs, you can define your own constraints:

A custom constraint
type Numerik interface {
	~int | ~float64
}
 
func RataRata[T Numerik](data []T) T {
	var total T
	for _, v := range data {
		total += v
	}
	return total / T(len(data))
}

The ~ sign allows alias types with the same underlying representation. The slices and maps packages, stable since Go 1.21, use generics for common operations such as slices.Sort, slices.Contains, and maps.Keys — no more writing sort helpers for every type. To see the list of available functions, run go doc slices from your terminal.

Combined Patterns in Real Applications

In real applications, all three work together. Here's an example of a generic repository:

A generic repository
package main
 
type Penyimpan[T any] struct {
	items []T
}
 
func (s *Penyimpan[T]) Tambah(item T) {
	s.items = append(s.items, item)
}
 
func (s *Penyimpan[T]) Semua() []T {
	return s.items
}

This pattern shows the power of Go: structs carry state, method receivers define behavior, and generics make it reusable for any type. This is the foundation of the repository pattern you will apply when connecting applications to a database in episode 8.

Closing

Episode 5 completed your data modeling toolkit: structs with field tags for serialization and method receivers for behavior, interfaces with implicit duck typing and any, and modern generics with type parameters, custom constraints, and the generic slices and maps packages.

Key takeaways:

  • Structs are data models; field tags control JSON serialization.
  • Pointer receivers let methods modify state.
  • Interfaces are satisfied implicitly through a set of methods.
  • any is the alias for interface{} meaning any type.
  • Generics use type parameters with constraints.
  • The slices and maps packages provide standard generic algorithms.

In the next episode we will discuss error handling, testing, and basic debugging — error handling idioms with error, fmt.Errorf, and errors.Is or errors.As, unit testing with the testing package, table-driven tests, and benchmarking with go test. Your code quality will be measured, not just assumed.

Learning Golang - Data Types, Struct, Interface, and Generics | Learning Golang