Structs and Data Modeling

Updated

July 30, 2026

Overview

Structs are Go’s primary mechanism for creating custom composite types. They group related fields together.

Defining Structs

type Person struct {
    Name    string
    Age     int
    Email   string
    Active  bool
}

Creating Instances

// Zero value
var p Person  // All fields get zero values

// Literal (named fields - preferred)
p := Person{
    Name:  "Alice",
    Age:   30,
    Email: "alice@example.com",
}

// Literal (positional - fragile)
p := Person{"Alice", 30, "alice@example.com", true}

// Using new
p := new(Person)  // *Person with zero values

// Expression-based new (Go 1.26+)
p := new(Person{Name: "Alice"})  // *Person initialized with values
Embedded fields (composition)

Employee
|-- Person
|    |-- Name
|    |-- Age
|-- Address
|    |-- City
|-- Title

Accessing Fields

p.Name = "Bob"
fmt.Println(p.Age)

// Pointer access (automatic dereference)
ptr := &p
ptr.Name = "Carol"  // Same as (*ptr).Name

Anonymous Structs

point := struct {
    X, Y int
}{10, 20}

// Useful for one-off data
data := struct {
    ID    int
    Value string
}{1, "test"}

Embedding (Composition)

type Address struct {
    Street string
    City   string
}

type Employee struct {
    Person    // Embedded
    Address   // Embedded
    Title string
}

e := Employee{
    Person:  Person{Name: "Alice", Age: 30},
    Address: Address{City: "NYC"},
    Title:   "Engineer",
}

// Promoted fields
e.Name    // Same as e.Person.Name
e.City    // Same as e.Address.City

Struct Tags

Metadata for serialization and other tools:

type User struct {
    ID        int    `json:"id" db:"user_id"`
    Name      string `json:"name,omitempty"`
    Password  string `json:"-"`  // Omit from JSON
    CreatedAt time.Time `json:"created_at"`
}

Reading Tags

import "reflect"

t := reflect.TypeOf(User{})
field, _ := t.FieldByName("Name")
tag := field.Tag.Get("json")  // "name,omitempty"

Comparison

Structs are comparable if all fields are comparable:

p1 := Person{Name: "Alice"}
p2 := Person{Name: "Alice"}
p1 == p2  // true

Constructors

Go uses factory functions:

func NewPerson(name string, age int) *Person {
    return &Person{
        Name:   name,
        Age:    age,
        Active: true,  // Default
    }
}

func NewPersonWithDefaults() *Person {
    return &Person{
        Active: true,
    }
}

Summary

Pattern Usage
Define type Name struct { fields }
Create Name{Field: value}
Embed Include type name without field name
Tags Field Type \key:“value”`| | Constructor |func NewType() *Type`

More examples

Example: literal and field access

Save as main.go and go run . (with go mod init example if needed).

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func main() {
    u := User{Name: "ada", Age: 36}
    fmt.Printf("%+v\n", u)
    u.Age++
    fmt.Println("age:", u.Age)
}

Expected:

{Name:ada Age:36}
age: 37

Example: embedding promotes fields

Save as main.go and go run . (with go mod init example if needed).

package main

import "fmt"

type Address struct {
    City string
}

type Person struct {
    Name string
    Address
}

func main() {
    p := Person{Name: "lin", Address: Address{City: "Berlin"}}
    fmt.Println(p.Name, "in", p.City) // promoted field
}

Expected:

lin in Berlin

Runnable example

Save as main.go. From an empty directory:

go mod init example
go run .
package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type Address struct {
    Street string
    City   string
}

type Person struct {
    Name string
    Age  int
}

type Employee struct {
    Person  // embedded
    Address // embedded
    Title   string
}

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name,omitempty"`
    Password string `json:"-"`
}

func NewEmployee(name, title, city string) *Employee {
    return &Employee{
        Person:  Person{Name: name, Age: 0},
        Address: Address{City: city},
        Title:   title,
    }
}

func main() {
    var zero Person
    fmt.Printf("zero person: %+v\n", zero)

    e := NewEmployee("Ada", "Engineer", "London")
    e.Age = 36 // promoted field
    fmt.Printf("employee: name=%s title=%s city=%s age=%d\n", e.Name, e.Title, e.City, e.Age)

    // Pointer field access auto-dereferences
    p := &Person{Name: "Grace", Age: 85}
    p.Age++
    fmt.Printf("pointer person: %+v\n", p)

    // Anonymous struct for one-off data
    point := struct{ X, Y int }{10, 20}
    fmt.Printf("point=(%d,%d)\n", point.X, point.Y)

    // Comparable when all fields are comparable
    a := Person{Name: "Ada", Age: 36}
    b := Person{Name: "Ada", Age: 36}
    fmt.Println("a==b?", a == b)

    // Tags for JSON + reflection
    u := User{ID: 7, Name: "Ada", Password: "secret"}
    raw, err := json.Marshal(u)
    if err != nil {
        fmt.Println("json error:", err)
        return
    }
    fmt.Println("json:", string(raw))

    t := reflect.TypeOf(User{})
    if f, ok := t.FieldByName("Name"); ok {
        fmt.Println("Name tag:", f.Tag.Get("json"))
    }
    if f, ok := t.FieldByName("Password"); ok {
        fmt.Println("Password tag:", f.Tag.Get("json"))
    }
}

Expected output (illustrative):

zero person: {Name: Age:0}
employee: name=Ada title=Engineer city=London age=36
pointer person: &{Name:Grace Age:86}
point=(10,20)
a==b? true
json: {"id":7,"name":"Ada"}
Name tag: name,omitempty
Password tag: -

What to notice: - Embedding promotes fields (e.Name, e.City) without inheritance. - Factory NewEmployee sets defaults; zero Person is still valid. - JSON tags control names, omitempty, and secrets (json:"-"). - Struct equality works when every field is comparable.

Try next: Set Name to "" and remarshal—observe omitempty dropping the field.