Question

How to Initialize an Empty Slice in Go

goarraysslice

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-nil slice value

That means for many cases, they are effectively equivalent.

The key idea is this:

  • []int{} is a slice literal
  • make([]int, 0) creates a slice using Go's built-in make function

Why does this matter?

Because make becomes especially useful when you want to specify capacity ahead of time:

numbers := make([]int, 0, 100)

This creates an empty slice with room for 100 elements before Go needs to allocate a larger backing array.

So the usual guidance is:

  • Use []T{} when you simply want an empty slice.
  • Use make([]T, 0, capacity) when you know roughly how many items you will append.

This matters in real programs because slices are used everywhere: collecting results, building JSON data, processing records, accumulating errors, and more. Choosing the right initialization style can make code clearer and sometimes more efficient.

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.

Take Quiz

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:

[10] 1 1
[20] 1 1

Both slices start empty and both can grow with append.

Example: using capacity when you expect growth

package main

import "fmt"

func main() {
	values := make([]int, 0, 3)

	fmt.Println(len(values), cap(values))

	values = append(values, 1)
	values = append(values, 2)
	values = append(values, 3)

	fmt.Println(values)
	fmt.Println(len(values), cap(values))
}

This is useful when you know you will append several values and want to avoid unnecessary reallocations.

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:

  1. scores := make([]int, 0, 2)

    • Creates an empty slice of int
    • Length is 0 because it contains no elements yet
    • Capacity is 2 because space for 2 elements is reserved
  2. First Println

    • Prints [] 0 2
    • The slice is empty, but it is ready to hold two values without growing
  3. scores = append(scores, 90)

    • Adds 90 to the slice
    • Length becomes 1
    • Capacity stays 2
  4. Second Println

    • Prints something like [90] 1 2
  5. scores = append(scores, 95)

    • Adds 95
    • Length becomes 2
    • Capacity is still 2
  6. Third Println

    • Prints something like [90 95] 2 2

The important lesson is that an empty slice is still fully usable. It simply starts with no elements.

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.

Take Quiz

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
  • nil has 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, 0, len(input))
for _, item := range input {
	results = append(results, process(item))
}

Practical pattern: JSON responses

Some teams prefer returning empty arrays instead of null in JSON. In those cases, a non-nil empty slice such as []T{} may be chosen deliberately.

Practical pattern: guard clause with empty result

if len(source) == 0 {
	return []int{}
}

This can be used when an API contract prefers an empty slice over nil.

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:

  • length 10
  • capacity 10

That means it already contains 10 zero values.

Correct version:

numbers := make([]int, 0, 10)

3. Thinking one form is universally more correct

Both are correct:

a := []int{}
b := make([]int, 0)

Choose based on readability and whether you need to control capacity.

4. Forgetting to assign the result of append

Broken code:

numbers := []int{}
append(numbers, 1)

Correct code:

numbers := []int{}
numbers = append(numbers, 1)

append returns the updated slice.

5. Overusing make([]T, 0) when a literal is simpler

This is valid:

names := make([]string, 0)

But if capacity is not important, this is often shorter and clearer:

names := []string{}

Comparisons

FormResultNil?Best use
var s []intEmpty slice value with zero value semanticsYesWhen nil is fine or preferred
[]int{}Empty slice literalNoWhen you want a simple non-nil empty slice
make([]int, 0)Empty slice created with makeNoValid, but most useful when extended with capacity
make([]int, 0, 10)Empty slice with capacity 10NoWhen you expect future appends
make([]int, 10)Slice with 10 zero-value elementsNoWhen you need 10 existing elements immediately

[]T{} vs make([]T, 0)

  • Both are correct.
  • Both create empty, non-nil slices.
  • []T{} is often more concise.
  • make([]T, 0, cap) is better when planning capacity.

Empty slice vs nil slice

BehaviorNil sliceEmpty non-nil slice
len(s)00
cap(s)00 or more
append(s, x)WorksWorks
s == niltruefalse
JSON output in some contextsMay become nullMay become []
Take Quiz

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 []T when a nil slice is acceptable.
  • Do not confuse make([]T, n) with make([]T, 0, n).
  • Always assign the result of append.

Important checks

len(s)    // number of elements
cap(s)    // reserved space
s == nil  // only true for nil slices

Rule of thumb

  • No capacity needs? []T{}
  • Need capacity? make([]T, 0, n)
  • Want zero-value nil slice? var s []T

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?

For a simple empty slice, []T{} is often clearer. For preallocated capacity, make([]T, 0, n) is idiomatic.

Take Quiz

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 filterEvens that accepts []int and 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.
Take Quiz

Keep learning

Related questions

View more