Merging two Go maps often comes down to one precise rule: entries from one map are assigned into another, and matching keys replace existing destination values. maps.Copy gives that operation a standard-library form without changing its underlying assignment semantics.

The function mutates the destination map. It does not allocate a replacement, return a merged value, or recursively copy data stored behind pointers, slices, maps, or other reference-bearing values.

Source entries overwrite matching destination keys

maps.Copy accepts a destination followed by a source. Every source pair is assigned to the destination. Keys that exist only in the destination remain present.

package main

import (
    "fmt"
    "maps"
)

func main() {
    defaults := map[string]int{
        "workers": 4,
        "retries": 2,
    }
    overrides := map[string]int{
        "workers": 8,
    }

    maps.Copy(defaults, overrides)
    fmt.Println(defaults)
}

After the call, workers is 8 while retries remains 2. The operation is therefore closer to applying a set of assignments than replacing the entire destination map.

That distinction is useful for layered configuration data, lookup tables, registries, and similar structures where later values are meant to take precedence while unrelated destination entries stay intact.

The destination must already be writable

Because maps.Copy assigns entries into dst, a nil destination cannot accept a non-empty source. The assignment follows ordinary map behavior and panics when an entry is written to a nil map.

var dst map[string]int
src := map[string]int{"workers": 8}

maps.Copy(dst, src) // panic: assignment to entry in nil map

An empty but allocated destination is different:

dst := make(map[string]int)
maps.Copy(dst, src)

A nil source does not require special handling. It has no entries to copy, so the destination remains unchanged.

This allocation boundary also separates maps.Copy from maps.Clone. Clone creates a new map from one existing map, while Copy writes source entries into storage supplied by the caller.

Copying values is shallow

Each source value reaches the destination through ordinary assignment. For scalar values such as integers or booleans, that often matches the isolation a caller expects. Reference-bearing values retain their references.

package main

import (
    "fmt"
    "maps"
)

func main() {
    src := map[string][]int{
        "ports": {8080, 8443},
    }
    dst := make(map[string][]int)

    maps.Copy(dst, src)
    dst["ports"][0] = 9090

    fmt.Println(src["ports"])
}

The slice header is copied into dst, but its backing array is shared. Changing an element through the destination is therefore visible through the source entry as well.

The same boundary applies to pointer values and nested maps. maps.Copy does not claim deep-copy semantics. Code that needs independent nested state has to copy that state separately according to its concrete types and ownership rules.

Named map types can participate

The generic signature accepts map types whose underlying forms share the same key and value types. That means a named map type can be copied to or from an ordinary map when those element types match.

package main

import "maps"

type Limits map[string]int

func main() {
    dst := Limits{"workers": 4}
    src := map[string]int{"workers": 8, "retries": 2}

    maps.Copy(dst, src)
}

The destination keeps its declared type because the function mutates the value passed to it rather than constructing a result of some inferred map type.

The key and value types still have to align. maps.Copy is not a conversion facility: it does not transform map[string]int32 into map[string]int64, parse string keys, or invoke conversion callbacks.

Aliasing remains visible after the merge

A map assignment can create another handle to the same map. Since maps.Copy mutates its destination, every alias of that destination observes the resulting entries.

base := map[string]int{"workers": 4}
alias := base

maps.Copy(base, map[string]int{"workers": 8})

fmt.Println(alias["workers"]) // 8

If the caller needs to preserve base, cloning before applying source entries makes the ownership change explicit:

merged := maps.Clone(base)
maps.Copy(merged, overrides)

This pattern creates a new top-level map and then applies the overwrite rule to it. Nested reference-bearing values can still be shared because both maps.Clone and maps.Copy use shallow assignment for entries.

Merge order defines precedence

Multiple calls can express precedence directly. Starting from defaults, later copies can apply environment-specific or request-specific values in order:

merged := maps.Clone(defaults)
maps.Copy(merged, environment)
maps.Copy(merged, request)

For a key present in all three maps, the request value is the final one. A key absent from later maps retains the most recent value that was actually copied or cloned into merged.

The order is deterministic at the key-conflict level even though Go map iteration order itself is unspecified. Assignment order among distinct keys does not affect the resulting set of entries. When the same key appears in separate source maps, call order establishes which source has precedence.

maps.Copy stays deliberately narrow: it applies map assignments to existing destination storage. That narrow contract makes merge precedence visible in code, while allocation policy and deep-copy requirements remain decisions for the surrounding program.