Deep Copy in Golang: Techniques and Best Practices
Daniel Hayes
Full-Stack Engineer · Leapcell

Key Takeaways
- Deep copying in Go is essential to avoid unintended modifications to shared data.
- Slices, maps, and structs with reference types require explicit deep copying.
- Third-party libraries like
copier
can simplify deep copy operations.
In software development, creating copies of data structures is a common requirement. In Go (Golang), understanding how to perform deep copies is essential for ensuring that modifications to one data structure do not inadvertently affect another. This article explores various methods to achieve deep copying in Go.
Understanding Shallow vs. Deep Copy
Before diving into deep copying techniques, it's crucial to distinguish between shallow and deep copies:
-
Shallow Copy: Creates a new variable that references the same underlying data as the original. Changes to the data affect both the original and the copy.
-
Deep Copy: Produces a new variable with its own separate copy of the data. Modifications to the data in the copy do not impact the original.
Deep Copying Primitive Types
Primitive types in Go, such as integers, floats, booleans, and strings, are copied by value. Assigning one variable to another creates a deep copy:
a := 42 b := a b = 100 // 'a' remains 42, 'b' is 100
In this example, a
and b
are independent; changing b
does not affect a
.
Deep Copying Composite Types
For composite types like arrays, slices, maps, and structs, deep copying requires more attention.
Arrays
Arrays are value types in Go. Assigning one array to another results in a deep copy:
arr1 := [3]int{1, 2, 3} arr2 := arr1 arr2[0] = 10 // 'arr1' remains [1, 2, 3], 'arr2' is [10, 2, 3]
Slices
Slices are reference types. A simple assignment copies the slice header, not the underlying data:
slice1 := []int{1, 2, 3} slice2 := slice1 slice2[0] = 10 // Both 'slice1' and 'slice2' are [10, 2, 3]
To deep copy a slice, you need to copy the underlying data:
slice1 := []int{1, 2, 3} slice2 := make([]int, len(slice1)) copy(slice2, slice1) slice2[0] = 10 // 'slice1' is [1, 2, 3], 'slice2' is [10, 2, 3]
Maps
Maps are also reference types. A direct assignment shares the same underlying data:
map1 := map[string]int{"a": 1, "b": 2} map2 := map1 map2["a"] = 10 // Both 'map1' and 'map2' are {"a": 10, "b": 2}
To deep copy a map:
map1 := map[string]int{"a": 1, "b": 2} map2 := make(map[string]int) for k, v := range map1 { map2[k] = v } map2["a"] = 10 // 'map1' is {"a": 1, "b": 2}, 'map2' is {"a": 10, "b": 2}
Structs
Structs are value types. Assigning one struct to another copies the data:
type Point struct { X, Y int } p1 := Point{1, 2} p2 := p1 p2.X = 10 // 'p1' is {1, 2}, 'p2' is {10, 2}
However, if a struct contains reference types (e.g., slices, maps, pointers), you must deep copy those fields manually.
Using Third-Party Libraries
For complex data structures, manually implementing deep copy logic can be error-prone. Third-party libraries like copier can simplify the process:
import "github.com/jinzhu/copier" type Person struct { Name string Age int Friends []string } p1 := Person{Name: "Alice", Age: 30, Friends: []string{"Bob", "Charlie"}} var p2 Person copier.Copy(&p2, &p1) p2.Friends[0] = "David" // 'p1.Friends' is ["Bob", "Charlie"], 'p2.Friends' is ["David", "Charlie"]
Ensure you understand how the library performs deep copying, especially for nested or complex types.
Conclusion
Deep copying in Go requires careful consideration of the data types involved. While primitive types are straightforward, composite types necessitate explicit handling to ensure true deep copies. Utilizing third-party libraries can aid in managing complex structures, but always verify their behavior aligns with your application's requirements.
FAQs
Slices and maps are reference types, meaning assignments copy only references, not the underlying data.
Manually allocate a new slice and copy each element to ensure a true deep copy.
When dealing with complex nested structures where manual deep copying is error-prone.
We are Leapcell, your top choice for hosting Go projects.
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!
Follow us on X: @LeapcellHQ