Question
I have a Go struct and I want it to be initialized with sensible default values.
In many languages, this is usually handled with a constructor. However, Go is not object-oriented in the traditional sense, and structs do not have built-in constructors.
I have seen the init function, but that works at the package level rather than for individual structs.
Is there a struct-level equivalent in Go?
If not, what is the accepted best practice in Go for initializing a struct with default values?
Short Answer
By the end of this page, you will understand how Go handles struct initialization without built-in constructors. You will learn the idiomatic Go approach of using constructor-style functions, when zero values are enough, and how to provide safe, readable defaults for your types.
Concept
In Go, structs do not have constructors built into the language. Instead, Go relies on a combination of:
- zero values
- struct literals
- constructor-style helper functions
A zero value is the default value Go gives a type when you do not set it explicitly:
0for numbers""for stringsfalsefor booleansnilfor slices, maps, pointers, interfaces, channels, and functions
This is a major part of Go's design. Many Go types are intentionally designed so their zero value is already useful.
For example:
type Counter struct {
count int
}
A Counter{} is already valid because count starts at 0.
However, sometimes a type needs more than zero values to be useful. Maybe it needs:
- a default timeout
- a pre-created map
- a default configuration
- validation before use
In those cases, the idiomatic Go approach is to write a constructor-style function, usually named NewTypeName.
Example:
type Server {
host
port
timeout
}
Server {
Server{
host: ,
port: ,
timeout: ,
}
}
Mental Model
Think of a Go struct like a paper form.
- If the form is blank, Go still gives every field a default value. That is the zero value.
- Sometimes a blank form is good enough.
- Sometimes you want to hand out forms that already have common fields filled in. That is what a constructor-style function does.
So instead of the struct building itself, you create a helper function that returns a ready-to-use version of that struct.
init() is more like turning on the lights in the whole building before anyone enters. It prepares the package, not each individual form.
Syntax and Examples
Basic constructor-style function
In Go, the common pattern is to define a function like NewTypeName.
package main
import "fmt"
type User struct {
Name string
Admin bool
Score int
}
func NewUser() User {
return User{
Name: "Guest",
Admin: false,
Score: 0,
}
}
func main() {
user := NewUser()
fmt.Println(user)
}
This returns a User with sensible defaults.
Returning a pointer
Sometimes constructor-style functions return a pointer, especially when:
- the struct is large
- methods use pointer receivers
- you want to modify the same instance
package main
import "fmt"
type Config struct {
Host string
Port int
}
*Config {
&Config{
Host: ,
Port: ,
}
}
{
cfg := NewConfig()
fmt.Println(cfg.Host, cfg.Port)
}
Step by Step Execution
Example
package main
import "fmt"
type Cache struct {
Enabled bool
Size int
}
func NewCache() Cache {
return Cache{
Enabled: true,
Size: 100,
}
}
func main() {
c := NewCache()
fmt.Println(c.Enabled, c.Size)
}
What happens step by step
- The
Cachestruct type is defined with two fields:EnabledandSize. - The function
NewCache()is defined. It returns aCachevalue. - Inside
NewCache(), a struct literal is created:Enabledis set totrueSizeis set to100
Real World Use Cases
Constructor-style functions are common in Go when a type needs setup beyond zero values.
Common practical uses
- HTTP clients with default timeouts
- database wrappers with initialized connections or configuration
- services with default host and port values
- caches with preallocated maps
- configuration structs with fallback values
- workers with channels already created
Example: default API client
type Client struct {
BaseURL string
Timeout int
}
func NewClient() *Client {
return &Client{
BaseURL: "https://api.example.com",
Timeout: 30,
}
}
Example: initialized map field
type Store struct {
items map[string]int
}
func NewStore() *Store {
return &Store{
items: make(map[string]int),
}
}
Real Codebase Usage
In real Go codebases, developers often combine constructor-style functions with a few idiomatic patterns.
1. NewTypeName naming convention
This is the most common pattern:
func NewLogger() *Logger { ... }
func NewServer() *Server { ... }
func NewClient() *Client { ... }
It is familiar to Go developers and communicates intent clearly.
2. Zero-value-friendly design
Go developers often try to make types usable without requiring a constructor.
type Counter struct {
n int
}
func (c *Counter) Inc() {
c.n++
}
This works even if you declare:
var c Counter
c.Inc()
3. Guard clauses and validation
Constructors are often used to reject invalid input early.
(*Server, ) {
port <= {
, fmt.Errorf(, port)
}
&Server{Port: port},
}
Common Mistakes
1. Trying to use init() as a struct constructor
init() is for package setup, not per-instance setup.
Wrong idea
func init() {
// This does not run every time a struct is created.
}
Fix
Use a normal function such as NewUser().
2. Forgetting to initialize maps
A nil map cannot be written to.
Broken code
type Settings struct {
Values map[string]string
}
func main() {
s := Settings{}
s.Values["theme"] = "dark"
}
This will panic.
Fix
func NewSettings() *Settings {
return &Settings{
Values: ([]),
}
}
Comparisons
Struct literal vs constructor-style function
| Approach | Example | Best when | Pros | Cons |
|---|---|---|---|---|
| Struct literal | User{Name: "A"} | Caller knows all values | Simple and direct | Repeats setup logic |
| Zero value | var c Counter | Zero value is valid | Very idiomatic | Cannot set custom defaults |
| Constructor-style function | NewUser() | Defaults or validation are needed | Centralized setup | Adds one extra function |
init() vs NewType()
Cheat Sheet
Quick rules
- Go has no built-in constructors for structs.
- Use a constructor-style function like
NewTypeName. - Prefer designing types so the zero value is useful when possible.
- Use
init()only for package initialization, not struct instances. - Use constructors when you need:
- default values
- validation
- initialized maps, slices, or channels
- hidden setup logic
Common patterns
func NewUser() User {
return User{Name: "Guest"}
}
func NewConfig() *Config {
return &Config{Port: 8080}
}
func NewServer(port int) (*Server, error) {
if port <= 0 {
return nil, fmt.Errorf("invalid port")
}
return &Server{Port: port}, nil
}
Zero value reminder
FAQ
Is there a constructor keyword in Go?
No. Go does not have a constructor keyword. The usual approach is a normal function such as NewUser().
Should every struct in Go have a constructor?
No. If the zero value is already useful, a constructor is often unnecessary.
What is the idiomatic name for a constructor in Go?
The common naming convention is NewTypeName, such as NewClient or NewServer.
When should a Go constructor return a pointer?
Usually when the struct is mutable, large, or commonly used through pointer receiver methods.
Can I use init() to set default values for each struct instance?
No. init() runs once for package initialization, not every time you create a struct.
How do I enforce valid initialization in Go?
Use a constructor-style function that validates inputs and returns either a valid struct or an error.
Why are zero values important in Go?
They allow many types to work immediately without extra setup, which keeps code simple and idiomatic.
Mini Project
Description
Build a small Go configuration type for an application server. This project demonstrates when constructor-style functions are useful: setting defaults, initializing internal data, and making the type easy to use safely.
Goal
Create a ServerConfig type that starts with sensible defaults and can be used immediately without manual setup.
Requirements
- Create a
ServerConfigstruct with fields for host, port, debug mode, and headers. - Write a constructor-style function that returns a ready-to-use config with sensible defaults.
- Initialize any map fields properly so writing to them does not panic.
- In
main, create a config using the constructor and customize one or two fields. - Print the final config values to confirm the defaults and custom changes are applied.
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.