Updated:

as changes the runner

/execute as @e[type=minecraft:zombie] run say I am zombie

Every zombie runs the command, and @s now means that zombie.

at changes the position

/execute at @e[type=minecraft:zombie] run particle minecraft:flame ~ ~1 ~ 0 0 0 0 1

The command runs at each zombie's position. Position matters for particles, sounds, block checks and ~ coordinates.

Both together

/execute as @e[type=minecraft:zombie] at @s run particle minecraft:flame ~ ~1 ~ 0 0 0 0 1

Each zombie becomes the runner, and the particle appears at that zombie. Think of it as "as whom" and "where".

Only as, or only at

/execute as @e[type=minecraft:zombie] run setblock ~ ~2 ~ minecraft:glowstone
/execute at @e[type=minecraft:zombie] run setblock ~ ~2 ~ minecraft:glowstone

The first changes only the runner; the position stays where the command started, so a single glowstone appears there. The second runs at each zombie, so every zombie gets glowstone above its head.

Who @s means

After as, @s is the selected target. at moves only the position, so @s stays the original runner.

/execute as @a run tellraw @s {"text":"A message just for you"}

Every player gets a message addressed to them. Without as, /tellraw @s in a command block targets the block itself and nobody sees it.

Order matters

as @a at @s runs as each player, at that player's position. at @s as @a runs at the original runner's position, as each player, so the position never changes. /execute is read left to right.

Common patterns

at vs positioned as

at copies position, rotation and dimension. positioned as copies only the position and keeps the original rotation. With ^ coordinates, use at for the target's facing and positioned as to keep your own.

When it does not work

Common questions

Check your command

The Command Error Doctor checks common syntax mistakes in your browser.