Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Blog
  3. Godot Patterns
  4. GDScript Dictionary map() and map_in_place
Blog/Godot Patterns/GDScript Dictionary map() and map_in_place
Godot PatternsFeatured

GDScript Dictionary map() and map_in_place

Practical patterns for mapping GDScript dictionaries in Godot 4: for-loops, in-place updates, keys().map(), and reusable helpers.

SyntaxCacheFebruary 11, 202612 min read
godotgdscriptdictionariesfunctional-programming

If you came here looking for Dictionary.map() or Dictionary.map_in_place() in GDScript — neither exists. Array got .map(), .filter(), .reduce(), .any(), and .all() when GDScript was rewritten for Godot 4.0—part of a complete language overhaul that touched every built-in type. Dictionary got none of them. And map_in_place() isn't a real method on any Godot type — if an AI coding assistant told you it was, it hallucinated.

This post covers how to map over dictionaries today (including in-place mutation), plus the Dictionary methods that come up most in game code. The short version: use a for loop to build a new Dictionary, mutate values in place with for key in dict: dict[key] = ..., or use dict.keys().map() when you only need an Array back.

Does GDScript have Dictionary.map() or map_in_place()?

No to both. Here's the full gap:

MethodArrayDictionary
.map()YesNo
.map_in_place()NoNo
.filter()YesNo
.reduce()YesNo
.any()YesNo
.all()YesNo

map_in_place() (sometimes written mapinplace or map_inplace) doesn't exist on any Godot type. If you searched for it after an AI assistant suggested my_dict.map_in_place(func(k, v): ...), you're not alone — it's one of the most common GDScript hallucinations. The method was never part of the engine.

If you're coming from JavaScript or Python, the missing .map() feels like an oversight. Object.entries().map() and dict comprehensions are so natural in those languages that you don't think about them. GDScript's Dictionary just doesn't have functional methods yet.

There's an open proposal (godot-proposals #7973) to add them. Until it ships, you've got three options, all based on the Array functional methods GDScript already has.

The for-loop pattern (idiomatic GDScript)

This is what most Godot codebases use. It's explicit, easy to step through in the debugger, and readable by anyone who knows GDScript.

Say you have weapon stats and want to apply a damage multiplier buff:

var weapon_stats := {"sword": 10, "bow": 7, "staff": 5}
var multiplier := 1.5

var buffed := {}
for weapon in weapon_stats:
    buffed[weapon] = weapon_stats[weapon] * multiplier

print(buffed)
# {"sword": 15.0, "bow": 10.5, "staff": 7.5}
for key in dict iterates keys

for x in my_dict: gives you keys, not values, not key-value pairs. This trips up Python developers who expect .items() tuple unpacking. GDScript doesn't have that. Use my_dict[key] inside the loop to get the value.

You can iterate values directly if you don't need the keys:

for damage in weapon_stats.values():
    print(damage)  # 10, 7, 5

Or transform both keys and values in one pass:

var display_stats := {}
for weapon in weapon_stats:
    var label := weapon.capitalize()
    display_stats[label] = "DMG: %d" % weapon_stats[weapon]

print(display_stats)
# {"Sword": "DMG: 10", "Bow": "DMG: 7", "Staff": "DMG: 5"}

The for-loop also handles filtering while mapping, which the other approaches can't do as cleanly:

var enemies := {
    "goblin": {"hp": 0, "name": "Goblin"},
    "orc": {"hp": 45, "name": "Orc Warrior"},
    "slime": {"hp": 12, "name": "Blue Slime"},
}

# Only living enemies, mapped to just their names
var alive := {}
for id in enemies:
    if enemies[id]["hp"] > 0:
        alive[id] = enemies[id]["name"]

print(alive)
# {"orc": "Orc Warrior", "slime": "Blue Slime"}

Not glamorous, but it handles everything. When in doubt, use this.

Mapping a dictionary in place

The examples above all create a new dictionary. If you want to transform values without allocating a second dictionary — the thing map_in_place() would do if it existed — just assign back to the same keys:

var enemy_hp := {"goblin": 30, "orc": 80, "dragon": 200}

# Double all HP values in place — no new dictionary created
for enemy in enemy_hp:
    enemy_hp[enemy] = enemy_hp[enemy] * 2

print(enemy_hp)
# {"goblin": 60, "orc": 160, "dragon": 400}

This is safe. Modifying values during iteration works fine in GDScript. What breaks is adding or removing keys during iteration (see the gotcha below).

You can wrap this into a helper if you use the pattern often:

## Transforms every value in the dictionary in place.
## The callable receives (key, value) and should return the new value.
static func map_in_place(dict: Dictionary, transform: Callable) -> void:
    for key in dict:
        dict[key] = transform.call(key, dict[key])

Usage:

var prices := {"sword": 100, "shield": 60, "potion": 25}

# 20% discount on everything, mutates prices directly
DictUtils.map_in_place(prices, func(_item, price): return int(price * 0.8))

print(prices)
# {"sword": 80, "shield": 48, "potion": 20}

When to use in-place vs. new dictionary: Mutate in place when you own the data and no other code holds a reference to the old values. Create a new dictionary when you need the original unchanged (undo systems, comparing before/after, passing data to functions that shouldn't see the mutation).

The keys().map() trick

Dictionary.keys() returns an Array, and Arrays do have .map(). So you can get partway to functional style:

var inventory := {"health_potion": 3, "mana_potion": 1, "bomb": 5}

var labels := inventory.keys().map(
    func(item): return "%s x%d" % [item.capitalize(), inventory[item]]
)

print(labels)
# ["Health Potion x3", "Mana Potion x1", "Bomb x5"]

This works because the lambda captures inventory from the outer scope. GDScript closures close over local variables, so the inner function can read the dictionary even though it only receives the key as an argument.

This returns an Array, not a Dictionary

keys().map() gives you a flat Array. The key-value structure is gone. Fine for building display strings or feeding into a UI list, but useless if you need a Dictionary back.

Filtering works the same way:

# Items with quantity 3 or more
var stocked := inventory.keys().filter(
    func(item): return inventory[item] >= 3
)

print(stocked)  # ["health_potion", "bomb"]

The mental model: keys().map() is for when you want a list out of a dictionary. If you need a dictionary out of a dictionary, use the for-loop.

A reusable map_dict() helper

If you're writing the same for-loop across multiple scripts, a utility function cuts the repetition:

# dict_utils.gd — register as autoload "DictUtils"

## Returns a new Dictionary with each value transformed by the callable.
## The callable receives (key, value) and should return the new value.
static func map_dict(dict: Dictionary, transform: Callable) -> Dictionary:
    var result := {}
    for key in dict:
        result[key] = transform.call(key, dict[key])
    return result


## Returns a new Dictionary containing only entries where predicate returns true.
## The predicate receives (key, value).
static func filter_dict(dict: Dictionary, predicate: Callable) -> Dictionary:
    var result := {}
    for key in dict:
        if predicate.call(key, dict[key]):
            result[key] = dict[key]
    return result
Put helpers in an autoload

Register this script as an autoload named DictUtils in Project Settings > Autoload. Then DictUtils.map_dict() and DictUtils.filter_dict() are available everywhere without preload() or const boilerplate.

Here's both helpers in action. Say you have a shop's loot table and want to show the player what they can afford, with a discount applied:

var loot_table := {
    "iron_sword": 120,
    "health_potion": 25,
    "dragon_scale": 500,
    "leather_boots": 80,
}
var player_gold := 150

# What can the player buy right now?
var affordable := DictUtils.filter_dict(
    loot_table,
    func(_item, price): return price <= player_gold
)
print(affordable)
# {"iron_sword": 120, "health_potion": 25, "leather_boots": 80}

# Apply a 20% holiday discount to everything
var on_sale := DictUtils.map_dict(
    loot_table,
    func(_item, price): return int(price * 0.8)
)
print(on_sale)
# {"iron_sword": 96, "health_potion": 20, "dragon_scale": 400, "leather_boots": 64}

# Chain: discount first, then filter to affordable
var deals := DictUtils.filter_dict(
    on_sale,
    func(_item, price): return price <= player_gold
)
print(deals)
# {"iron_sword": 96, "health_potion": 20, "leather_boots": 64}

You could keep going with reduce_dict() or map_keys(), but I'd stop here. Two helpers cover most cases, and past that point the for-loop is probably clearer than a chain of utility calls. Write helpers for patterns you repeat; don't build a library nobody asked for.

Dictionary patterns you'll use more often

These aren't .map() substitutes, but they solve the problems that lead people to search for it in the first place.

Safe access with .get()

Direct key access on a missing key returns null and logs an error. That's survivable on its own, but chained access on that null will crash:

# stats["weapon"] returns null if missing, then ["damage"] fails on null
var damage = stats["weapon"]["damage"]

# Safe — returns 0 if any key is missing
var damage = stats.get("weapon", {}).get("damage", 0)

Use .get() anywhere you touch data that might not exist: save files, network responses, config dictionaries. Chaining .get() with an empty dict default is the GDScript equivalent of optional chaining.

Merging dictionaries

merge() combines two dictionaries. The second argument controls whether existing keys get overwritten:

# Apply user preferences over defaults
var defaults := {"volume": 0.8, "fullscreen": false, "vsync": true}
var user_prefs := {"volume": 0.5, "fullscreen": true}

defaults.merge(user_prefs, true)  # true = overwrite existing keys
print(defaults)
# {"volume": 0.5, "fullscreen": true, "vsync": true}

merge() replaces values wholesale. For additive stats where you want to sum values instead of overwriting, you still need a loop:

var base_stats := {"hp": 100, "attack": 10, "defense": 5}
var gear_bonus := {"attack": 3, "defense": 7, "crit": 0.05}

for stat in gear_bonus:
    base_stats[stat] = base_stats.get(stat, 0) + gear_bonus[stat]

print(base_stats)
# {"hp": 100, "attack": 13, "defense": 12, "crit": 0.05}

Check before mutating with .has()

if inventory.has("health_potion"):
    inventory["health_potion"] -= 1
    if inventory["health_potion"] <= 0:
        inventory.erase("health_potion")

GDScript has no try/except, so you can't catch a missing-key error after the fact. Check with .has() before modifying and use .get(key, default) when reading.

Duplicating before you transform

If you need to transform a dictionary without touching the original, .duplicate() makes a copy:

var original := {"hp": 100, "attack": 10}
var modified := original.duplicate()
modified["attack"] = 25

print(original["attack"])  # 10 — unchanged
print(modified["attack"])  # 25

Pass true for a deep copy if your dictionary contains nested dictionaries or arrays. Without it, inner collections are shared between the original and the copy, so mutating one mutates both.

var nested := {"stats": {"hp": 100}}

var shallow := nested.duplicate()       # inner dict is shared
var deep := nested.duplicate(true)      # inner dict is independent

shallow["stats"]["hp"] = 0
print(nested["stats"]["hp"])  # 0 — oops, shared reference

deep["stats"]["hp"] = 999
print(nested["stats"]["hp"])  # still 0 — deep copy is independent

Godot 4.5 added duplicate_deep() for finer control over recursive duplication of nested resources and sub-resources. If you're working with deeply nested data that includes Resource objects, it can be more predictable than duplicate(true):

var deep := nested.duplicate_deep()

Which pattern for which job

I want to...Use
Transform every value (new dict)For-loop or map_dict() helper
Transform every value (in place)For-loop or map_in_place() helper
Keep only some entriesFor-loop or filter_dict() helper
Build a display list from keys + valueskeys().map()
Combine two dictionaries (overwrite).merge(other, true)
Add numeric values from two dictsFor-loop with .get(key, 0)
Read a key that might be missing.get(key, default)
Remove an entry conditionally.has() + .erase()
Transform without mutating the original.duplicate() + for-loop

Gotchas that waste real debugging time

Don't erase keys while iterating

This is tempting but unsafe. Modifying a dictionary's keys during iteration produces unpredictable results:

# BAD — may skip entries or crash
for item in inventory:
    if inventory[item] <= 0:
        inventory.erase(item)

Collect keys first, then erase in a separate pass:

var to_remove: Array[String] = []
for item in inventory:
    if inventory[item] <= 0:
        to_remove.append(item)

for item in to_remove:
    inventory.erase(item)

Dictionaries preserve insertion order

GDScript dictionaries maintain the order keys were inserted. This is useful for stable UI lists and predictable iteration, but don't rely on it for game logic. If ordering matters, sort explicitly or use an Array of keys.

Typed dictionaries (Godot 4.4+)

Godot 4.4 added typed dictionaries—one of the most requested GDScript features. If you're on 4.4 or later, you can declare the key and value types:

var costs: Dictionary[String, int] = {
    "iron_sword": 120,
    "health_potion": 25,
}

The editor will catch wrong-type assignments at parse time. Typed dictionaries behave identically at runtime, with no performance difference. Just earlier error detection. (Tested through Godot 4.6.1-stable.)

FAQ

Does Dictionary.map() exist in GDScript?

No. Array has .map(), .filter(), .reduce(), .any(), and .all(). Dictionary has none of these. Use a for-loop to build a new dictionary, or call dict.keys().map() if you only need an Array back.

Does Dictionary.map_in_place() or mapinplace exist?

No. map_in_place() is not a method on Dictionary, Array, or any other Godot class. It has never existed in any version of Godot 4 (through 4.6.1). This method is frequently hallucinated by AI coding assistants. To transform dictionary values in place, use for key in dict: dict[key] = new_value.

How do I filter a Dictionary in GDScript?

Loop over keys and copy only the entries that match your condition into a new dictionary. Or use the filter_dict() helper shown above.

Can I iterate keys and values together?

Not with a single for statement. GDScript doesn't support for key, value in dict: (there's a proposal for it). For now, iterate keys and look up values with dict[key].

Is it safe to delete keys while looping?

No. Collect the keys to remove first, then erase them after the loop finishes. See the gotcha above.


Want to practice GDScript syntax? We have exercises covering GDScript foundations and arrays and loops, with more dictionary drills on the way.


References

  • Godot 4 Dictionary class reference — full list of Dictionary methods
  • Godot 4 Array class reference — where .map(), .filter(), .reduce() live
  • GDScript basics: Dictionaries — official language guide
  • Proposal #7973: Add functional methods to Dictionary — community request for Dictionary.map()

Related Posts

Facade Pattern in Godot 4 GDScript: Taming "End Turn" Spaghetti12 min readHow to Remember Programming Syntax Without Re-reading Docs10 min readWhy Vibe Coding Slows Down Experienced Developers9 min read
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.