# Edge Cases for `maps.Delete` vs Manual Delete Loops in Go

> Explore Go edge cases: maps.DeleteFunc panics on nil maps, unlike built-in delete. Learn which map deletion method to use for robust code.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: deep-dive
- Published: 2026-09-04

---

**The built-in `delete(m, k)` safely ignores nil maps, whereas `maps.Delete(m, k)` triggers a runtime panic on nil inputs—a critical distinction when choosing between idiomatic deletion and generic helper consistency.**

The Go standard library (since Go 1.21) introduced the generic `maps` package, providing `maps.Delete[K, V](m map[K]V, k K)` as a type-safe wrapper around the built-in `delete` statement. While this helper aligns with other `maps` utilities like `Keys` and `Values`, it diverges from the primitive `delete` behavior in several edge cases that impact nil-safety, version compatibility, and static analysis compliance. According to the JetBrains/go-modern-guidelines repository, understanding these distinctions prevents runtime panics and maintains codebase consistency.

## Nil Map Safety: The Primary Edge Case

**Nil map dereference** represents the most significant behavioral difference between the two approaches. When operating on a map that might be uninitialized, the built-in `delete` statement handles nil gracefully by performing a no-op, while `maps.Delete` lacks this defensive check.

Consider a struct with an optional configuration map:

```go
type Config struct {
    Settings map[string]string // may be nil
}

func (c *Config) Remove(key string) {
    // Safe: Built-in delete handles nil without panic
    delete(c.Settings, key)
    
    // Dangerous: maps.Delete panics if c.Settings is nil
    // maps.Delete(c.Settings, key) // ❌ runtime panic
}

```

To use `maps.Delete` safely with potentially nil maps, you must add explicit guards:

```go
if c.Settings != nil {
    maps.Delete(c.Settings, key) // Now safe, but verbose
}

```

This requirement adds boilerplate that negates the readability benefits of the generic helper in defensive code paths.

## Zero-Value Maps and Uninitialized Variables

**Zero-value map variables** behave identically to nil maps when using the built-in `delete`, returning immediately without error. However, `maps.Delete` treats these as nil pointer dereferences, resulting in the same panic behavior documented in the nil case.

Both zero-value variables and explicit nil assignments trigger this panic, making `maps.Delete` unsuitable for functions receiving maps that may not be initialized by the caller.

## Version Compatibility and Import Requirements

**Go version constraints** impose practical limitations on `maps.Delete` adoption. The standard library `maps` package requires Go 1.21 or later. Projects supporting earlier Go versions must import `golang.org/x/exp/maps` instead, introducing external dependencies solely for deletion syntax.

The `maps.Delete` function signature uses type parameters:

```go
func Delete[K comparable, V any](m map[K]V, k K)

```

This requires comparable keys, matching the built-in `delete` constraints, but adds the overhead of generic instantiation compared to the direct language primitive.

## Performance Characteristics

**Runtime overhead** remains minimal but non-zero for `maps.Delete`. The built-in `delete` operates as a direct compiler primitive with no function call overhead. `maps.Delete` introduces a thin generic wrapper that inlines in most cases but theoretically adds a function call boundary and type parameter resolution.

In performance-critical loops processing millions of entries, the manual `delete` statement avoids any generic dispatch overhead, though the difference is typically negligible in standard application code.

## Static Analysis and Linting Implications

The JetBrains/go-modern-guidelines repository references specific linting rules that influence deletion strategy selection. In `internal/guidelines/guidelines_test.go#L18-L20`, the `maps_keys_values_iter` guideline encourages using `maps.Keys` and `maps.Values` instead of manual iteration, establishing a precedent for `maps` package adoption.

However, linter behavior varies by tool:

- **gosimple** (rule `S1025`) may flag manual deletes inside loops when generic helpers exist, nudging toward `maps.Delete` for API consistency
- Most linters do **not** enforce `maps.Delete` over the built-in `delete` statement, recognizing the nil-safety trade-off
- Read-only map views may trigger lint warnings for any mutating operation regardless of the deletion method used

## Code Examples: Practical Deletion Patterns

### Pattern 1: Safe Nil-Resistant Deletion

```go
// Defensive deletion on potentially nil configuration
var options map[string]int // nil until initialized

// Built-in: Safe no-op
delete(options, "verbose")

// Generic helper: Requires nil check to prevent panic
if options != nil {
    maps.Delete(options, "verbose")
}

```

### Pattern 2: Consistency with Maps Package Utilities

When already using `maps.Keys` or `maps.Values` for iteration, `maps.Delete` provides API symmetry:

```go
// Using maps package consistently throughout function
for _, key := range maps.Keys(m) {
    if shouldRemove(key) {
        maps.Delete(m, key) // Consistent with maps.Keys usage
    }
}

```

### Pattern 3: Idiomatic Pre-1.21 Loop Deletion

For backward compatibility or nil-safety priority, manual deletion remains idiomatic:

```go
// Direct map iteration with built-in delete
for k := range m {
    if shouldRemove(k) {
        delete(m, k) // No panic risk on nil m
    }
}

```

## Summary

- **Nil safety**: Built-in `delete` handles nil maps gracefully; `maps.Delete` panics without explicit nil checks
- **Version requirements**: `maps.Delete` requires Go 1.21+ or the `golang.org/x/exp/maps` dependency
- **API consistency**: Use `maps.Delete` when the surrounding code already utilizes other `maps` package helpers like `Keys` or `Values`
- **Performance**: Built-in `delete` offers marginally better performance with zero function call overhead
- **Linting**: The `maps_keys_values_iter` guideline encourages `maps` package usage, but nil-safety concerns often override this recommendation

## Frequently Asked Questions

### Why does `maps.Delete` panic on nil maps when `delete` does not?

The built-in `delete` statement includes compiler-level nil checks that convert the operation into a no-op when the map is nil. `maps.Delete`, implemented as a generic function in [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go), receives the map as a parameter and attempts to dereference it to modify the underlying hashmap structure, triggering a runtime panic when the map reference is nil.

### Should I migrate existing `delete` calls to `maps.Delete` for consistency?

Only if your code already imports the `maps` package for other operations like `Keys` or `Values`. According to the JetBrains guidelines referenced in `internal/guidelines/guidelines_test.go#L18-L20`, maintaining consistency with the `maps` package improves readability, but the nil-safety risk of `maps.Delete` makes the built-in `delete` preferable for libraries that must handle uninitialized maps defensively.

### What Go versions support `maps.Delete`?

The `maps` package entered the standard library in Go 1.21. For Go 1.20 and earlier, you must import `golang.org/x/exp/maps` to access the generic `Delete` function. Projects with version constraints below 1.21 should continue using the built-in `delete` statement to avoid external dependencies.

### Does `maps.Delete` offer any performance benefits over manual loops?

No. `maps.Delete` provides a thin wrapper around the built-in delete operation with negligible overhead due to generic type resolution. For high-performance scenarios involving millions of deletions, the built-in `delete` statement avoids function call overhead, though modern compilers typically inline the generic wrapper. Choose `maps.Delete` for API consistency, not performance optimization.