skip to content

In Go, what is the difference between make([]int, 3) and new([]int)?

level: middleimportance: must knowfreq 72%

answer

  1. Two allocating builtins, different jobs
  2. One returns a pointer, one returns the value
  3. Only three types accept the sizing one
  4. Zeroed storage is not an initialised map
  5. Second argument is length, third is capacity

basics

~20 s

make([]int, 3) builds an initialised slice of three zeroed ints and returns the slice itself. new([]int) allocates only a zeroed slice header and returns a *[]int that points at a nil slice of length 0.

solid answer

~40 s

`new(T)` works for any type: it allocates zeroed storage for a `T` and returns a `*T`. It never initialises anything beyond zeroing, so `new([]int)` hands you a pointer to a nil slice — you would still have to build a real slice before it is useful. `make` is defined only for slices, maps and channels, takes type-specific arguments, and returns an initialised value of type `T`, not a pointer. `make([]int, 3)` gives a slice of length 3 and capacity 3 whose elements are zero; `make([]int, 0, 3)` gives length 0 with room for three appends; `make(map[string]int, 64)` pre-sizes a map whose length is still 0. In practice you reach for `make` or a composite literal, and `new` mostly shows up as `new(int)` for a pointer to a plain zero value.

code

go · 10 lines
go
s := make([]int, 3)    // []int, len 3, cap 3, elements 0
t := make([]int, 0, 3) // []int, len 0, cap 3
m := make(map[string]int, 64) // empty writable map, len 0

p := new([]int) // *[]int; *p is a nil slice, len 0
q := new(int)   // *int; *q is 0

// to get a usable map through new you must still initialise it
mp := new(map[string]int)
*mp = make(map[string]int)

go deeper

for a junior

Memorise the shapes: make for slices and maps, returning the value; new for anything, returning a pointer to a zeroed value. Be able to say that new of a map does not give you a map you can store entries in.

for a middle

Explain the mechanics: what the second and third arguments to make mean for a slice, why a map size hint leaves len at 0, and why zeroing alone cannot produce a usable map.

for a senior

Show judgment about preallocation. Say when make([]T, 0, n) measurably reduces allocations under a known result size, and demonstrate the appended-after-zeros bug you have seen in review or in a benchmark run with -benchmem.

for a principal

Own the guidance rather than the trivia: decide when a hot path deserves capacity hints at all, and keep the team from cargo-culting preallocation into code where the size is unknown and the hint just wastes memory.

## Two allocating builtins with different jobs Go has exactly two builtin functions that allocate, and they are not interchangeable. **`new(T)`** takes a *type* and returns a `*T`. It allocates enough storage for one `T`, sets that storage to `T`'s zero value, and gives you its address. It knows nothing about what `T` means. `new(int)` is a `*int` pointing at `0`; `new(User)` is a `*User` whose every field is zeroed; `new([]int)` is a `*[]int` pointing at a slice header that is all zeros — that is, a nil slice with length 0 and capacity 0. **`make(T, ...)`** takes a type *and* size arguments, and returns an initialised value of type `T` — never a pointer. It is defined for only three types: slices, maps and channels. Those are the types whose usable form needs runtime bookkeeping that zeroing alone cannot produce, which is exactly why they get their own builtin. ## What make does per type * **Slices.** `make([]T, n)` allocates a backing array of `n` zeroed elements and returns a slice with `len` and `cap` both `n`. `make([]T, n, c)` allocates a backing array of `c` elements and returns a slice with `len == n` and `cap == c`; `c` must be at least `n`, and a negative or out-of-range length panics at run time (a constant one is a compile error). * **Maps.** `make(map[K]V)` returns an empty, writable map. `make(map[K]V, hint)` does the same but pre-sizes the internal storage for roughly `hint` entries. The hint is not a limit and does not change `len`, which is still 0; it is purely an allocation optimisation for when you know how many entries you are about to insert. * **Channels.** `make` is the only way to create one, and its second argument sizes it. That mechanism belongs to Go's concurrency material and is not developed here. ## Why new is the wrong tool for these three The zero value of a slice, map or channel is nil. For a slice, nil is often still workable — you can take its length and append to it. For a map, nil is readable but not writable. For a channel, nil blocks. So `new(map[string]int)` gives you a `*map[string]int` whose pointed-at map is nil and cannot store anything, and you would have to write `*m = make(map[string]int)` before it does any work. That is two steps to reach what `make` gives you in one, plus a pointer nobody wanted. This is why "use `new` for maps and slices" is a reliable interview red flag. ## Length versus capacity, and the append trap The most common real bug in this area is confusing `make([]T, n)` with `make([]T, 0, n)`. ``` s := make([]string, 3) s = append(s, "x") // len(s) == 4: three empty strings, then "x" ``` `make([]string, 3)` does not mean "room for three", it means "three elements, already present, all zero". `append` adds *after* those. When you intend to fill a slice by appending and you know the final size, the preallocation you want is `make([]T, 0, n)`: length 0, so the first append writes at index 0, and capacity `n`, so the appends do not have to reallocate. When instead you intend to fill by index — `for i := range s { s[i] = f(i) }` — `make([]T, n)` is the right call. ## Composite literals as the third route A composite literal builds and initialises in one expression, and is usually the most readable choice when you know the contents: `[]int{1, 2, 3}`, `map[string]int{"a": 1}`, `&User{ID: 1}`. `[]int{}` and `make([]int, 0)` both give a non-nil, empty slice; `map[string]int{}` and `make(map[string]int)` both give an empty, writable map. Use `make` when you want a size or a capacity, and a literal when you want contents. For structs, the idiomatic pointer form is `&T{...}` rather than `new(T)`, because it can set fields in the same expression. `new` survives mainly for the case with no literal form at all — `new(int)`, `new(time.Duration)` — where you need a pointer to a plain zero value and do not want a named temporary. ## Quick reference | expression | type | result | |---|---|---| | `new([]int)` | `*[]int` | points at a nil slice, len 0, cap 0 | | `make([]int, 3)` | `[]int` | three zeroed ints, len 3, cap 3 | | `make([]int, 0, 3)` | `[]int` | len 0, cap 3, ready for three appends | | `new(map[string]int)` | `*map[string]int` | points at a nil map, not ready to store entries | | `make(map[string]int)` | `map[string]int` | empty, writable | | `new(User)` | `*User` | zeroed struct, same as `&User{}` |

  • What is the difference between make([]int, 5) and make([]int, 0, 5)?
    The first has length 5 and capacity 5: five zero values are already there, so `append` adds a sixth element. The second has length 0 and capacity 5: nothing is there yet, and five appends fit without reallocating. Appending to `make([]T, n)` when you meant to preallocate is the classic source of leading zero values.
  • Does make(map[string]int, 1000) change the map's length?
    No. `len` is still 0; the second argument is only a size hint that pre-sizes internal storage so the first thousand inserts do fewer growth steps. It is not a maximum either — the map grows past it normally.
  • Is new(T) ever the idiomatic choice for a struct?
    Rarely. `&T{}` is equivalent for a zeroed struct and, unlike `new`, can set fields in the same expression, so it is what most Go code uses. `new` earns its place for types with no literal form worth writing, such as `new(int)` when a function wants a `*int` to a zero value.

saying these in an interview costs you the question

  • Says new(map[string]int) gives a map you can write to
  • Thinks make returns a pointer like new does
  • Claims make works for any type, including structs
  • Believes make([]int, 5) is empty and ready for append
  • Says the map size hint caps how many entries fit