Filtering a Go map often means removing entries from an existing map rather than allocating a replacement. maps.DeleteFunc expresses that operation directly: it visits entries and deletes each pair for which a predicate returns true.

That mutation model is the central detail. The function does not return a filtered copy, and callers that share the same map observe the deletions.

The predicate receives both key and value

maps.DeleteFunc accepts a map and a function with the key and value types of that map. A compact filter can use either argument or both:

package main

import (
    "fmt"
    "maps"
)

func main() {
    counts := map[string]int{
        "alpha": 4,
        "beta":  0,
        "gamma": 7,
    }

    maps.DeleteFunc(counts, func(name string, count int) bool {
        return count == 0
    })

    fmt.Println(counts)
}

The predicate marks entries for deletion. A true result removes the current pair; false keeps it. In this example, the entry with a zero count is removed while the other entries remain.

The key parameter matters when retention depends on identity rather than only stored data. A registry can remove entries by prefix, namespace, or another property encoded in the key without a separate pass that first collects keys.

Filtering changes the original map

Maps are reference-like values. Passing a map to maps.DeleteFunc gives the function access to the same underlying map state held by the caller.

package main

import (
    "fmt"
    "maps"
)

func main() {
    active := map[string]bool{
        "api":    true,
        "worker": false,
    }
    alias := active

    maps.DeleteFunc(active, func(_ string, enabled bool) bool {
        return !enabled
    })

    fmt.Println(alias)
}

alias observes the deletion because assigning a map does not clone its entries into separate storage. Code that needs an isolated result can clone first and filter the clone:

filtered := maps.Clone(active)
maps.DeleteFunc(filtered, func(_ string, enabled bool) bool {
    return !enabled
})

That creates a distinct top-level map before deletion. The clone remains shallow, so slices, pointers, maps, and other reference-bearing values stored inside entries can still refer to shared data.

Deletion during map iteration is defined behavior

A direct loop can express the same operation:

for key, value := range records {
    if expired(key, value) {
        delete(records, key)
    }
}

Go permits deletion of map entries that have not yet been reached during a range operation; deleted entries are not produced later by that iteration. maps.DeleteFunc uses this established map behavior to apply its predicate while traversing the map.

This also means there is no ordering contract to build on. Go map iteration order is unspecified, so a predicate must not depend on visiting entries in a particular sequence. If filtering logic needs ordered processing or cross-entry accumulation with deterministic sequencing, a map traversal alone does not provide that property.

Predicate side effects can obscure the mutation boundary

The predicate is ordinary Go code, so it can technically perform work beyond evaluating the current pair. Keeping it focused on the deletion condition makes the operation easier to reason about.

A predicate that mutates external state couples filtering to traversal order. A predicate that also changes the same map introduces additional range-mutation behavior into an operation whose visible purpose is already deletion. Such code can be valid in narrow cases, but the resulting state depends on more than the stated filter condition.

A pure condition keeps the boundary clear:

maps.DeleteFunc(cache, func(_ string, entry Entry) bool {
    return entry.ExpiresAt.Before(cutoff)
})

The map mutation then comes from one place: entries satisfying the condition are removed.

Nil and empty maps need no special branch

Deleting from a nil map is valid in Go, and ranging over a nil map produces no entries. As a result, maps.DeleteFunc can receive a nil map without a guard.

var labels map[string]string

maps.DeleteFunc(labels, func(_, _ string) bool {
    return true
})

The call completes without changing the nil state. The same property makes empty maps a natural no-op.

This differs from operations that insert entries, where a nil map cannot accept assignments. maps.DeleteFunc only removes entries, so it does not need to allocate map storage.

Named map types retain their type at the call boundary

The generic signature accepts types whose underlying type is a map. A named map type can therefore be passed directly:

type Headers map[string]string

headers := Headers{
    "content-type": "application/json",
    "debug":        "1",
}

maps.DeleteFunc(headers, func(key, _ string) bool {
    return key == "debug"
})

No conversion to an unnamed map[string]string is required. The operation mutates the supplied Headers value in place.

In-place filtering is a state decision

maps.DeleteFunc is a concise fit when the intended result is a smaller version of the same map state. Its semantics are more specific than a general filtering abstraction: there is no returned collection, no preserved traversal order, and no deep copy of retained values.

When callers need the original map unchanged, cloning before deletion makes that ownership boundary explicit. When shared mutation is intended, applying maps.DeleteFunc directly states the operation without an intermediate key list or replacement map.