DEV Community

Cover image for make vs new in Go: Differences and Best Practices
Leapcell
Leapcell

Posted on

6 1 1 1 1

make vs new in Go: Differences and Best Practices

Cover

Go language provides two commonly used ways to allocate memory: make and new. Although both are used for memory allocation, their roles and usage scenarios are quite different. Understanding the differences between these two is crucial for writing efficient and maintainable Go code. This article will thoroughly analyze the differences between make and new, their suitable scenarios, and offer some usage tips.

Basic Differences Between make and new

new: Creates Zero Value of Pointer Types

new is a keyword in Go used for memory allocation. Its function is to allocate a block of memory for a type and return a pointer to that memory. The memory allocated by new is initialized to the zero value of the type.

Example: Using new

package main

import "fmt"

func main() {
    var p *int = new(int)
    fmt.Println(*p) // Output: 0, memory allocated for int by `new` is initialized to zero value
}
Enter fullscreen mode Exit fullscreen mode
  • Return type: new returns a pointer to the type.
  • Zero value initialization: new initializes the allocated memory to the zero value of the type. For example, for int type, the zero value is 0; for string type, it is an empty string "".

make: Initializes Slices, Maps, and Channels

make is a special built-in function in Go, specifically used to initialize three built-in data types: slice, map, and channel. Unlike new, make does not return a pointer to the allocated memory, but instead returns the initialized object itself.

Example: Using make

package main

import "fmt"

func main() {
    // Initialize slice
    s := make([]int, 5)
    fmt.Println(s) // Output: [0 0 0 0 0]

    // Initialize map
    m := make(map[string]int)
    m["age"] = 30
    fmt.Println(m) // Output: map[age:30]

    // Initialize channel
    ch := make(chan int, 2)
    ch <- 1
    fmt.Println(<-ch) // Output: 1
}
Enter fullscreen mode Exit fullscreen mode
  • Return type: make returns the object itself (slice, map, or channel), not a pointer.
  • Memory allocation and initialization: make not only allocates memory but also initializes the data structure itself. For example, when initializing a slice, make allocates the underlying array and sets its length and capacity.

Main Differences Summary

  • Purpose:

    • new: Allocates memory and returns a pointer to the type.
    • make: Initializes and returns a slice, map, or channel object.
  • Return Value:

    • new: Returns a pointer to the type.
    • make: Returns the initialized object itself.
  • Applicable Types:

    • new: All types.
    • make: Slice, map, and channel.
  • Initialization:

    • new: Returns zero value.
    • make: Initializes according to the data structure’s type.

Tips for Using make and new

Tips for Using new

Suitable for struct types:
new is often used to allocate memory for structs and return pointers to them. It is important to note that the initial value of a struct pointer is the zero value of the struct.

Example: Using new to allocate memory for a struct

type Person struct {
    Name string
    Age  int
}

func main() {
    p := new(Person)
    fmt.Println(p)      // Output: &{ 0}
    fmt.Println(p.Name) // Output: empty string
    fmt.Println(p.Age)  // Output: 0
}
Enter fullscreen mode Exit fullscreen mode
  • Pointers created by new: Since new returns a pointer to the struct, you can directly modify its fields with p.Name or p.Age.

Tips for Using make

Initialize slices with specified capacity:
make can be used to initialize a slice with a specified length and capacity. Using make, you can efficiently allocate the underlying array and initialize the slice.

Example: Using make to initialize a slice with capacity

// Initialize a slice with length 5 and capacity 10
s := make([]int, 5, 10)
fmt.Println(len(s), cap(s)) // Output: 5 10
Enter fullscreen mode Exit fullscreen mode
  • Specify map capacity during initialization: When creating a map with make, you can specify its initial capacity, which helps optimize performance by avoiding multiple memory expansions as elements are inserted.

Example: Using make to initialize a map

m := make(map[string]int, 10) // Set initial capacity to 10
m["age"] = 30
m["height"] = 175
fmt.Println(m) // Output: map[age:30 height:175]
Enter fullscreen mode Exit fullscreen mode
  • Initialize buffered channels: Use make to create a buffered channel, specifying the channel’s buffer size. This is very useful in concurrent programming.

Example: Using make to create a buffered channel

ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch) // Output: 1
Enter fullscreen mode Exit fullscreen mode

Choosing the Appropriate Memory Allocation Method

  • Usage scenario for structs: If you only need a pointer to a struct and have no special requirements during initialization, using new is a simple and common approach.
  • Usage scenario for slices, maps, and channels: If you need to initialize a slice, map, or channel and may modify their contents, make is the more appropriate choice—especially when you need to specify capacity in advance.

Performance Considerations of make and new

  • Memory allocation overhead: When initializing slices, maps, and channels, make not only allocates memory but also performs type initialization, which may incur additional overhead. In contrast, new only allocates memory and initializes it to the zero value, so its overhead is relatively small.
  • Avoid unnecessary memory allocations: For types such as slices, maps, or channels, it is recommended to specify an appropriate capacity when using make to reduce the number of memory reallocations.

Common Misuses

  • Incorrectly using new to create slices or maps: When new is used with slices, maps, or channels, it only returns the zero value of the type and does not perform initialization. Therefore, if you use new to create a slice, map, or channel and try to access its contents directly, it will result in a runtime error.

Incorrect example: Misusing new to create a map

m := new(map[string]int) // Incorrect: returns a pointer, not an initialized map
m["age"] = 30 // Runtime error: m is nil
Enter fullscreen mode Exit fullscreen mode

Correct example: You should use make to initialize a map.

m := make(map[string]int)
m["age"] = 30
Enter fullscreen mode Exit fullscreen mode

Summary

In Go, make and new are both keywords for memory allocation, and although their functions are similar, they have clear differences.
new is used to allocate memory for a type and returns a pointer, and it is suitable for most types;
while make is mainly used to initialize slices, maps, and channels, providing stronger initialization capabilities.

  • new: Suitable for creating pointers to struct types and other basic types, and initializes the memory to the zero value.
  • make: Used to initialize slices, maps, and channels, supports specifying capacity, and completes internal initialization.

Understanding the different usage scenarios and performance impacts of these two will help you write more efficient and maintainable Go code.


We are Leapcell, your top choice for hosting Go projects.

Leapcell

Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:

Multi-Language Support

  • Develop with Node.js, Python, Go, or Rust.

Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Explore more in the Documentation!

Try Leapcell

Follow us on X: @LeapcellHQ


Read on our blog

ACI image

ACI.dev: Best Open-Source Composio Alternative (AI Agent Tooling)

100% open-source tool-use platform (backend, dev portal, integration library, SDK/MCP) that connects your AI agents to 600+ tools with multi-tenant auth, granular permissions, and access through direct function calling or a unified MCP server.

Star our GitHub!

Top comments (0)

Dynatrace image

Highlights from KubeCon Europe 2025

From platform engineering to groundbreaking advancements in security and AI, discover the KubeCon Europe 2025 insights that are shaping the future of cloud native observability.

Learn more