[UE5] Blueprint Arrays, Sets, and Maps: Choosing the Right Container

Created: 2026-07-20

When one variable isn't enough, containers take over. A visual guide to the differences between Array (ordered), Set (no duplicates), and Map (look up by key) plus their main nodes, with a hands-on inventory that counts item quantities with a Map.

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.

Three containers, Array, Set, and Map, shown side by side

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

Sponsored

How the Three Containers Differ

All three hold multiple values. What differs is the questions they can answer .

ContainerHow it storesQuestions it's good atWhat it's bad at
ArrayIn 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
SetNo duplicates. No order"Do I have this?"There is no "third one"
MapKey-value pairs. No order"What's the value for this key?"Can't hold the same key twice
A comparison of the three containers: an Array as numbered shelves, a Set as a box that rejects duplicates, and a Map as labeled drawers

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.

OptionMeaning
Single VariableAn ordinary variable (one value)
ArrayAn array
SetA set
MapA dictionary. Picking it adds a second field for the value type
The container toggle button to the right of a variable's type dropdown, showing the four options

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.

Sponsored

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.

A quick-reference chart of the main nodes, Add, Insert, Remove, Get, Length, Contains, grouped by role
NodeWhat it doesWatch out for
AddAppends to the endReturns the index it was added at
InsertInserts at a given indexEverything after it shifts back by one
Remove IndexRemoves by indexEverything after it shifts forward by one
Remove ItemRemoves by valueRemoves every match
Get (a copy)Retrieves by indexOut-of-range indices cause an error
Set Array ElemOverwrites at a given indexOut of range is an error here too
LengthReturns the element count0 when empty
Last IndexReturns the last index (Length - 1)-1 for an empty array
Contains ItemReturns a bool for whether it's presentSearches from the start, so it's slow with many elements
Find ItemReturns the index it foundReturns -1 when not found
Is Valid IndexReturns a bool for whether an index is usableUse 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 Index instead of computing Length - 1 yourself

What you Get is a copy. When an array holds structs, retrieving one with Get and modifying it does not change the original array. Use Set Array Elem to 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.

PinContents
Loop BodyExecutes once per element
Array ElementThe current element
Array IndexThe current index
CompletedExecutes 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.

A numbered diagram showing how removing an element mid-loop shifts the rest forward and causes the next element to be skipped

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, then Remove Item them all after Completed. This is the most straightforward and readable option
  • Iterate backwards: use Reverse for Loop to walk from Last Index down to 0. If you remove from the back, the indices of earlier elements never move
  • Build a new array from what you keep: Add only 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 .

Sponsored

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.

NodeWhat it does
AddAdds a value. Does nothing if it's already there
RemoveRemoves a value
ContainsReturns a bool for whether it's present
LengthReturns the element count
To ArrayConverts 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++.

NodeWhat it doesWatch out for
AddRegisters a key and valueOverwrites if the key already exists
FindRetrieves the value for a keyReturns the value plus a bool for whether it was found
ContainsReturns a bool for whether the key exists
RemoveRemoves by key
KeysReturns the list of keys as an arrayUse this when you want to loop
ValuesReturns the list of values as an array
LengthReturns the number of pairs
The structure of a Map, looking up a quantity value from an item ID key, with the roles of Find, Add, and Keys

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 Add under the same key is the reliable approach (see the struct article).

Sponsored

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.

After three herbs and one potion, the Array has 4 entries, the Set has 2, and the Map has herb 3 and potion 1
ContainerContentsQuestion 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 NameTypeContainerDefault Value
PickupLogNameArray(empty)
FoundItemsNameSet(empty)
InventoryName → IntegerMap(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 PinType
ItemIDName

The body just records into the three containers in turn.

The PickupItem function node graph, in three stages: Array Add, Set Add, and Map Find → Add → Add
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 Add instead 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 between Herb and herb
  • Found is 4FoundItems is an Array rather than a Set. Check the container setting in the Details panel
  • Log stays 0 → the Target of the Add for PickupLog is wired to a different variable
  • Print String shows nothing → the execution pins in PickupItem aren'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.

Sponsored

Bonus: Good to Know for Later

  • Contains Item gets slower in proportion to element count: an Array's Contains Item compares 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 . Contains on 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 Add at 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 whose Length is 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

QuestionContainer 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 . Length and 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 Add overwrites . 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?