When you open the right-click menu to bundle up some logic you use repeatedly, Function and Macro sit right next to each other. It's easy to get stuck on which one to pick.
Both look like "tools for grouping logic," but they work in completely different ways . A Function is a subroutine that gets called; a Macro is a stamp whose contents get copied into place at compile time. That difference is where the decisive distinctions come from: whether you can use Delay, and whether you can branch execution pins.
This article explains the difference between Functions and Macros, starting from how they work. At the end, we build a "skill cooldown" for an action game using both tools.
What You'll Learn
- The mechanical difference: a Function is called, a Macro is copied in
- The two decisive differences: whether Delay works and whether execution pins can branch
- The selection rule: Function by default, Macro only for the exceptions
- Hands-on: build a skill cooldown by splitting the work between a Function and a Macro
Function Basics: The "Function" That Gets Called
A Function is exactly what a function is in programming. It takes input, processes it, and returns a result. It works as a subroutine that is called from somewhere and returns when it's done .

| Trait | Details |
|---|---|
| Execution flow | One entry (execution pin), one exit (Return). Runs from start to finish in one go |
| Latent nodes | Delay and Timeline cannot be used |
| Local variables | Can be defined inside the Function (see Blueprint Variables 101) |
| Recursion | Supported |
| Pure Function | You can create a "pure calculation" node with no execution pins |
| Debugging | The execution flow is clear and easy to follow |
For example, checking "is the target within range" is a textbook Function.
Function: IsWithinRange (Pure, Input: TargetLocation, Range → Output: Boolean)
→ Calculate the distance between your location and TargetLocation → return whether it is <= Range
It changes no state, it just computes a value and returns it. Making logic like this a Pure Function lets you call it as a clean node with no execution pins.
Macro Basics: The "Stamp" That Gets Copied In
A Macro is not a function. It's a stamp that registers a group of nodes . Rather than an instance being executed each time you call it, its nodes are expanded (copied) into the calling graph at compile time .
| Trait | Details |
|---|---|
| Execution flow | Can have multiple entries and exits (execution pins can branch on the way out) |
| Latent nodes | Delay and friends can be used |
| Local variables | Not supported (pass values through input pins or wires) |
| Recursion | Not supported (the expansion would go on forever) |
| Expansion | Nodes are copied into every place you use it, so graphs get fat |
| Debugging | You end up following expanded nodes, which is a bit trickier than a Function |
The headline capability that only Macros have is branching execution pins .

Just as a Branch node has two exits for True and False, nodes that "leave through a different path depending on a condition" can only be built as Macros. The stock DoOnce, Gate, and For Each Loop nodes are Macros the engine ships with.
The other exclusive is latent nodes like Delay . A Function operates under the promise of "run all the way through and return," so it can't wait partway.

Comparison Table: Where the Decisive Differences Are
It all comes down to "called" versus "copied in."

| Item | Function | Macro |
|---|---|---|
| How it works | Gets called (subroutine) | Gets copied in (inline expansion) |
| Execution pins | One in, one out only | Can have multiple entries and exits |
| Latent nodes (Delay etc.) | Not supported | Supported |
| Recursion | Supported | Not supported |
| Local variables | Can be defined | Cannot be defined |
| Effect on the graph | A single call node is enough | Expanded into every place it's used, bloating the graph |
| Debugging | Easy | Somewhat trickier |
The Rule: Function by Default, Macro Only for Exceptions
The rule when you're unsure is simple: default to a Function, and reach for a Macro only when you need something a Function can't do .
- Use a Function: pure calculations and state checks, logic that doesn't need branching execution pins, complex logic where you want easy debugging, recursion
- Use a Macro: when you want to branch execution pins on the way out, or when you need latent nodes like
Delay
There are three common mistakes. (1) Trying to put a Delay inside a Function (this is a compile error; move it to a Macro or the Event Graph). (2) Overusing Macros (expansion copies inflate both your graph and your compile times). (3) Recursion in a Macro (infinite expansion, which errors out).
Hands-On: Building a Skill Cooldown With Both Tools
To get the distinction into your hands, let's use both tools on a single mechanic. The subject is a skill cooldown : an action game's special move, a shmup's bomb, an RPG's spell cast. "Mash the button all you like, it only fires once per interval" shows up in every genre.

Here's the split. "Can I fire?" is a calculation, so it's a Function. "Wait three seconds and unlock again" waits on time, so it's a Macro.
Setup
Add the variables the check needs to your character Blueprint. Match the default values too.
| Variable | Type | Default | Role |
|---|---|---|---|
CurrentMana | Float | 50.0 | Current mana |
SkillCost | Float | 25.0 | Mana one skill use costs |
bIsStunned | Bool | false | Whether you're stunned |
For input, receive a key event such as Left Mouse Button for testing (in a real game, replace it with an Enhanced Input IA_Skill → Enhanced Input intro).
- The Function side:
CanUseSkill(Pure): create a Pure Function that returns True only whenCurrentMana >= SkillCostandNOT bIsStunned. It's a pure check that doesn't wait on time, so it's Function territory
Function: CanUseSkill (Pure / output: bResult / Boolean)
→ (CurrentMana >= SkillCost) ─┐
→ (NOT bIsStunned) ─┴→ AND Boolean → Return (bResult)
- The Macro side: the
CooldownMacro: create a new Macro with an execution pin andTime(Float) as Inputs and an execution pin (Then) as Output, then wire the body like this
Macro: Cooldown
In → DoOnce
Completed → Sequence
Then 0 → Out: Then ← let this activation through
Then 1 → Delay (Duration: Time) → DoOnce's Reset ← re-unlock after Time seconds
DoOnce's Completed has a single execution pin, so it can't wire directly to both Out and Delay (a Blueprint execution output pin connects to only one destination). Insert a Sequence node in between: Then 0 lets the activation through, and Then 1 runs the cooldown timer.

DoOnce is "a lock that closes after letting one call through," and Delay is "the person who comes to unlock it three seconds later." This body needs a Delay, so it cannot be built as a Function . Try searching for the Delay node inside a Function. It won't appear in the list (or it will error out).
- Combine them into the activation logic: wire
Input Event → Branch (Condition: CanUseSkill) → True → Cooldown (Time: 3.0) → Spawn the projectileand you're done
Hit Play and mash the button. No matter how many times you press it, only one projectile comes out per three seconds, and after three seconds exactly one more gets through. Hold the button and wait ten seconds and you get exactly 4 shots (at 0, 3, 6, and 9 seconds). That count is your answer check. If a projectile fires on every press, check whether the Delay's pin is wired to Then instead of Reset.
To feel the division of labor, set bIsStunned to true and press Play. Even once the cooldown is up, no projectile fires. That single change shows that "can I fire?" (the Function) and "throttle the spam" (the Macro) are separate parts.
Two things to take away.
- Pick the tool by role: calculation and checks go in a Function, control of time and flow goes in a Macro. Even within one mechanic, splitting the parts by what each tool is good at is what good design looks like
- This Cooldown Macro is reusable forever: dashes, invincibility frames after a hit, preventing repeated message popups. It works for every "stop the spam" situation. Grow it into a general-purpose Macro
Bonus: Good to Know for Later
- Don't confuse it with Collapse Nodes: right-clicking a group of nodes and choosing "Collapse Nodes" is purely visual tidying, and it can't be reused . If you want reuse, make it a Function or Macro (the collapsed node's menu also offers "Collapse to Function / Macro")
- To share across multiple Blueprints: gather Functions into a Blueprint Function Library and Macros into a Macro Library, and you can call them from anywhere in the project. A general-purpose Cooldown Macro is worth putting there
- When you want intermediate values in a Macro: Macros have no Local Variables. The moment you need complex intermediate calculations, the right move is to extract that part into a Function
Summary
| Tool | How it works | Biggest strength |
|---|---|---|
| Function | Gets called (subroutine) | Clear execution flow, easy debugging, local variables |
| Macro | Gets copied in (inline expansion) | Branching execution pins, latent nodes like Delay |
The phrase to remember: Function by default, Macro only for time and branching . Group your logic with that rule and your Blueprints stay readable and hard to break.
The next step after grouping logic is tidying the whole graph. Head to 10 Techniques for Organizing Blueprint Graphs. The design approach of "drop per-frame Tick and build around events" is covered in Designing Without Event Tick.
What could you use today's Cooldown Macro for in your own game? Dashes, special moves, stopping door spam. There's no shortage of uses.