Question
In Go, what is the preferred way to declare an empty slice whose size is not fixed?
For example, is it better to write:
mySlice1 := make([]int, 0)
or:
mySlice2 := []int{}
Which approach is correct, and when should each one be used?
Short Answer
By the end of this page, you will understand how empty slices work in Go, the difference between make([]T, 0) and []T{}, when they behave the same, and when make becomes useful because of capacity planning.
Concept
In Go, a slice is a flexible view over an underlying array. Slices are commonly used when you want a collection that can grow or shrink.
Both of these create an empty slice:
make([]int, 0)
[]int{}
In practice, both are valid and both produce a slice with:
- length
0 - capacity
0 - non-
nilslice value
That means for many cases, they are effectively equivalent.
The key idea is this:
[]int{}is a slice literalmake([]int, 0)creates a slice using Go's built-inmakefunction
Why does this matter?
Because make becomes especially useful when you want to specify capacity ahead of time:
numbers := make([]int, 0, )
Mental Model
Think of a slice like a shopping cart.
- The length is how many items are currently in the cart.
- The capacity is how much space the cart currently has before you need a bigger one.
Now compare the two forms:
[]int{}gives you an empty cart.make([]int, 0)also gives you an empty cart.make([]int, 0, 10)gives you an empty cart that already has room for 10 items.
So if you do not care about reserved space, both empty-cart versions are fine. If you want to prepare for future appends, make is the tool designed for that.
Syntax and Examples
Core syntax
Empty slice with a literal
numbers := []int{}
Empty slice with make
numbers := make([]int, 0)
Empty slice with preallocated capacity
numbers := make([]int, 0, 5)
Example: both forms work the same for appending
package main
import "fmt"
func main() {
one := []int{}
two := make([]int, 0)
one = append(one, 10)
two = append(two, 20)
fmt.Println(one, len(one), cap(one))
fmt.Println(two, len(two), cap(two))
}
Possible output:
Step by Step Execution
Consider this example:
package main
import "fmt"
func main() {
scores := make([]int, 0, 2)
fmt.Println(scores, len(scores), cap(scores))
scores = append(scores, 90)
fmt.Println(scores, len(scores), cap(scores))
scores = append(scores, 95)
fmt.Println(scores, len(scores), cap(scores))
}
Step by step:
-
scores := make([]int, 0, 2)- Creates an empty slice of
int - Length is
0because it contains no elements yet - Capacity is
2because space for 2 elements is reserved
- Creates an empty slice of
-
First
Println- Prints
[] 0 2 - The slice is empty, but it is ready to hold two values without growing
- Prints
Real World Use Cases
Empty slices are common in real Go programs.
Collecting database results
You may query rows and append each result:
users := []User{}
or, if you expect many rows:
users := make([]User, 0, 100)
Building API responses
When preparing JSON arrays for an HTTP response, you often start with an empty slice and append items as you process data.
Filtering data
If you loop through values and keep only matches, an empty slice is the natural starting point:
var evens []int
for _, n := range nums {
if n%2 == 0 {
evens = append(evens, n)
}
}
Collecting validation errors
Applications often build a list of problems before returning them:
errors := []string{}
Preallocating for performance
When parsing a file with a known number of records, make([]T, 0, n) can reduce repeated allocations.
Real Codebase Usage
In real codebases, developers choose the initialization style based on intent.
Common patterns
Use var s []T for a nil slice when nil is acceptable
var items []string
This is common when:
- you want the zero value
nilhas a meaningful distinction- you will append later
Use []T{} for a clearly non-nil empty slice
items := []string{}
This is useful when you want to signal: "this is intentionally an empty collection."
Use make([]T, 0, n) when capacity matters
items := make([]string, 0, expectedCount)
This is common in:
- parsers
- data import code
- batch processing
- performance-sensitive loops
Practical pattern: append in loops
results := make([]Result, , (input))
_, item := input {
results = (results, process(item))
}
Common Mistakes
1. Confusing empty slices with nil slices
These are different:
var a []int // nil slice
b := []int{} // empty, non-nil slice
c := make([]int, 0) // empty, non-nil slice
They often behave similarly with len, cap, and append, but they are not identical.
fmt.Println(a == nil) // true
fmt.Println(b == nil) // false
fmt.Println(c == nil) // false
Avoid this mistake by deciding whether nil vs empty matters in your program.
2. Using make([]int, 10) when you meant "empty with room for 10"
Broken intent:
numbers := make([]int, 10)
This does not create an empty slice with capacity 10. It creates a slice with:
Comparisons
| Form | Result | Nil? | Best use |
|---|---|---|---|
var s []int | Empty slice value with zero value semantics | Yes | When nil is fine or preferred |
[]int{} | Empty slice literal | No | When you want a simple non-nil empty slice |
make([]int, 0) | Empty slice created with make | No | Valid, but most useful when extended with capacity |
make([]int, 0, 10) | Empty slice with capacity 10 | No | When you expect future appends |
Cheat Sheet
// Nil slice
var s []int
// Empty, non-nil slice
s := []int{}
// Empty, non-nil slice using make
s := make([]int, 0)
// Empty slice with reserved capacity
s := make([]int, 0, 10)
// Slice with length 10 (already contains 10 zero values)
s := make([]int, 10)
Quick rules
- Use
[]T{}for a simple empty slice. - Use
make([]T, 0, n)when you know expected capacity. - Use
var s []Twhen anilslice is acceptable. - Do not confuse
make([]T, n)withmake([]T, 0, n). - Always assign the result of
append.
Important checks
len(s) // number of elements
cap(s)
s ==
FAQ
Is make([]int, 0) better than []int{} in Go?
Not generally. Both are correct for creating an empty non-nil slice. Use make when you also want to set capacity.
What is the difference between an empty slice and a nil slice in Go?
A nil slice has no underlying slice value and compares equal to nil. An empty slice has length 0 but is not nil.
Should I use var s []int or []int{}?
Use var s []int if a nil slice is acceptable. Use []int{} if you specifically want a non-nil empty slice.
Why would I use make([]T, 0, n)?
It lets you reserve capacity for future appends, which can reduce allocations and improve performance.
Does append work on a nil slice?
Yes. Appending to a nil slice works in Go.
What does make([]int, 10) do?
It creates a slice with length 10 and capacity 10, filled with zero values. It is not empty.
Which style is more idiomatic in Go?
Mini Project
Description
Build a small Go program that collects even numbers from a list. This demonstrates when to start with an empty slice and when to preallocate capacity for future append calls.
Goal
Create a function that filters a slice of integers and returns only the even numbers using an empty slice correctly.
Requirements
- Write a function named
filterEvensthat accepts[]intand returns[]int. - Start with an empty result slice.
- Append only even numbers to the result.
- Print the final slice, its length, and its capacity.
- Add a version that preallocates capacity using
make.
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.