Question
In Go, the introduction spends a lot of time explaining the difference between new() and make(). However, in practice, it seems possible to create values inside a local function scope and return them safely.
Why would you use new() or make() at all? When should each allocator be used, and why are they necessary if local variables can be returned from a function?
Short Answer
By the end of this page, you will understand why new() and make() exist in Go, what each one actually does, and why returning local values from a function is completely valid. You will also learn which types use make(), which types can use new(), and the practical situations where Go developers choose one approach over another.
Concept
In Go, new() and make() solve different problems.
new(T)allocates zeroed storage for a value of typeTand returns*T.make(T, ...)creates and initializes certain built-in reference-like types and returns a value of typeT, not*T.
What new() does
new(T) gives you a pointer to a zero value of type T.
p := new(int)
fmt.Println(*p) // 0
This is roughly similar to:
var x int
p := &x
So new() is not magic memory management that makes values survive after a function returns. Go already allows that safely.
What make() does
Mental Model
Think of Go values as different kinds of containers.
new()gives you the address of an empty box.make()gives you a box that is already assembled and ready for use for special container types.
Analogy
Imagine three situations:
new(int)is like getting a numbered parking spot with a car-sized empty space in it. You now have a pointer to that spot.make([]int, 3)is like getting an actual shelf with 3 usable slots already set up.- Returning a local variable from a function is like handing someone the shelf before leaving the room. Go makes sure it remains available.
The key idea is:
new()allocates storagemake()initializes special built-in data structures- returning locals is safe because Go handles the memory details for you
Syntax and Examples
Core syntax
new()
p := new(T)
- Allocates zeroed storage for type
T - Returns
*T
Example:
package main
import "fmt"
func main() {
count := new(int)
fmt.Println(*count) // 0
*count = 42
fmt.Println(*count) // 42
}
Here, count is a pointer to an int.
make()
s := make([]int, len, cap)
m := make(map[string]int)
ch := make( )
Step by Step Execution
Consider this example:
package main
import "fmt"
func build() (*int, map[string]int) {
n := 7
m := make(map[string]int)
m["value"] = n
return &n, m
}
func main() {
p, m := build()
fmt.Println(*p)
fmt.Println(m["value"])
}
Step by step
Inside build()
-
n := 7- A local integer variable is created with value
7.
- A local integer variable is created with value
-
m := make(map[string]int)- A usable empty map is created.
- This map is initialized and ready to store key-value pairs.
-
m["value"] = n
Real World Use Cases
Common practical uses of make()
Building maps for lookups
userAges := make(map[string]int)
userAges["Sam"] = 31
Used in:
- caching data
- counting occurrences
- grouping records
- configuration lookups
Creating slices with known size
results := make([]string, 0, 100)
Used in:
- collecting database rows
- building API response lists
- processing files line by line
Creating channels for goroutines
jobs := make(chan string)
Used in:
- worker pools
- background processing
- passing data safely between goroutines
Common practical uses of new()
When you specifically need a pointer to a zero value
Real Codebase Usage
In real Go codebases, developers usually choose the clearest form rather than always reaching for new().
Common patterns
Composite literals for structs
type Server struct {
Host string
Port int
}
func NewServer() *Server {
return &Server{Host: "localhost", Port: 8080}
}
This is more common than:
s := new(Server)
s.Host = "localhost"
s.Port = 8080
return s
make() before writing to a map
func countWords(words []string) map[string]int {
counts := make(map[string]int)
for _, w := range words {
counts[w]++
}
counts
}
Common Mistakes
1. Using new(map[...]) and expecting a usable map
Broken code:
m := new(map[string]int)
(*m)["a"] = 1
Why it fails:
new(map[string]int)returns*map[string]int- the map value inside is still
nil - writing to a nil map causes a panic
Correct code:
m := make(map[string]int)
m["a"] = 1
2. Using new([]int) when you really want a slice
Broken idea:
s := new([]int)
fmt.Println(len(*s)) // 0, but not very useful
This gives you a pointer to a nil slice, not an initialized working slice with length or capacity.
Usually you want:
Comparisons
new() vs make()
| Feature | new() | make() |
|---|---|---|
| Works with | Any type | Only slice, map, channel |
| Returns | Pointer to type (*T) | The type itself (T) |
| Initializes internal runtime data | No | Yes |
| Common use | Get pointer to zero value | Create usable slice/map/channel |
new(User) vs &User{}
| Form |
|---|
Cheat Sheet
Quick reference
Use new() when
- You need a
*T - You want a pointer to a zero value
p := new(int) // *int
u := new(User) // *User
Use make() when
- You need a usable slice, map, or channel
s := make([]int, 5)
m := make(map[string]int)
ch := make(chan string)
Important rules
new(T)returns*Tmake(T, ...)returnsTmake()only works for slices, maps, and channels- Returning pointers to local variables is safe in Go
- Nil map: readable, not writable
FAQ
Why does Go have both new() and make()?
Because they do different jobs. new() allocates zeroed storage and returns a pointer. make() creates usable slices, maps, and channels.
Can I return a pointer to a local variable in Go?
Yes. Go makes this safe automatically.
Why can't I write to a nil map?
A nil map has no initialized internal storage for entries. Use make(map[K]V) first.
Why can I append to a nil slice but not write to a nil map?
Slices and maps have different runtime behavior. append can allocate a new backing array for a nil slice, but map assignment requires an initialized map.
Should I use new() for structs?
Usually, &Struct{} or &Struct{Field: value} is clearer and more idiomatic.
Does make() allocate on the heap?
You usually do not need to care. Go decides stack or heap placement automatically based on usage.
Can make() be used with arrays or structs?
No. make() is only for slices, maps, and channels.
Mini Project
Description
Build a small Go program that manages a simple word counter. This project demonstrates when to use make() for maps and slices, and shows that local variables can be safely returned from functions.
Goal
Create a program that counts word frequency, returns the result from a helper function, and prints the counts.
Requirements
- Create a function that accepts a slice of words.
- Use a map to count how many times each word appears.
- Initialize the map correctly so assignments do not panic.
- Return the map from the function.
- In
main(), call the function and print the results.
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.