You start building an inventory and end up with Item1, Item2, Item3. You start managing enemies and create Enemy1, Enemy2. Every time the count grows, so do your variables and nodes, and by the tenth one it's unbearable.
This is what containers are for. Blueprints have three of them: Array, Set, and Map , all of which hold multiple values of the same type. The three differ in exactly three ways: whether they keep order, whether they allow duplicates, and whether you can look values up by key . This article covers how to choose between them, their main nodes, and the "modifying an array while looping over it" problem that nearly every beginner hits.
What You'll Learn
- Array = ordered / Set = no duplicates / Map = look up by key
- Containers are chosen with the toggle button to the right of a variable's type
- The main Array nodes, and how to fix Index out of bounds
- Why you must not modify an array while looping over it
- Hands-on: an inventory that tracks item IDs and quantities with a Map
- How the Three Containers Differ
- Switching Containers Next to the Type
- The Main Array Nodes
- For Each Loop and Modifying During a Loop
- Set: The Container With No Duplicates
- Map: The Container You Look Up by Key
- Hands-On: Building an Inventory That Counts Quantities
- Bonus: Good to Know for Later
- Summary
How the Three Containers Differ
All three hold multiple values. What differs is the questions they can answer .
| Container | How it stores | Questions it's good at | What it's bad at |
|---|---|---|---|
| Array | In order. Duplicates allowed | "What's the third one?" "How many are there?" | Searching a large number of elements for "do I have this?" is slow |
| Set | No duplicates. No order | "Do I have this?" | There is no "third one" |
| Map | Key-value pairs. No order | "What's the value for this key?" | Can't hold the same key twice |

Put in terms of games, the choice gets pretty clear.
- Array: a log of pickups in order, party member order, patrol waypoint order. Things where the order itself carries meaning
- Set: IDs of items already collected, unlocked stages, monsters registered in a bestiary. Things you don't want registered twice
- Map: item ID → quantity, player name → score, setting name → value. Things you want to look up by some handle
When in doubt, ask yourself: "do I want to line these up by number, or look them up by name?" By number means Array, by name means Map, and if it's neither because you only need to know whether something is present, that's a Set.
Switching Containers Next to the Type
In Blueprints, containers aren't separate types. They are a "way of holding" an existing type.
When you create a variable in the My Blueprint panel, the Details panel has a Variable Type field. To the right of the type dropdown is a small button where you pick one of four options.
| Option | Meaning |
|---|---|
| Single Variable | An ordinary variable (one value) |
| Array | An array |
| Set | A set |
| Map | A dictionary. Picking it adds a second field for the value type |

Only Map gives you two fields. The left one is the key type and the right one is the value type. For "item ID (Name) → quantity (Integer)," set Name on the left and Integer on the right.
Some types can't be keys. Set elements and Map keys are hashed internally for fast lookups, so only hashable types can be used . Integer, Float, Name, String, Enum, and object references work, but Text and most structs cannot be keys and will produce a compile error. Using Name rather than Text for item IDs is the standard practice.
The Main Array Nodes
Arrays come up far more often than the other two. Drag an array variable into the graph and pull a wire off its pin to see the available nodes.

| Node | What it does | Watch out for |
|---|---|---|
| Add | Appends to the end | Returns the index it was added at |
| Insert | Inserts at a given index | Everything after it shifts back by one |
| Remove Index | Removes by index | Everything after it shifts forward by one |
| Remove Item | Removes by value | Removes every match |
| Get (a copy) | Retrieves by index | Out-of-range indices cause an error |
| Set Array Elem | Overwrites at a given index | Out of range is an error here too |
| Length | Returns the element count | 0 when empty |
| Last Index | Returns the last index (Length - 1) | -1 for an empty array |
| Contains Item | Returns a bool for whether it's present | Searches from the start, so it's slow with many elements |
| Find Item | Returns the index it found | Returns -1 when not found |
| Is Valid Index | Returns a bool for whether an index is usable | Use it as a safety check before Get |
Indices start at 0 . With three elements, the usable indices are 0, 1, and 2, Length is 3, and Last Index is 2.
The most common stumble here is this error in the Output Log.
Blueprint Runtime Error: "Attempted to access index 3 from array 'Inventory' of length 3!"
It means "you tried to access index 3 of an array holding 3 items." The number of elements and the highest usable index are always off by one. Using Length directly as an index guarantees this error.
There are two ways to prevent it.
- Check with Is Valid Index before Get: use this when the index comes from outside (a UI click position, a value from save data, and so on)
- Use Last Index: if you just want "the last element," use
Last Indexinstead of computingLength - 1yourself
What you Get is a copy. When an array holds structs, retrieving one with
Getand modifying it does not change the original array. UseSet Array Elemto write it back. This is an easy place to get stuck, so the struct article covers the mechanism and the fix in detail.
For Each Loop and Modifying During a Loop
To process an array one element at a time, use For Each Loop . Pull off the array pin and it shows up in the list.
| Pin | Contents |
|---|---|
| Loop Body | Executes once per element |
| Array Element | The current element |
| Array Index | The current index |
| Completed | Executes once after everything is done |
To stop early, use For Each Loop with Break . Sending execution into the Break pin ends the loop at that point. It's handy when you want to stop at the first match.
And now for the main point. Never add to or remove from the array you are looping over inside a For Each Loop.

The reason is clear once you look at the mechanism. A For Each Loop simply walks the index up from 0 one at a time . Say you have A, B, C, D and you remove B at index 1. Now C moves to index 1 and D moves to index 2. The loop looks at index 2 next, so C, which moved to index 1, is never processed .
"Remove enemies whose HP is 0 from the array" is exactly this shape. The symptom is that only half of them get removed.
There are three ways to handle it.
- Collect the removals in a separate array: during the loop, just add to
RemoveTargets, thenRemove Itemthem all afterCompleted. This is the most straightforward and readable option - Iterate backwards: use
Reverse for Loopto walk fromLast Indexdown to 0. If you remove from the back, the indices of earlier elements never move - Build a new array from what you keep:
Addonly the elements that pass your condition into a separate array, then swap it into the original variable at the end
The same applies to adding. Calling Add during a loop can keep extending Last Index, which means the loop never finishes .
Set: The Container With No Duplicates
A Set is a container that cannot hold the same value twice . Calling Add with a value that's already there doesn't increase the count. It isn't an error either, it's just quietly ignored.
| Node | What it does |
|---|---|
| Add | Adds a value. Does nothing if it's already there |
| Remove | Removes a value |
| Contains | Returns a bool for whether it's present |
| Length | Returns the element count |
| To Array | Converts to an array (use this when you want to loop) |
Sets are a good fit when you only need to know whether you have something .
- IDs of items already collected (opening the same chest twice doesn't add another)
- IDs of unlocked stages
- IDs of monsters registered in the bestiary
- IDs of NPCs you've already talked to
The value of a Set is that you don't have to write duplicate-prevention logic . Doing the same thing with an Array means two steps every time: check with Contains Item, then Add.
A Set has no indices . You can't retrieve Get(0), and the order isn't guaranteed. When you want to process elements one by one, convert it with To Array and loop over that.
Map: The Container You Look Up by Key
A Map holds key-value pairs . It's the same thing as TMap in C++.
| Node | What it does | Watch out for |
|---|---|---|
| Add | Registers a key and value | Overwrites if the key already exists |
| Find | Retrieves the value for a key | Returns the value plus a bool for whether it was found |
| Contains | Returns a bool for whether the key exists | |
| Remove | Removes by key | |
| Keys | Returns the list of keys as an array | Use this when you want to loop |
| Values | Returns the list of values as an array | |
| Length | Returns the number of pairs |

Where a Map shines most is counting things . One Map<Name, Integer> answers "how many herbs do I have?" instantly. Doing the same with Arrays means keeping an array of item names and an array of counts in sync, and the moment they drift apart everything falls over.
There are two things to keep in mind with Maps.
First, Add overwrites. It is not "do nothing if it already exists." To increase a count you need to Find the current value, add 1, and put it back with Add . We build exactly that in the hands-on section.
Second, order is not guaranteed. The array you get from Keys isn't necessarily in insertion order, and removing elements can rearrange it. If order matters, keep a separate Array for display order , or Sort the result of Keys before using it.
What Find returns is also a copy. When the value type is a struct, modifying what you got from Find does not change the original Map. Putting it back with
Addunder the same key is the reliable approach (see the struct article).
Hands-On: Building an Inventory That Counts Quantities
RPG inventories, roguelike items on the floor, survival game crafting materials. "Pick things up and count them" shows up regardless of genre. Here we'll record the same pickups in all three containers at once, so you can see with your own eyes that each one answers a different question .
What You'll See
After picking up herbs three times and a potion once, the three containers look like this.

| Container | Contents | Question it answers |
|---|---|---|
PickupLog (Array) | Herb, Herb, Herb, Potion (4 entries) | "What did I pick up third?" |
FoundItems (Set) | Herb, Potion (2 entries) | "Have I ever seen a potion?" |
Inventory (Map) | Herb→3, Potion→1 | "How many herbs do I have?" |
Repro Setup
Create a new project from the Third Person template , open BP_ThirdPersonCharacter, and create the following variables. All of them start empty .
| Variable Name | Type | Container | Default Value |
|---|---|---|---|
PickupLog | Name | Array | (empty) |
FoundItems | Name | Set | (empty) |
Inventory | Name → Integer | Map | (empty) |
You switch containers with the button to the right of Variable Type in the Details panel. Choosing Map for Inventory gives you two fields, so set Name on the left and Integer on the right .
Building the PickupItem Function
Create a new PickupItem function under Functions in the My Blueprint panel, and add one input pin.
| Input Pin | Type |
|---|---|
ItemID | Name |
The body just records into the three containers in turn.

Function: PickupItem (Input: ItemID / Name)
→ Add (Target: PickupLog, New Item: ItemID) ← append to the Array in pickup order
→ Add (Target: FoundItems, New Item: ItemID) ← add to the Set (duplicates are ignored)
→ Find (Target: Inventory, Key: ItemID)
Value → into A of Add (A: Value, B: 1)
Found? → unused (when not found, Value is 0, so +1 simply gives 1)
→ Add (Target: Inventory, Key: ItemID, Value: the sum above) ← same key, so it overwrites
The important part is the last three lines. Find the current count, add 1, put it back under the same key. Because a Map's Add overwrites, this single chain covers both "increment if present" and "create with 1 if absent."
When Find doesn't find anything, it returns Integer's default value of 0 . Adding 1 to that gives 1, so you never need a branch for "is this the first time?"
Calling It and Checking
Call it four times in the Event Graph and print the results.
Event BeginPlay
→ PickupItem (ItemID: Herb)
→ PickupItem (ItemID: Herb)
→ PickupItem (ItemID: Herb)
→ PickupItem (ItemID: Potion)
→ Length (Target: PickupLog) → Print String (Append("Log: ", ToString))
→ Length (Target: FoundItems) → Print String (Append("Found: ", ToString))
→ Find (Target: Inventory, Key: Herb)
Value → Print String (Append("Herb: ", ToString))
Hit Play and the top left of the screen shows this.
Log: 4
Found: 2
Herb: 3
Four pickups means the Array has 4 , two distinct kinds means the Set has 2 , and three herbs means the Map's value is 3 . The point of this exercise is that the same four calls produce three different answers .
Now add one more PickupItem (Herb). You get Log 5, Found still 2, and Herb 4 . Only the Set refuses to grow, which is precisely what a Set is.
If something isn't working, here's how to narrow it down.
- Herb is always 1 → you're feeding 1 straight into
Addinstead of adding it to the result of Find - Herb stays 1 even on the fourth pickup → you're writing to a different key rather than through the Map's
Add. Since the key is a Name, also suspect a typo betweenHerbandherb - Found is 4 →
FoundItemsis an Array rather than a Set. Check the container setting in the Details panel - Log stays 0 → the Target of the
AddforPickupLogis wired to a different variable - Print String shows nothing → the execution pins in
PickupItemaren't connected all the way through (see Debugging with Print String)
Two things to take away.
- Pick a container based on the question you want to answer: even with identical contents, an Array is only fast at order, a Set at presence, and a Map at correspondence. Write down in one sentence what you'll ask of the data before you build it, and the choice makes itself
- To count things, use Map's Find → +1 → Add: this three-node shape works unchanged for item quantities, kill counts, score tallies, and every other kind of counting. If you take away one thing, take this
Once your items grow to include "name, attack power, and icon," it's time to make the value a struct. Build S_Item in the struct article and you can use it directly as a Data Table row.
Bonus: Good to Know for Later
- Contains Item gets slower in proportion to element count: an Array's
Contains Itemcompares from the start, one by one. A few dozen entries won't bother you, but if you're checking thousands every frame, switch to a Set or Map .Containson a Set or Map answers in roughly constant time no matter how many entries there are - Arrays can be filled in ahead of time: you can populate elements in advance via Default Value in the Details panel. For fixed counts like "five patrol points," setting them up in the level is easier to verify than calling
Addat runtime - Maps pair well with saving: "item ID → quantity" is already the shape of save data. Put a Map in a SaveGame object variable and it saves as-is (see Implementing Save/Load)
- Fixed data belongs in a DataTable: data that's decided at design time , like "the list of all items," is easier to manage in a DataTable than typed by hand into a Blueprint array (see Data Table 101 and Data-Driven Design with Data Assets)
- Don't forget the empty case: calling
Get(0)on an array whoseLengthis 0 causes an error. For logic like "target the first enemy," always pair it with a Length > 0 check . This kind of slip is also covered in Common Blueprint Mistakes
Summary
| Question | Container to pick |
|---|---|
| Does order carry meaning? | Array |
| Do you want no duplicates, or only need presence? | Set |
| Do you want to look things up by some handle? | Map |
- Switch containers with the button to the right of the variable's type . Only Map gets two fields
- Array indices start at 0 .
Lengthand the highest index are off by one - Never modify the array you're looping over. To remove, collect in a separate array or iterate backwards
- A Map's
Addoverwrites . To increment, use Find → +1 → Add
Once you can hold multiple pieces of data, the next thing to organize is state . Before you drown in bools, head to Managing State with Enums and Switch.
Take a look at the Blueprint you're working on right now. Are there variables whose names differ only by a trailing number?