Constructors in Go: Best Practices 2025 the Docs Don't Tell You About

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Constructors in Go: Not "Constructors," But Something Better



If you come to Go from the world of Python, Java, or C++, the first thing that will likely shock you is that Go has no constructors. At all. No __init__, ClassName(), or new MyStruct() with automatic invocation.



But don't panic just yet. This isn't a bug, it's a feature. By 2025, the Go community has fully formulated best practices for object initialization. And they are so elegant that you'll stop missing classic constructors.



In this article, we'll cover:

  • Why Go has no constructors and why that's cool
  • How to emulate them without pain
  • Factory functions, builder pattern, and fluent interfaces
  • Beginner mistakes and anti-patterns
  • Real-world examples from 2025 code


Let's go!



Why Are There No Constructors in Go? (Spoiler: It's a Conscious Choice)



The designers of Go (Rob Pike, Ken Thompson, and others) deliberately omitted constructors. Why?



  • Simplicity. Constructors often breed hidden logic, magic, and implicit calls. Go is a language where explicit is better than implicit.
  • Control. You decide when and how to initialize a struct. No hidden states.
  • Zero values. In Go, every variable always has a default (zero) value. This means var user User is already a valid object you can work with.


But this doesn't mean you can't write functions that create and configure objects. You can! And you should. They're just not called "constructors," but factory functions.



Basic Technique: Factory Function (NewXXX)



The most common pattern is a function starting with New. It returns a pointer to a struct, ensuring the object is created correctly.



package main

import "fmt"

// User — a simple structtype User struct { Name string Email string Age int}

// NewUser — factory function (constructor)func NewUser(name string, email string, age int) *User { return &User{ Name: name, Email: email, Age: age, }}

func main() { user := NewUser("Alice", "alice@example.com", 30) fmt.Println(user.Name) // Alice}


This is classic. But what if there are many parameters? 5, 10, 15? Passing them in order is a path to bugs. This is where optional parameters come into play.



Best Practice 2025: Functional Options



In 2025, functional options are the de facto standard for configuring complex objects in Go. The pattern was popularized by Dave Cheney, and it fits perfectly with the language's philosophy.



The idea: you pass option functions to the factory, which modify the struct before returning it.



package main

import "fmt"

type Server struct { Host string Port int Timeout int // in seconds TLS bool}

// Option — functional type for optionstype Option func(*Server)

// WithTimeout — option to set timeoutfunc WithTimeout(t int) Option { return func(s *Server) { s.Timeout = t }}

// WithTLS — option to enable TLSfunc WithTLS(enabled bool) Option { return func(s *Server) { s.TLS = enabled }}

// NewServer — factory with optionsfunc NewServer(host string, port int, opts ...Option) *Server { // default values s := &Server{ Host: host, Port: port, Timeout: 30, // default TLS: false, } // apply options for _, opt := range opts { opt(s) } return s}\

Blogs

Book Recommendations