Learning Goals 3 min
By the end of this lesson you will be able to:
- Name the four main causes of lag in a big Scratch project — runaway clones, too many forever loops per sprite, expensive sensing checks, and large bitmap costumes.
- Apply aggressive delete this clone to keep the live clone count under control, including a spawner cap that stops creating new clones above a threshold.
- Use the combine-forevers-into-one pattern and the skip-if-hidden guard (<not <visible>>) so hidden or off-stage sprites stop doing work.
Warm-Up — Hafiz's chugging shooter 7 min
Hafiz built a top-down shooter with a player ship, alien clones that spawn from above, and bullet clones the player fires. After two minutes of play the game is chugging — sprites stutter, bullets lag behind the spacebar, the frame rate drops to a slideshow. Here's the alien spawner script:
when flag clicked
forever
create clone of [myself v]
wait (0.2) seconds
end
Hafiz says, "I think Scratch is slow. Maybe my laptop is old." Predict what's really happening to his project after 60 seconds of play.
Reveal the answer
After 60 seconds, the spawner has created 300 alien clones. Some get shot down, but most just keep going. Every clone runs its own forever loop, checks touching [Bullet v]? against potentially hundreds of bullets, and redraws its sprite every frame. Scratch can handle a hundred or so sprites comfortably — past two hundred, it starts to lag on any laptop. Hafiz's laptop is fine. His spawner has no brakes.
Today's lesson is the brakes — five techniques to keep a busy game running smoothly without sacrificing the fun.
L3 projects are big. A platformer, a shooter, a maze — they all reach the point where the browser can't keep up. This lesson is the toolkit for when that day comes.
New Concept — five fixes for laggy projects 15 min
Performance bugs aren't like normal bugs. The code works. It just works slowly. The fix is to do less work per frame. Here are five ways.
Fix 1 — delete clones aggressively
Every clone you don't delete this clone stays alive until the flag is clicked again. A clone that has fallen off the bottom of the Stage is invisible — but it's still in memory, still running its forever loop, still checking sensing blocks. Always delete clones the moment they're no longer needed.
when I start as a clone
show
go to x: (pick random (-220) to (220)) y: (180)
repeat until <(y position) < (-180)>
change y by (-5)
end
delete this clone
Fix 2 — cap the spawner
Even with aggressive deletion, a spawner that creates clones faster than they die will pile up. Add a live-count variable and check it before each new clone:
when flag clicked
set [count v] to (0)
forever
if <(count) > (50)> then
stop [this script v]
end
create clone of [myself v]
change [count v] by (1)
wait (0.2) seconds
end
when I start as a clone
show
go to x: (pick random (-220) to (220)) y: (180)
repeat until <(y position) < (-180)>
change y by (-5)
end
change [count v] by (-1)
delete this clone
50 is just a number — pick what feels right for your game. Most laptops handle 100–200 clones comfortably. Past 200, lag.
Fix 3 — combine forever loops into one
Three separate forever loops on one sprite is three full passes per frame. Scratch can usually merge them, but you can do it explicitly with one forever containing three if-then guards:
when flag clicked
forever
if <key [left arrow v] pressed?> then
change x by (-5)
end
if <key [right arrow v] pressed?> then
change x by (5)
end
if <key [space v] pressed?> then
create clone of [Bullet v]
end
end
Same behaviour, fewer scripts running. On a sprite with five or six forever loops, combining them can cut the load measurably.
Fix 4 — cheaper sensing checks
touching [Alien v]? is expensive. Scratch checks every pixel of your sprite against every pixel of every alien clone — every frame. With 100 alien clones, that's 100 pixel-by-pixel comparisons per frame, per bullet. If you have 20 bullets, that's 2000 comparisons per frame.
For small sprites a distance check is much cheaper:
if <(distance to [Alien v]) < (20)> then
delete this clone
end
Distance only checks against the original sprite, not its clones — so this trick works best when the target is a single non-cloned sprite. For clones, you may still need touching — but you can guard it (see Fix 5).
Fix 5 — skip work when hidden
A hidden sprite still runs its scripts. If you have 50 hidden clones each running a forever loop with a touching check, that's 50 expensive checks per frame for sprites the player can't even see. Guard the work:
when I start as a clone
forever
if <not <visible?>> then
wait (0.5) seconds
else
if <touching [Bullet v] ?> then
delete this clone
end
change y by (-3)
end
end
The visible? hexagonal reporter is in the Sensing palette. Combined with <not> it's a powerful guard for "I'm not on screen right now — don't waste cycles".
Worked Example — fixing Hafiz's shooter 12 min
Open Hafiz's original spawner. We'll bring it from "300 clones and chugging" to "smooth at 50" in seven steps.
Step 1 — Measure first
Make a variable called count. Tick its watcher and put it on the Stage. Click the flag, play for 30 seconds, watch the count climb. Confirm the lag is correlated with the climb — write down the number where you first feel the stutter (typically around 100–150).
Step 2 — Add the cap
Edit the spawner. Set count to 0 at flag click. Inside the forever, before create clone of [myself v], add if <(count) > (50)> then stop [this script v]. The spawner now self-limits.
Step 3 — Decrement on death
On the alien's when I start as a clone script, just before delete this clone, add change [count v] by (-1). Now the count breathes — up when a clone is born, down when it dies — and the spawner restarts the moment count drops back under 50.
Step 4 — Combine the player's forevers
Check the player ship. Hafiz has three forevers — one for left arrow, one for right arrow, one for spacebar. Combine them into a single forever with three if-thens inside. Same behaviour, less work per frame.
Step 5 — Swap touching for distance on the bullets
Bullet hit-check was touching [Alien v]?. Since each alien is small, switch to distance to [Alien v] < 15. Faster check, same feel.
Step 6 — Add the hidden-skip guard
Some aliens drift off-screen briefly before scrolling back in. Wrap their forever in the <not <visible?>> guard so they don't run their expensive checks while invisible.
Step 7 — Re-measure
Click the flag. Play for two minutes. The watcher hovers around 50, never above. The game runs smoothly. Stutters gone. The fixes worked, and you can prove it because the watcher gives you a number.
The full assembled stack
when flag clicked
set [count v] to (0)
forever
if <(count) > (50)> then
stop [this script v]
end
create clone of [myself v]
change [count v] by (1)
wait (0.2) seconds
end
when I start as a clone
show
go to x: (pick random (-220) to (220)) y: (180)
forever
if <not <visible?>> then
wait (0.3) seconds
else
change y by (-3)
if <(y position) < (-180)> then
change [count v] by (-1)
delete this clone
end
end
end
What you just built: the same pattern used by every commercial bullet-hell shooter, every clicker game, every project that has to handle hundreds of moving things. The shape is always: cap the count, kill what's done, skip what's hidden, prefer cheap checks.
Try It Yourself — three optimisation drills 15 min
Goal: Build a falling-snow effect with a capped spawner. Up to 30 snowflakes at a time, never more. When a flake falls off the bottom, it dies and the count goes down so the spawner can make a fresh one.
when flag clicked
set [count v] to (0)
forever
if <(count) < (30)> then
create clone of [myself v]
change [count v] by (1)
end
wait (0.1) seconds
end
when I start as a clone
go to x: (pick random (-220) to (220)) y: (180)
repeat until <(y position) < (-180)>
change y by (-2)
end
change [count v] by (-1)
delete this clone
Think: The if-then guard before the clone-create is the cap. The pair of change [count v] by (+/-1) blocks keep the count breathing. This single pattern makes spawners safe for any project.
Goal: Take a sprite that has three separate forever loops — one for arrow keys, one for animation, one for sound — and combine them into a single forever with three if-then guards inside.
when flag clicked
forever
if <key [right arrow v] pressed?> then
change x by (4)
end
if <(timer) > (0.3)> then
next costume
reset timer
end
if <key [space v] pressed?> then
play sound [pop v]
end
end
Think: Each job is now an if-then guard. The animation uses the timer reporter to time itself, since the loop doesn't have a wait. This is the most common single-sprite optimisation pattern in L3.
Goal: Build a project with two sprite types — a Player and an Enemy clone. The Player checks distance to [Enemy v] against a threshold instead of using touching. Then add the <not <visible?>> guard on every Enemy clone so off-screen enemies stop doing work.
when flag clicked
forever
if <(distance to [Enemy v]) < (25)> then
say [Ouch!] for (0.5) seconds
end
end
when I start as a clone
show
forever
if <not <visible?>> then
wait (0.5) seconds
else
change x by (-3)
if <(x position) < (-240)> then
delete this clone
end
end
end
Think: Distance-to-Enemy reads against the original Enemy sprite — not the clones. That's a real limitation. For clone-vs-clone collision you still need touching. But for Player-vs-original sprite checks, distance is much faster and just as accurate for small sprites.
Mini-Challenge — Priya's never-ending fireworks 5 min
"It runs great for ten seconds…"
Priya built a fireworks display. Pressing the spacebar fires a rocket clone. Each rocket clone, when it reaches the top of the Stage, fires ten spark clones outward. She loves it — for the first ten seconds. Then the Stage chokes and her browser tab freezes. She shows you the spark script:
when I start as a clone
show
go to x: (x position) y: (y position)
point in direction (pick random (-180) to (180))
forever
move (4) steps
change [color v] effect by (5)
end
Predict what happens after 30 seconds of spacebar-mashing. What's the bug, and which two fixes from this lesson would you apply?
Reveal one valid solution
Every rocket creates 10 sparks. Each spark has a forever loop that never exits. Sparks fly off the edge of the Stage but stay alive in memory — Scratch clamps them at the edge and they sit there, invisible, still ticking their forever loops. After 30 seconds and ~10 rocket-launches, that's 3000 immortal spark clones grinding through the browser.
Two fixes from this lesson:
- Aggressive delete. Add a check inside the forever — if the spark is off-screen (<((x position)) > (240)> or similar) or has lived past a counter, delete this clone.
- Cap the spawner. Track the total spark count. If it's over 200, don't fire new rockets — or fire rockets with fewer sparks.
when I start as a clone
show
point in direction (pick random (-180) to (180))
set [life v] to (60)
repeat (60)
move (4) steps
change [color v] effect by (5)
end
delete this clone
Now each spark lives for exactly 60 frames (about one second), then deletes itself. The whole skill is realising that "forever" almost never means forever in a real game. Forever usually means "until this thing's job is done".
Recap 3 min
You met the five performance fixes for L3 projects that have grown too heavy: aggressive delete this clone for finished clones, a count-based spawner cap to stop unlimited growth, combining many forever loops into one with if-then guards, swapping expensive touching for cheaper distance to on small sprites, and gating clone work with <not <visible?>> so hidden sprites skip their bodies. None of these change what your game does. They change how much work the browser does per frame — and that's the difference between 60 fps and a slideshow.
- Clone cap
- A count variable + an if-then guard on the spawner that stops creating new clones above a chosen number. The count goes up when a clone is born, down when it dies.
- Aggressive delete
- Calling delete this clone the instant a clone's job is done — off-screen, hit, expired — instead of letting it sit forever in memory.
- Combined forever
- One forever with several if-then guards inside, replacing multiple parallel forever loops. Cheaper per frame than the multi-hat version.
- Distance check
- distance to [sprite v] < threshold. A round-number comparison that's much cheaper than the pixel-by-pixel touching. Works well for small sprites and original (non-clone) targets.
- Hidden-skip guard
- An <not <visible?>> wrapper that lets hidden or off-stage clones idle (wait) instead of running their full per-frame work.
Homework 2 min
The Optimisation Audit. Pick the biggest project you've made in L3 (most sprites, most clones, most scripts) and run it through the five-fix checklist.
- Open the project. Add a count watcher for any cloned sprite. Click the flag, play for two minutes, write down the highest number the count reaches.
- Apply at least one of the five fixes — cap the spawner, aggressive delete, combine forevers, switch to distance, or add the hidden-skip guard. Pick the fix that matches what you observed.
- Re-measure. Did the count cap as expected? Is the game smoother?
- If your project doesn't lag at all, deliberately uncap it — set the spawn rate to
0.05 secondsand let it run for a minute. Watch it chug. Then re-apply your fix and watch it recover.
Save as HW-L3-47-Optimised.sb3. Keep the original somewhere safe so you can compare.
Bring back next class:
- The optimised
.sb3file. - Your before-and-after count numbers.
- Your answer to: "Which fix had the biggest visible effect on your project? Why?"
Heads up for next class: SCR-L03-48 is the Level 3 Recap and Vocabulary — the closing lesson of the level. We'll walk back through all eight clusters of L3, name the single biggest idea from each, build a vocabulary card of 18+ terms, and tackle one final cert-style mini-challenge that combines lists, my blocks, clones, and state machines.