Memory Allocation in Go: make, new and var

In Go, there are several ways to allocate and initialize memory.
Pre-requisite: undertsand types in Go

var Declaration
When you declare a variable using var:

var x int
var s string
var m map[string]int
var sl []int
  • The variable is zero-valued (0 for numeric types, "" for strings, nil for pointers, maps, slices, etc.)
  • For primitive types (int, string, etc.), memory is allocated directly
  • For reference types (slices, maps, channels), only the header structure is created, but not the underlying data structure - they're initialized to nil

new() Function
new() allocates memory but doesn't initialize it:

x := new(int)
m := new(map[string]int) // m points to a nil map
  • Memory is allocated for that specific type (so the type is known)
  • The memory is zero-initialized (filled with zeros)
  • Returns a pointer to a newly allocated, zero-valued variable
  • Memory is allocated but the underlying data structure isn't initialized** (for composite reference types)
  • For maps, slices, and channels, this means you get a non-nil pointer to a nil value

make() Function
make() allocates AND initializes memory:

m := make(map[string]int)
s := make([]int, 5, 10)
ch := make(chan int, 10)
  • Only works for slices, maps, and channels
  • Allocates and initializes the underlying data structure**
  • Returns the initialized (non-pointer) value itself, not a pointer
  • Can specify capacity and length for slices and buffered channels

Key Differences

What they return:

  • var declares a variable with its zero value
  • new() returns a pointer to a zero-valued variable
  • make() returns an initialized (non-pointer) value

What they can be used for:

  • var can be used for any type
  • new() can be used for any type
  • make() can only be used for slices, maps, and channels

Initialization:

  • var and new() only zero-initialize
  • make() properly initializes the underlying data structure

In Gist

// Using var
var i int        // i = 0
var s string     // s = ""
var m map[string]int  // m = nil, not usable yet!

// Using new()
pi := new(int)   // pi is a *int pointing to 0
pm := new(map[string]int) // pm is a *map[string]int pointing to nil map
// (*pm)["key"] = 1 would panic!

// Using make()
m2 := make(map[string]int)  // m2 is an initialized, empty map
m2["key"] = 1    // works fine

sl := make([]int, 3, 5)  // slice with length 3, capacity 5

** When we say Memory is allocated but the underlying data structure isn't initialized- What do we mean?

📌📌 We refer to specific situation where we are working on types that:

  1. Are composite (composed of other types)
  2. Have reference semantics (pass by reference)
  3. Require internal data structure initialization
    which would be slices, maps and channels.

When we use new() on a map:

  • Memory is allocated for the map header (so Go knows it's a map)
  • The header is zero-initialized, which means the pointer to the underlying hash table is set to nil
  • We get a pointer to this header

The map header exists and has a type, but it points to nothing (nil). That's why we say the underlying data structure isn't initialized. The map exists as a variable with the correct type, but it's not ready to be used for storing key-value pairs.

When we use make() for slices, maps, and channels, we're initializing the underlying data structures that these reference types point to, making them ready for use.

m := new(map[string]int) // m points to a map header, but the map itself is nil
// (*m)["key"] = 1       // This would panic because the underlying hash table doesn't exist yet

m := make(map[string]int) // Creates the map header AND initializes the hash table
m["key"] = 1               // This works fine

Other composite types that don't have this dual nature (like structs and arrays) don't need make() - they can be used immediately when declared with var or can be allocated with new() without further initialization.