Wiki 9Minecraft Wiki 9Minecraft

Execution Forking in Bedrock Commands

How to control, multiply, and optimize command execution branches using the modern /execute syntax.

7Sections
4Code examples
3Known errors

Execution forking happens whenever a single `/execute` command splits into multiple execution contexts, running its final command or subcommands once for every entity that matches a selector. Mastering how and when execution forks is critical when writing behavior pack `.mcfunction` files: intentional forking lets you iterate over entity pairs or groups without hardcoding loops, while accidental forking is the leading cause of catastrophic server lag.

What is Execution Forking?

When Minecraft Bedrock evaluates a command, it begins with a single execution context. That context holds two primary pieces of data: the current executor (who is running the command, accessed via `@s`) and the current execution position, rotation, and dimension (where the command is running, accessed via `~ ~ ~` or `^ ^ ^`). When you chain subcommands inside `/execute`, each subcommand reads from and modifies this context before passing it along to the next subcommand in the chain.

Forking occurs when a subcommand resolves a target selector that matches more than one entity. Instead of picking one entity or bundling them into a list, the command engine duplicates the entire remaining command chain once for each matched entity. If your selector matches five zombies, the command forks into five distinct execution branches. Each branch finishes the rest of the `/execute` chain independently, completely unaware of the other branches.

This behavior is fundamental to how `.mcfunction` files operate in behavior packs. Because Bedrock functions do not have native `for` or `while` loop syntax, execution forking is the standard mechanism for iterating over groups of entities, comparing scores across multiple targets, and applying localized effects to dynamic crowds.

Subcommands That Fork vs. Subcommands That Filter

Not every subcommand inside `/execute` causes a branch to split, even if it contains a multi-target selector like `@e` or `@a`. To write predictable functions, you must distinguish between context-modifying subcommands (which fork) and conditional subcommands (which only filter the existing branch).

Subcommands that change who or where the command is running will fork the execution once per matched target. If a selector in one of these subcommands matches zero entities, the current branch terminates immediately and the final `run` command never fires. Conversely, conditional subcommands like `if entity` or `unless entity` evaluate the selector as a boolean check. Even if `if entity @e[type=minecraft:cow]` finds fifty cows, it simply returns true and passes the single existing execution branch forward without multiplying it.

SubcommandForks Execution?Behavior with Multiple Targets
as <targets>YesSplits into one branch per matched entity, updating `@s` for each branch while keeping position unchanged.
at <targets>YesSplits into one branch per matched entity, updating position, rotation, and dimension while keeping `@s` unchanged.
positioned as <targets>YesSplits into one branch per matched entity, updating only the execution coordinates X, Y, and Z.
rotated as <targets>YesSplits into one branch per matched entity, updating only the yaw and pitch angles.
facing entity <targets> <anchor>YesSplits into one branch per matched entity, rotating the execution context to look at each target in turn.
if entity <targets>NoChecks if at least one entity matches. Passes the current single branch forward if true; terminates if false.
unless entity <targets>NoChecks that zero entities match. Passes the current single branch forward if true; terminates if false.

The Cartesian Product Trap (`as` Followed by `at`)

The most common mistake made when transitioning to the modern `/execute` syntax is chaining two multi-target selectors together when trying to make entities run a command at their own feet. If you write `execute as @e[type=minecraft:armor_stand] at @e[type=minecraft:armor_stand] run ...`, you are instructing the game to fork twice.

When the game evaluates `as @e[type=minecraft:armor_stand]`, and there are 10 armor stands in the world, it creates 10 branches. Next, each of those 10 branches evaluates `at @e[type=minecraft:armor_stand]`. Because `@e` searches the entire dimension again rather than referring to the current executor, each of the 10 branches splits into 10 more branches. You now have 100 execution branches (10 multiplied by 10). Every armor stand is running the command at the location of every other armor stand, as well as its own.

To bind the execution position to the specific entity selected in the `as` clause without forking a second time, you must use `at @s`. Because `@s` only ever resolves to the single entity currently holding the execution context for that branch, `at @s` updates the position for each of the 10 branches without creating any new ones.

BP/functions/forking_comparison.mcfunction
# WRONG: Forks 10 x 10 = 100 times if there are 10 zombies.
# Every zombie spawns particles at every other zombie's location.
execute as @e[type=minecraft:zombie] at @e[type=minecraft:zombie] run particle minecraft:villager_angry ~ ~2 ~

# CORRECT: Forks 10 x 1 = 10 times.
# Each branch updates its position to its own executor (@s).
execute as @e[type=minecraft:zombie] at @s run particle minecraft:villager_angry ~ ~2 ~

Intentional Forking for Entity-to-Entity Comparisons

While accidental multiplication causes bugs, intentional Cartesian forking is a powerful pattern when you genuinely need to compare pairs of entities or draw lines between targets. Because `/execute` overwrites `@s` whenever you run a new `as` subcommand, you need a way to remember the first entity while your branch forks across the second set of entities.

The standard recipe for this is the Temporary Tag Handoff. First, fork across your primary group of entities using `as` and run a sub-function. Inside that sub-function, each entity is now `@s` in its own isolated branch. You assign a temporary tag (such as `current_source`) to `@s`, then immediately run a second command that forks across your secondary targets. Inside that inner fork, `@s` refers to the secondary target, while `@e[tag=current_source,c=1]` refers back to the primary entity that initiated the branch. Once the inner check completes, you remove the temporary tag before the branch finishes so the next primary entity starts with a clean slate.

BP/functions/arena/check_target_pairs.mcfunction
# This function is called via: execute as @a[tag=arena_player] at @s run function arena/check_target_pairs
# At the start of this branch, @s is one specific arena_player.

# 1. Mark the current branch's initiator
tag @s add current_initiator

# 2. Fork across all other players within 15 blocks, excluding the initiator.
# Inside this command, @s becomes the nearby target, while current_initiator remembers who started the check.
execute as @a[tag=arena_player,tag=!current_initiator,r=15] if score @s arena_points < @p[tag=current_initiator] arena_points run effect @s weakness 2 0 true

# 3. Clean up the marker before the next outer branch runs
tag @e[tag=current_initiator] remove current_initiator

Context Persistence Across Function Calls

When an `/execute` command forks and ends with `run function <path>`, each forked branch executes the entire target `.mcfunction` file from top to bottom before the game moves on to the next branch. Crucially, the execution context (both the `@s` executor and the `~ ~ ~` position/rotation) passes directly into the called function and persists across every line in that file.

This allows you to fork once in a master tick function and perform ten or twenty sequential actions on each entity without re-evaluating `@e` on every single line. Since resolving `@e` requires scanning the world's entity list, whereas resolving `@s` is an instant pointer lookup on the current branch, grouping your per-entity logic into a sub-function called via a single fork drastically reduces server tick time.

BP/functions/tick.json
{
  "values": [
    "boss/tick_dispatcher"
  ]
}

Sub-Function Branch Isolation

In the example below, `boss/tick_dispatcher.mcfunction` forks once per custom boss entity and shifts the execution position to that boss. Inside `boss/behavior_step.mcfunction`, every command assumes `@s` is the boss and `~ ~ ~` is the boss's current location. If a line inside the sub-function modifies the execution context (for instance, using `at @a[r=10]` to fork again at nearby players), that modification only lasts for that specific line; the very next line in the function reverts to the parent branch's context where `@s` and `~ ~ ~` belong to the boss.

BP/functions/boss/behavior_step.mcfunction
# Called via: execute as @e[type=minecraft:warden,tag=custom_boss] at @s run function boss/behavior_step

# Increment this specific boss's cooldown timer
scoreboard players add @s attack_timer 1

# If timer hits 100, fork across nearby players to strike them, without losing the boss's context on later lines
execute if score @s attack_timer matches 100.. at @a[r=12] run summon minecraft:lightning_bolt ~ ~ ~

# Here, ~ ~ ~ is still the boss's position, and @s is still the boss
execute if score @s attack_timer matches 100.. run particle minecraft:sonic_explosion ~ ~1.5 ~
execute if score @s attack_timer matches 100.. run scoreboard players set @s attack_timer 0

Reducing Fork Overhead and Pruning Branches Early

Every time an execution branch forks, the game allocates memory for the new context and runs all subsequent subcommands for that branch. If your pack forks across 500 entities and then runs a heavy check like `if block ~ ~-1 ~ minecraft:grass_block`, it performs 500 block lookups. However, if you can filter the entities inside the selector arguments before the fork happens, or prune branches early using cheap checks before expensive ones, you keep your add-on performant.

Always move tag, type, score, and radius checks inside the target selector brackets (`@e[type=...,tag=...,scores={...}]`) rather than chaining `execute as @e if entity @s[tag=...]`. When filters are inside the first selector, the engine only creates branches for entities that pass all criteria. When you must chain multiple conditions in `/execute`, order them from least expensive to most expensive: check `if score` or `if entity @s` first, check `if block` second, and only perform secondary `as` or `at` forks at the very end of the chain after dead branches have already been terminated.

Which versions this applies to

Applies to Minecraft Bedrock Edition 1.19.50 and newer (where the revised /execute syntax became standard). Requires format_version or min_engine_version of at least [1, 19, 50] in your behavior pack manifest.json to use the new syntax inside .mcfunction files. No experimental toggles are required.

When it does not work

What you seeWhyThe fix
Function call limit of 10000 was exceeded, stopping execution.Your `/execute` command forked across a massive number of entities (or forked exponentially via `as @e at @e`) before running `function <name>`. Bedrock caps the total number of command/function executions at 10,000 per single top-level invocation to prevent infinite loops and server freezes.Replace `at @e` with `at @s` if you only intended to move execution to the current entity, and narrow your initial selector with `type=`, `tag=`, or `c=` limits so fewer branches are spawned before the `run function` call.
Syntax error: Unexpected "as": at "execute >>as<< @a at @s"Your behavior pack's `manifest.json` has a `min_engine_version` lower than `[1, 19, 50]`. For backward compatibility, Bedrock compiles `.mcfunction` files using the legacy `/execute <origin> <position> <command>` parser unless the manifest explicitly targets 1.19.50 or higher.Open `BP/manifest.json` and update `header.min_engine_version` to at least `[1, 19, 50]` (for example, `[1, 20, 80]`), then reload the world.
No targets matched selectorYou ran a command inside a `.mcfunction` file targeting `@s`, but the function was executed directly from `tick.json` or a command block without first forking context via `execute as <targets> run function ...`. Server-level contexts like `tick.json` have no entity executor, so `@s` resolves to nothing.Ensure the function is called through an `execute as <selector> run function <path>` wrapper, or replace `@s` with an explicit selector like `@a` or `@e[tag=...]` if the function is meant to run directly from `tick.json`.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.