Question

Deleting Keys from a Map During range in Go

godictionaryfor-loop

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 over m.
  • 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.

  • range means: “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.

Take Quiz

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.

Your original pattern

for key, value := range table {
    fmt.Printf("deleting %v => %v\n", key, value.value)
    delete(table, key)
}

This is safe. The loop will continue until iteration is complete, and the map will end up empty.

Deleting with a condition

for key, info := range table {
    if info.value == "3" || info.value == "7" {
        delete(table, key)
    }
}

This is the common real-world pattern: inspect each entry and remove only the ones you do not want.

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:

  1. The loop starts iterating over m.
  2. Suppose Go visits b first.
    • Prints visiting b 2
    • 2 % 2 == 0, so nothing is deleted.
  3. Suppose Go visits a next.
    • Prints visiting a 1
    • 1 % 2 == 1, so delete(m, "a")
  4. Suppose Go visits c next.
    • Prints visiting c 3
    • 3 % 2 == 1, so delete(m, "c")
  5. Loop ends.
  6. Final map contains only b.

Important details:

  • The visit order could be c, a, b or some other order.
  • The deletion itself is safe.
  • If a key is deleted before the iterator reaches it, it will not appear later in the loop.

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.

Take Quiz

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

If deletion conditions are complicated, or if you need logging, metrics, or stable ordering, teams often collect keys first.

var toDelete []string
for key, value := range m {
    if shouldRemove(value) {
        toDelete = append(toDelete, key)
    }
}

for _, key := range toDelete {
    delete(m, key)
}

This is not required for safety, but it can improve readability or control.

Important real-world caveat: concurrent access

Deleting during range is safe within a single goroutine's normal use, but plain Go maps are not safe for concurrent read/write access without synchronization. If one goroutine iterates while another modifies the map, that is a different problem and can panic.

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.Mutex
  • sync.RWMutex
  • sync.Map when appropriate
  • channel-based ownership patterns

Mistake 3: Expecting inserted items to behave predictably during the same loop

for k := range m {
    m["new"] = 123
    fmt.Println(k)
}

The newly added key may or may not appear during that iteration. Do not rely on it.

Mistake 4: Rebuilding the map when simple deletion is enough

Some beginners write extra code unnecessarily:

filtered := make(map[string]int)
for k, v := range m {
    if v >= 0 {
        filtered[k] = v
    }
}
m = filtered

This is fine if you want a new map, but if you just want to remove entries, this is simpler:

for k, v := range m {
    if v < 0 {
        delete(m, k)
    }
}

Mistake 5: Forgetting that delete on a missing key is allowed

Beginners sometimes add unnecessary existence checks:

if _, ok := m[key]; ok {
    delete(m, key)
}

Usually this can just be:

delete(m, key)

Comparisons

Operation during range on a Go mapSafe?Notes
Delete current keyYesIdiomatic and supported
Delete another key in the same mapYesIf not yet reached, it will not be visited later
Insert a new keyAllowed, but result is not predictable for that iterationDo not rely on whether it appears
Read valuesYesNormal use in a single goroutine
Concurrent read/write from multiple goroutinesNoRequires synchronization

Delete in place vs build a new map

ApproachBest whenTrade-off
Delete during iterationYou want to mutate the existing mapSimple and memory-efficient
Build a new filtered mapYou want to preserve the original or make logic clearerUses extra memory

Map vs slice removal

CollectionRemoval during iterationNotes
MapSafe to delete while rangingOrder is unspecified
SliceMore careful handling neededRemoving shifts indexes
Take Quiz

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 range is 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.

Take Quiz

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

  1. Create a Session struct with at least a user ID and an expired flag.
  2. Store several sessions in a map[string]Session.
  3. Iterate over the map and delete sessions whose Expired field is true.
  4. Print the sessions before cleanup and after cleanup.
Take Quiz

Keep learning

Related questions

View more