Question
In Go, how can you set default values for struct fields, and what are the common ways to initialize structs when you want values other than Go's zero values?
For example, if a struct has several fields, how should it be created so that some fields start with meaningful defaults instead of the language-provided defaults? I have seen multiple approaches and want to understand the standard patterns and trade-offs.
Short Answer
By the end of this page, you will understand how Go initializes structs by default, what zero values are, and how to provide your own defaults using literals, constructor functions, and option-style patterns. You will also see when each approach is appropriate and how this is commonly handled in real Go codebases.
Concept
In Go, every variable has a zero value when it is created without explicit initialization. Struct fields follow the same rule.
For example:
intbecomes0stringbecomes""boolbecomesfalse- pointers, slices, maps, functions, and interfaces become
nil
That means Go does not have built-in support for declaring custom default field values directly inside a struct type definition.
type ServerConfig struct {
Host string
Port int
Debug bool
}
If you create this struct with:
var cfg ServerConfig
then the values are:
Host = ""Port = 0Debug = false
If you want different defaults such as Host = "localhost" or Port = 8080, you must set them yourself.
This matters because real applications often need sensible starting values:
Mental Model
Think of a Go struct like a new form with blank or standard system-filled fields.
- Go automatically fills every field with its zero value.
- If you want more useful starting values, you create the form through a helper that fills in the common defaults first.
So instead of thinking, "How do I put defaults inside the struct type?", think:
"What is the safest way to create this struct so it starts in a valid state?"
A constructor function is like a factory worker who prepares the object before handing it to you.
- Go zero values = the factory's raw material
- constructor = the worker applying your preferred defaults
- caller overrides = custom adjustments for a specific use case
Syntax and Examples
Zero-value initialization
If you declare a struct without assigning field values, Go uses zero values.
package main
import "fmt"
type User struct {
Name string
Age int
Active bool
}
func main() {
var u User
fmt.Printf("%+v\n", u)
}
Output:
{Name: Age:0 Active:false}
Struct literal with explicit values
You can set values directly when creating the struct.
u := User{
Name: "Alice",
Age: 25,
Active: true,
}
This is best when:
- you are creating the value in one place
- all important values are already known
- you do not need reusable defaults
Constructor function for defaults
A common Go pattern is to write a constructor function.
package main
ServerConfig {
Host
Port
Debug
}
ServerConfig {
ServerConfig{
Host: ,
Port: ,
Debug: ,
}
}
{
cfg := NewServerConfig()
fmt.Printf(, cfg)
}
Step by Step Execution
Consider this example:
package main
import "fmt"
type Config struct {
Host string
Port int
}
func NewConfig() Config {
return Config{
Host: "localhost",
Port: 8080,
}
}
func main() {
cfg := NewConfig()
fmt.Println(cfg.Host)
fmt.Println(cfg.Port)
}
Step-by-step
-
type Config struct { ... }- Defines a struct type with two fields:
HostandPort.
- Defines a struct type with two fields:
-
func NewConfig() Config- Declares a function that returns a
Configvalue.
- Declares a function that returns a
-
return Config{ Host: "localhost", Port: 8080 }- Creates a new struct literal.
- Assigns custom values instead of leaving fields at zero values.
Real World Use Cases
Custom defaults for structs are common in many kinds of Go programs.
Application configuration
type AppConfig struct {
Env string
Port int
LogJSON bool
}
Defaults might be:
Env: "development"Port: 3000LogJSON: false
HTTP servers and clients
A server or client may need default:
- timeout values
- base URLs
- retry counts
- ports
Database settings
A database config struct may define defaults for:
- max open connections
- idle timeout
- connection lifetime
CLI tools
Command-line tools often use defaults unless the user passes flags.
Example:
- default output format:
text - default config file path
- default verbosity level
Data processing jobs
Batch jobs may use default:
- chunk sizes
- worker counts
Real Codebase Usage
In real Go codebases, developers usually choose one of a few practical patterns.
1. Constructor returning a ready-to-use value
func NewConfig() Config {
return Config{
Port: 8080,
}
}
Use this when:
- defaults are simple
- the struct should always start valid
- you want one clear creation path
2. Constructor returning a pointer
func NewConfig() *Config {
return &Config{
Port: 8080,
}
}
This is common when:
- the struct is large
- methods use pointer receivers
- the value will be modified after creation
3. Defaults plus validation
A constructor often sets defaults and then validates input.
func NewConfig(port int) (Config, error) {
cfg := Config{Port: 8080}
if port != 0 {
cfg.Port = port
}
cfg.Port < || cfg.Port > {
Config{}, fmt.Errorf()
}
cfg,
}
Common Mistakes
1. Expecting custom defaults in the struct definition
This is not valid Go:
type Config struct {
Host string = "localhost"
Port int = 8080
}
Go struct fields cannot be assigned default values inside the type definition.
2. Confusing zero values with missing values
Sometimes 0 or "" is a real value, not just a missing value.
func NewConfig(port int) Config {
cfg := Config{Port: 8080}
if port != 0 {
cfg.Port = port
}
return cfg
}
This treats 0 as "not provided." That may be okay, but only if 0 is never meaningful in your design.
To avoid confusion, you can use pointers or option functions when you need to distinguish "unset" from an intentional zero.
3. Repeating defaults in many places
Broken approach:
cfg1 := Config{Host: , Port: }
cfg2 := Config{Host: , Port: }
cfg3 := Config{Host: , Port: }
Comparisons
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Zero-value struct | Simple cases where zero values are valid | Very simple, idiomatic | No custom defaults |
| Struct literal | One-off initialization | Clear and direct | Defaults may be duplicated |
| Constructor function | Reusable defaults | Centralized logic, easy to maintain | Adds an extra function |
| Constructor with parameters | Defaults plus a few overrides | Simple and practical | Can become messy with many parameters |
| Functional options | Many optional fields | Flexible, readable call sites, scalable | More code and slightly harder for beginners |
Struct literal vs constructor
Cheat Sheet
Key rules
- Go gives all struct fields a zero value automatically.
- You cannot declare custom default field values inside a struct type.
- Use a constructor function to centralize custom defaults.
- Use named field literals for readability.
- Use options when many fields are optional.
Zero values
| Type | Zero value |
|---|---|
int | 0 |
string | "" |
bool | false |
| pointer | nil |
| slice | nil |
| map | nil |
FAQ
Does Go support default values directly in struct definitions?
No. Go only provides zero values automatically. Custom defaults must be set during initialization.
What is the most idiomatic way to give a struct default values in Go?
Usually a constructor function such as NewConfig() is the cleanest and most maintainable approach.
Should a Go constructor return a struct or a pointer?
Either is valid. Return a value for small, simple structs. Return a pointer when the struct is large or meant to be modified.
Can I use struct tags to set default values?
Not by themselves. Struct tags are just metadata. Some libraries read tags and apply defaults, but Go does not do that automatically.
Why does Go prefer zero values?
Zero values make variables immediately usable in many cases and keep the language simple. For example, a zero-value mutex or bytes buffer can still be valid to use.
How do I handle optional fields where zero is also a valid value?
Use pointers, separate boolean flags, or functional options so you can distinguish between "not provided" and an intentional zero value.
Is it okay to create structs directly instead of using a constructor?
Yes, if zero values or explicit field values are enough. Use a constructor when you need defaults, validation, or guaranteed invariants.
Mini Project
Description
Build a small configuration system for a Go application. The project demonstrates how to create a struct with sensible defaults, allow selected overrides, and keep initialization logic in one place instead of spreading hard-coded values throughout the codebase.
Goal
Create a NewAppConfig constructor that returns a config struct with defaults and supports custom overrides for selected fields.
Requirements
- Define an
AppConfigstruct with fields for app name, port, debug mode, and labels. - Provide sensible default values for all fields.
- Allow the caller to override at least the port and debug mode.
- Ensure the
Labelsmap is initialized and safe to write to. - Print the final configuration to verify the result.
Keep learning
Related questions
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.
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.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.