A struct groups related fields into a named type. It is Golang’s main tool for modeling users, tutorials, orders, API payloads, configuration, database records, and values passed between layers.
Structs keep data explicit. Instead of passing many separate values into a function, you can pass one well-named value with clear fields. This makes function signatures easier to understand and makes changes safer as the project grows.
package main
import "fmt"
type Tutorial struct {
ID int
Title string
Category string
Published bool
}
func (t Tutorial) Display() string {
return fmt.Sprintf("%d - %s", t.ID, t.Title)
}
A struct literal creates a struct value. Prefer named fields in most application code because they remain clear even when the struct changes. Positional literals are compact but fragile because field order matters.
Named fields also let you provide only the fields you need. Any omitted fields receive their zero value, such as 0 for numbers, false for booleans, and an empty string for strings.
| Literal Style | Example | Use |
|---|---|---|
| Named fields | Tutorial{ID: 1, Title: "Golang"} | Most application code |
| Positional | Tutorial{1, "Golang", "backend", true} | Small local structs only |
| Empty literal | Tutorial{} | Start with zero values and fill later |
tutorial := Tutorial{
ID: 1,
Title: "Golang Structs",
Category: "backend",
Published: true,
}
draft := Tutorial{
ID: 2,
Title: "Draft Lesson",
}
A method is a function with a receiver. The receiver connects behavior to a type without requiring classes. In Golang, data and behavior can stay close together while still using simple functions.
A value receiver works on a copy of the value. A pointer receiver can update the original value. Use a value receiver for small immutable behavior and a pointer receiver when the method mutates fields or avoids expensive copying.
type Counter struct {
Value int
}
func (c Counter) IsZero() bool {
return c.Value == 0
}
func (c *Counter) Increment() {
c.Value++
}
counter := Counter{}
counter.Increment()
fmt.Println(counter.IsZero())
Embedding supports composition by promoting fields and methods from one type into another. It is not classical inheritance. The outer struct still owns its own behavior, but it can reuse fields and methods from the embedded type.
Embedding is useful for shared metadata, common behavior, and small reusable building blocks. Avoid deep embedding chains because they can make it difficult to understand where a field or method comes from.
type AuditFields struct {
CreatedBy string
UpdatedBy string
}
type BlogPost struct {
ID int
Title string
AuditFields
}
post := BlogPost{
ID: 1,
Title: "Golang Embedding",
AuditFields: AuditFields{
CreatedBy: "admin",
UpdatedBy: "admin",
},
}
fmt.Println(post.CreatedBy)
Struct tags attach metadata to fields. Packages such as encoding/json, validators, form binders, and database libraries use tags to map fields to external names or rules.
Tags are string metadata, so they do not change the field type by themselves. A library must read the tag and decide how to use it.
| Tag | Meaning |
|---|---|
json:"id" | Encode or decode the field using the external name id. |
json:"email,omitempty" | Skip the field when it has an empty value. |
json:"-" | Ignore the field during JSON encoding and decoding. |
type UserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
Keep structs focused on one concept. A User struct should not also contain unrelated payment, permission, and report data unless that is truly the model being represented.
Create separate structs for database models, request payloads, and response payloads when their shapes differ. This avoids exposing internal fields accidentally and keeps validation rules easier to understand.
Explore 500+ free tutorials across 20+ languages and frameworks.