Deleting a key from a map in Go: what's under the hood?

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Delete key in map: how it works under the hood in Go

\n\n

Have you ever wondered what actually happens when you write delete(myMap, "key") in Go? Does the key simply disappear from memory? Or does a "hole" remain? And what if you delete a key from a nil map? If you've asked yourself these questions — you're not alone. The topic of delete key in map has gathered thousands of views on Stack Overflow, and today we'll look under the hood of this seemingly simple action.

\n\n

In this SEO article, we'll break down how a hash table in Go is structured (the hmap structure), how key deletion works, where the memory goes, and why you shouldn't be afraid of delete in loops. Let's go!

\n\n

A brief overview: how a map in Go works

\n\n

Before we talk about deletion, let's recall how map golang stores data. Internally, it's a hash table, the hmap structure. It consists of an array of buckets, each of which can store up to 8 key-value pairs. When you add a key, Go computes the hash, finds the bucket, and places the pair inside. If the bucket overflows, an overflow bucket chain is created.

\n\n

This is a classic implementation, but with a couple of tricks we'll discuss below.

\n\n

The hmap structure (simplified)

\n\n
type hmap struct {\n    count     int     // number of elements\n    flags     uint8\n    B         uint8   // logarithm of the number of buckets\n    buckets   unsafe.Pointer // pointer to the bucket array\n    oldbuckets unsafe.Pointer // during map growth\n    nevacuate uintptr\n    // ...\n}
\n\n

Key point: buckets is a contiguous array. Deletion does not shrink this array; it only marks the cell as "empty".

\n\n

Delete key in map: how it works at the code level

\n\n

The call delete(m, k) in Go is a built-in function that compiles into a runtime call. Internally, roughly the following happens:

\n\n
    \n
  1. Hash computation of the key — Go uses a hash function specific to the key type.
  2. \n
  3. Bucket lookup — the bucket number is determined by the lower bits of the hash.
  4. \n
  5. Linear search within the bucket (and overflow chains) — finds the cell with the desired key.
  6. \n
  7. Memory cleanup — Go zeroes out the memory for the key and value so the garbage collector can collect them.
  8. \n
  9. Marking the cell as "empty" — a special flag emptyRest or emptyOne.
  10. \n
  11. Decrementing the counter — m.count--.
  12. \n
\n\n

All of this happens in O(1) on average, but considering collisions, it can be O(n) in the worst case.

\n\n

Code example: delete in action

\n\n
package main\n\nimport "fmt"\n\nfunc main() {\n    m := map[string]int{\n        "apple":  5,\n        "banana": 3,\n        "cherry": 7,\n    }\n\n    fmt.Println("Before deletion:", m)\n    delete(m, "banana")\n    fmt.Println("After deletion:", m)\n\n    // Try deleting a non-existent key\n    delete(m, "grape") // nothing happens, no error\n    fmt.Println("After deleting a non-existent key:", m)\n}
\n\n

Note: delete key in map for a non-existent key is a safe operation. It simply does nothing. This is one of the common misconceptions among beginners.

\n\n

What happens to memory? Myths and reality

\n\n

Many developers fear that after delete, "holes" remain in the map, leading to memory leaks. Let's figure it out.

\n\n

Memory for keys and values

\n\n

When you delete a key, Go zeroes out the memory for that element. If the key or value were pointers or contained pointers (strings, slices, maps), the garbage collector will be able to collect them. The cell itself in the bucket remains, but is marked as empty.

\n\n

This means that the map does not shrink in size when you delete elements. The bucket array stays the same size. However, the freed memory for the key and value data can be reused by new insertions into the same map.

\n\n

Myth: "delete causes memory leaks"

\n\n

This is only partially true. If you constantly add and delete keys, the map will not shrink. But the memory for the bucket array itself is relatively small. The real problem can arise if you store large values (e.g., large slices) and then delete them — the underlying array of the slice may still be referenced if you keep a reference to it elsewhere. But that's not a map issue, it's a reference management issue.

\n\n

If you are truly concerned about memory, Go provides a way to create a new map and copy only the needed elements, leaving the old one for the GC. But in practice, this is rarely necessary.

\n\n

Deleting from a nil map

\n\n

What happens if you call delete on a nil map? Let's check:

\n\n
package main\n\nimport "fmt"\n\nfunc main() {\n    var m map[string]int // nil map\n    fmt.Println("Map is nil:", m == nil)\n    delete(m, "test") // this is safe!\n    fmt.Println("After deletion from nil map, no panic")\n}
\n\n

Yes, delete from a nil map is safe. The runtime simply checks if the map is nil and does nothing. This is a nice feature that saves you from having to check for nil before every deletion.

\n\n

Comparison with other operations

\n\n

delete vs. reassigning to nil

\n\n

Some developers, instead of deleting individual keys, simply assign the map variable to nil. This completely frees the memory for the map, but you lose all data. This is only suitable if you want to clear the entire map.

\n\n

delete vs. creating a new map

\n\n

If you need to delete many keys, it may be more efficient to create a new map and copy only the necessary elements. But this creates additional load on the GC. The choice depends on the specific task.

\n\n

Optimization tips

\n\n
    \n
  1. Don't be afraid of delete in loops — it works in O(1) on average and does not cause memory leaks if used correctly.
  2. \n
  3. If you need to clear the entire map, it's better to create a new one: m = make(map[K]V). The old map will be collected by the GC.
  4. \n
  5. For large maps with frequent deletions, consider using sync.Map if you need concurrent access, but remember that it is optimized for specific scenarios.
  6. \n
  7. Monitor memory using pprof if you suspect leaks. Most often, the problem is not in the map itself, but in references to its elements.
  8. \n
\n\n

Conclusion

\n\n

We've looked at how delete key in map works in Go under the hood. The key takeaways:

\n\n
    \n
  • delete marks a cell as empty, but does not shrink the map.
  • \n
  • Memory for keys and values is freed for the GC.
  • \n
  • Deleting from a nil map is safe.
  • \n
  • There is no need to fear "holes" — they are part of the hash table's normal operation.
  • \n
\n\n

Now you know exactly what happens when you call delete. Use this knowledge to write more efficient and confident Go code!

\n\n

If you found the article useful, share it with colleagues and subscribe to our blog — we have many more deep dives into Go internals ahead!

Blogs

Book Recommendations