As a Blueprint grows, the Variables panel fills up until you can no longer tell which variables actually matter. The usual cause is creating Class Variables even for temporary calculation results .
Blueprints have two kinds of variables, with different lifetimes and different reach: Class Variables and Local Variables. Only one question decides between them: "is this state, or is this a work-in-progress value?" This article explains the difference between the two, then walks through deliberately causing and fixing the "call the function twice and the value doubles" bug.
What You'll Learn
- The two variable types differ in scope (how far they reach) and lifetime
- The rule of thumb: state goes in Class, intermediate values go in Local
- Local Variables can only be created inside a Function (not in the Event Graph)
- Hands-on: deliberately cause and fix the "doubles on the second call" bug
Two Variable Types: Scope and Lifetime
A Blueprint variable behaves completely differently depending on where you create it.
- Class Variables (member variables): the regular variables you create in the "Variables" section of the My Blueprint panel
- Local Variables: variables you can only create inside a Function . The "Local Variables" section appears in the My Blueprint panel only while a function is open
The differences fall along two axes. The first is scope (where it can be reached from) .

| Class Variable | Local Variable | |
|---|---|---|
| Scope | Readable and writable from every function and event in the Blueprint (and from outside if set to Public) | Only inside the function that defines it. Completely invisible from outside |
| Lifetime | From instance creation to destruction | From the start of the function to its end |
| Typical use | An object's state , settings, references | Temporary calculation results , intermediate data |
The second axis is lifetime (how long it lives) .

A Class Variable keeps its value for as long as the actor is alive. A Local Variable is born the moment the function is called and vanishes without a trace when the function ends. That "disappears immediately" property is the key to the bug prevention we cover below.
Important note: Local Variables can only be defined inside a Function. You cannot create them in the Event Graph. If you find yourself wanting a temporary value in the Event Graph, that's a sign the logic should be extracted into a Function (see Function vs Macro).
The Rule: State in Class, Intermediate Values in Local
The guiding principle is minimizing scope . The narrower a variable's reach, the safer it is.

What Belongs in a Class Variable: An Object's "State"
Think about a player's CurrentHP, MaxHP, and IsDead. They go down on a damage event, go up in a heal function, and get read by the UI update. In other words, they are "current state" that multiple pieces of logic reference and update . That's exactly what Class Variables are for.
Event ReceiveDamage
→ Subtract Damage from CurrentHP (Class Variable) and Set
→ If CurrentHP <= 0 → Set IsDead (Class Variable) to True
What Belongs in a Local Variable: Intermediate Values Contained in a Function
Now consider the "base damage × buff multiplier" intermediate result inside a damage calculation function. That value is only needed for the few milliseconds the calculation takes . Nobody outside the function needs to know it. That's what Local Variables are for.
Function: CalculateFinalDamage (Return Value: Float)
→ Set the result of BaseDamage × BuffMultiplier into Local Variable: IntermediateDamage
→ Return IntermediateDamage × CriticalMultiplier as the Return Value
When the function ends, IntermediateDamage is gone. It never clutters the Variables panel and never affects anything else.
What Happens When Everything Is a Class Variable
The mine beginners step on most often is making even temporary calculation results into Class Variables. There are three main problems.
- A bloated variable list: the state variables that actually matter get buried under a pile of throwaway variables
- Debugging becomes a maze: when a variable can be written from anywhere, the suspect list for "why did this value change?" is every piece of logic in the Blueprint. With a Local Variable the only suspect is that one function, so the search narrows instantly (see Debugging with Print String)
- Leftover value bugs: this is the biggest trap. A Class Variable remembers its previous value . Let's step on it for real in the next section
Hands-On: Causing and Fixing the "Doubles on the Second Call" Bug
Total score in an RPG, combo tallies in a shmup, total carry weight in an inventory. "Loop over an array and sum it up" shows up in every genre. It is also the clearest possible teaching example of what goes wrong when you pick the wrong kind of variable. This time we'll get it wrong on purpose, witness the bug, and then fix it .

Setting up the repro. In any Actor Blueprint (BP_ScoreTester, for example), create the following variables.
| Variable Name | Type | Default Value | Where It Lives |
|---|---|---|---|
ScoreArray | Integer array | 100, 100, 100 | Variables panel (Class Variable) |
TotalScore | Integer | 0 | Variables panel (this is the deliberately wrong place) |
-
Build the deliberately wrong function: create the Function
CalculateTotalScore(return value: Total / Integer) and wire it like thisFunction: CalculateTotalScore (Return Value: Total / Integer) → For Each Loop (Array: ScoreArray) Loop Body → Set TotalScore (= Add (A: TotalScore, B: Array Element)) Completed → Return Node (Total ← TotalScore) -
Call it twice and watch: in the Event Graph, call it twice in a row
Event BeginPlay → CalculateTotalScore → Print String (Append("1st: ", ToString(Total))) → CalculateTotalScore → Print String (Append("2nd: ", ToString(Total)))Hit Play and you get
1st: 300. Correct. But then2nd: 600. The array hasn't changed at all, yet the total doubled -
Name the culprit: because
TotalScoreis a Class Variable, the 300 from the first call survived the end of the function , and the second call's additions piled on top of it -
Fix it: delete
TotalScorefrom the Variables panel, then withCalculateTotalScoreopen, createRunningTotal(Integer) under Local Variables in the My Blueprint panel. All you do is swap it inFunction: CalculateTotalScore (Return Value: Total / Integer) → For Each Loop (Array: ScoreArray) Loop Body → Set RunningTotal (= Add (A: RunningTotal, B: Array Element)) ← changed to Local Completed → Return Node (Total ← RunningTotal)Hit Play and you get
1st: 300/2nd: 300. It stays 300 no matter how many times you call it, because a Local Variable starts from a clean 0 on every callThe only thing that changed in the graph is where the variable lives. That alone changes the result.


Some of you are thinking "couldn't I just add a reset node and keep the Class Variable?" You're right, setting it to 0 at the top of the function works. But that also means signing up to manage "variables that need resetting every time" yourself, forever. With a Local Variable, the reset is guaranteed by the system. Relying on the mechanism rather than on human attention is the shortcut to Blueprints with fewer bugs.
Two things to take away.
- Ask yourself "do I still need this value after the function ends?": if the answer is no, go Local without hesitating. That single question sorts about 90% of your variables correctly
- Learn to infer the culprit from the symptom: "the value is wrong starting from the second time," "the previous run's state was still there after I retried." When you see those symptoms, suspect a leftover Class Variable first. This instinct keeps paying off
Bonus: Good to Know for Later
- Function Input/Output pins are in the same family: use Input pins to pass values into a function and the Return node to pass them back. Passing values through arguments and return values is the cleanest style of all, and it often removes the need for Local Variables entirely. Local Variables fit best for intermediate values you reuse several times inside a function
- Class Variables have organization tools: once you have a lot of variables, you can group them with Category in the Details panel. Sorting them into
Stats,Config, and so on makes the Variables panel much easier to scan - If you want different values per instance: turn on Instance Editable on a Class Variable and you can change the value per placed instance in the Details panel. That's how you get "same enemy Blueprint, but this one has extra HP"
Summary: A Variable Selection Checklist
| Question | If YES |
|---|---|
| Does it hold an object's state or settings? (HP, flags, references) | Class Variable |
| Is it referenced from multiple functions or events? | Class Variable |
| Is the value still needed after the logic finishes? | Class Variable |
| Is it temporary data used only inside one function? | Local Variable |
| Can it be thrown away once the logic finishes? | Local Variable |
The phrase to remember: state goes in Class Variables, process goes in Local Variables . Just making this call every time you create a variable will visibly improve how readable and bug-resistant your Blueprints are.
After variables comes organizing your logic. Pick up the skill of extracting logic into functions in Function vs Macro, and learn to tidy up whole graphs in 10 Techniques for Organizing Blueprint Graphs.
Scroll through your own Blueprint's Variables panel right now. How many "values you don't need once the function ends" are hiding in there?