make() vs new() in Go: What to Choose in 2025? A Clear Guide

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

Make or New? The Eternal Question of Go Developers



You've been writing Go for a week or a year, but you still sometimes pause for a second before choosing: make() or new()? That's normal. The question "Why would I make() or new()?" on Stack Overflow has gathered tens of thousands of views, and it remains relevant even in 2025. Why? Because behind the apparent simplicity lies a fundamental difference in the philosophy of memory management in Go.



Let's figure it out once and for all, so you never have to guess again. We'll go from the basics to real-world cases, highlight pitfalls, and provide a checklist for daily work. Let's go!



Short Answer (for those in a hurry)



If you need to allocate memory for a slice, map, or channel — use make(). In all other cases — use new() or the literal &Type{}. But the devil, as always, is in the details.



What is new() and How Does It Work



The function new(T) is a built-in Go function that allocates memory for type T, zeroes it (zero value), and returns a pointer to that memory: *T.



Simply put: new() gives you an empty but valid object. You get a pointer, but the object itself is not yet ready for full use if it requires initialization of internal structures.



package main

import "fmt"

type Person struct { Name string Age int}

func main() { p := new(Person) fmt.Println(p) // Output: &{ 0} fmt.Println(p.Name == "") // true}


Here, new(Person) allocated memory for the struct, zeroed the fields (empty string and 0), and returned a pointer. All fair.



What is make() and Its Superpower



make() is also a built-in function, but it works only with three types: slices, maps, and channels. Why only these? Because these types have an internal structure (header) that needs to be initialized, not just zeroed.



make() does not return a pointer. It returns an initialized value of the required type (not a pointer!).



// Slice: length 5, capacity 10s := make([]int, 5, 10)fmt.Println(len(s), cap(s)) // 5 10

// Map: ready for element insertionm := make(map[string]int)m["key"] = 42fmt.Println(m) // map[key:42]

// Channel: buffered with capacity 3ch := make(chan int, 3)ch <- 1fmt.Println(<-ch) // 1


Note: if you try to write m := new(map[string]int), you will get a pointer to a nil map. Inserting an element into such a map will cause a panic.



Why Doesn't new() Work for Slices, Maps, and Channels?



Let's look at the slice example. A slice in Go is a structure of three fields: a pointer to an array, length, and capacity. new([]int) will allocate memory for this structure and zero it. The pointer will be nil, length and capacity will be 0. You'll get &[] — a pointer to a nil slice. Will it break on index assignment? Yes, it will panic.



make([]int, 3) does three things:

  • Allocates an actual array of 3 elements under the hood (or more if capacity is specified).
  • Initializes the slice header (pointer, length, capacity).
  • Returns a ready-to-use slice.


The same goes for maps: make(map[string]int) creates a hash table and allocates memory for buckets. new(map[string]int) only gives a nil map.



When to Use new() in 2025?



Current practice in the Go community is that new() is rarely used. Most often, developers prefer literals with address-of: &Type{}. This is more readable and allows you to set fields immediately.

Blogs

Book Recommendations