Question
In Go, how can you remove selected keys from a map safely? Is it valid to call delete() while iterating over the same map with range, like in this example?
package main
import "fmt"
type Info struct {
value string
}
func main() {
table := make(map[string]*Info)
for i := 0; i < 10; i++ {
str := fmt.Sprintf("%v", i)
table[str] = &Info{value: str}
}
for key, value := range table {
fmt.Printf("deleting %v => %v\n", key, value.value)
delete(table, key)
}
}
What happens when keys are deleted during map iteration, and is this approach safe to use?
Short Answer
By the end of this page, you will understand how Go map iteration behaves, whether deleting keys during a range loop is safe, what Go guarantees about that behavior, and which deletion patterns are best for real programs.
Concept
In Go, a map is a built-in hash table structure used to store key-value pairs. You often iterate over a map using for ... range and remove entries with delete(map, key).
The key idea is:
- Deleting from a map during iteration is safe in Go.
- Go specifically allows
delete(m, key)while ranging overm. - If a map entry has not yet been reached during iteration and you delete it, that entry will not be produced later.
- If you delete the current key, that is also safe.
This matters because many languages either forbid modifying a collection during iteration or make the behavior undefined. Go is more explicit here for deletion.
However, there is an important distinction:
- Deleting entries during iteration is safe.
- Adding new entries during iteration has unpredictable iteration results for those new entries.
So the main rule is: if your goal is to remove matching items from a Go map, deleting inside a range loop is a normal and idiomatic approach.
A second thing to remember is that map iteration order in Go is not guaranteed. That means the order in which keys are visited and deleted is arbitrary.
In short:
- Safe to delete while ranging: yes
- Safe to rely on iteration order: no
- Safe to expect newly inserted items to always appear in the same loop: no
Mental Model
Think of a Go map like a basket of labeled cards.
rangemeans: “keep pulling cards from the basket in whatever order the basket gives them.”delete(table, key)means: “remove this card from the basket.”
If you remove a card that you already pulled, no problem. If you remove a card that is still in the basket waiting to be pulled, it simply will not be pulled later.
What you should not imagine is a neatly ordered list. A Go map is not a line of items with fixed positions. It is more like a shuffled container. That is why deletion is fine, but order is unpredictable.
Syntax and Examples
The basic deletion syntax is:
delete(myMap, key)
If the key exists, it is removed.
If the key does not exist, delete does nothing.
Deleting selected keys while ranging
package main
import "fmt"
func main() {
scores := map[string]int{
"alice": 90,
"bob": 40,
"carl": 75,
"dina": 30,
}
for name, score := range scores {
if score < 50 {
delete(scores, name)
}
}
fmt.Println(scores)
}
This safely removes entries where the score is below 50.
Deleting everything from a map
for key := range myMap {
delete(myMap, key)
}
This is a valid way to clear a map entry by entry.
Step by Step Execution
Consider this example:
package main
import "fmt"
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
for k, v := range m {
fmt.Println("visiting", k, v)
if v%2 == 1 {
fmt.Println("deleting", k)
delete(m, k)
}
}
fmt.Println("final:", m)
}
A possible run might behave like this:
- The loop starts iterating over
m. - Suppose Go visits
bfirst.- Prints
visiting b 2 2 % 2 == 0, so nothing is deleted.
- Prints
- Suppose Go visits
anext.- Prints
visiting a 1
- Prints
Real World Use Cases
Deleting map keys during iteration is useful in many real programs.
Removing expired cache entries
for key, item := range cache {
if item.Expired() {
delete(cache, key)
}
}
Filtering invalid API results
After loading a set of objects into a map, you may remove entries that fail validation.
for id, user := range users {
if user.Email == "" {
delete(users, id)
}
}
Cleaning feature flags or configuration
for name, enabled := range flags {
if !enabled {
delete(flags, name)
}
}
Removing completed jobs from an in-memory tracker
for id, job := range jobs {
if job.Done {
delete(jobs, id)
}
}
Pruning temporary session data
Maps are often used for in-memory session stores. Expired sessions can be deleted while scanning the map.
Real Codebase Usage
In real Go codebases, developers commonly use deletion during map iteration in simple cleanup passes.
Pattern: in-place filtering
Instead of building a second map, you can keep the original one and remove unwanted entries.
for key, value := range m {
if shouldRemove(value) {
delete(m, key)
}
}
This is useful when:
- you want to save memory
- the map is already owned by the current function
- preserving the original map is not required
Pattern: guard clause before delete
for key, value := range m {
if value == nil {
delete(m, key)
continue
}
if value.IsInvalid() {
delete(m, key)
}
}
This keeps cleanup logic clear and local.
Pattern: validation and normalization pass
A map may be populated first, then cleaned.
for key, cfg := range configs {
if cfg.Name == "" {
delete(configs, key)
}
}
Pattern: collect keys first when logic is complex
Common Mistakes
Mistake 1: Thinking map iteration order is fixed
Broken assumption:
for k := range m {
fmt.Println(k) // expecting alphabetical or insertion order
}
Avoid this by remembering:
- Go map iteration order is unspecified.
- If you need order, copy keys to a slice and sort them.
Mistake 2: Confusing deletion safety with concurrent safety
This is a very common misunderstanding.
Broken idea:
// Goroutine 1
for k := range m {
delete(m, k)
}
// Goroutine 2
fmt.Println(m["x"])
This is unsafe without synchronization if both access the map at the same time.
Avoid this by using:
sync.Mutexsync.RWMutexsync.Mapwhen appropriate- channel-based ownership patterns
Mistake 3: Expecting inserted items to behave predictably during the same loop
for k := range m {
m[] =
fmt.Println(k)
}
Comparisons
Operation during range on a Go map | Safe? | Notes |
|---|---|---|
| Delete current key | Yes | Idiomatic and supported |
| Delete another key in the same map | Yes | If not yet reached, it will not be visited later |
| Insert a new key | Allowed, but result is not predictable for that iteration | Do not rely on whether it appears |
| Read values | Yes | Normal use in a single goroutine |
| Concurrent read/write from multiple goroutines | No | Requires synchronization |
Delete in place vs build a new map
| Approach | Best when | Trade-off |
|---|---|---|
| Delete during iteration |
Cheat Sheet
// Delete one key
delete(m, key)
// Delete matching keys while ranging
for k, v := range m {
if shouldRemove(v) {
delete(m, k)
}
}
// Clear a map
for k := range m {
delete(m, k)
}
Key rules:
- Deleting from a Go map during
rangeis safe. - Deleted entries not yet reached will not be visited later.
- Map iteration order is not guaranteed.
- Inserting during iteration is allowed, but do not rely on whether new entries appear.
- Plain maps are not safe for concurrent read/write access.
delete(m, missingKey)is valid and does nothing.
When to use this pattern:
- remove expired items
- filter invalid entries
- clean up in-memory state
- clear a map
When not to rely on it:
- when you need sorted or stable ordering
- when multiple goroutines touch the same map without locking
FAQ
Can you delete from a map while iterating in Go?
Yes. Go allows deleting keys from a map during a for ... range loop over that same map.
What happens if a key is deleted before the loop reaches it?
It will not be produced later in that iteration.
Is deleting the current key safe in a range loop?
Yes. Deleting the key currently being visited is safe.
Can I add keys to a map during iteration?
You can, but the iteration behavior for newly added keys is not something you should depend on.
Does deleting while ranging preserve any order?
No. Go maps do not have guaranteed iteration order.
How do I remove all keys from a map?
A common pattern is:
for k := range m {
delete(m, k)
}
Is this safe with goroutines?
Not by itself. Regular Go maps need synchronization if multiple goroutines read and write at the same time.
Should I delete in place or create a new map?
Delete in place if you want to mutate the existing map simply. Build a new map if you need to keep the original or want a clearer transformation pipeline.
Mini Project
Description
Build a small Go program that manages an in-memory session store. Each session has a user ID and an Expired flag. Your task is to remove expired sessions by deleting entries directly from the map while iterating over it. This demonstrates the safe and idiomatic Go pattern of deleting map entries during a range loop.
Goal
Create a session cleanup program that removes expired sessions from a map and prints the remaining active sessions.
Requirements
- Create a
Sessionstruct with at least a user ID and an expired flag. - Store several sessions in a
map[string]Session. - Iterate over the map and delete sessions whose
Expiredfield istrue. - Print the sessions before cleanup and after cleanup.
Keep learning
Related questions
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.