Skip to content

Variables

Every variable declaration carries an access modifier. There is no bare let or var — the modifier is how you say where the variable lives and who can see it.

Modifier Visible to Lives on
public this file, and other files that import it stage or sprite
private this file only stage or sprite
temp this scope only (see the warning below) a mangled global
public score: num = 0;
private secret: num = 42;
proc tally() -> void {
temp running: num = 0;
running += score;
}

Top level → a stage-owned global, visible from every sprite:

public score: num = 0; # "score" on the stage

Inside a sprite → a sprite-owned member, visible only within that sprite:

sprite Cat {
private lives: num = 9; # "lives" on Cat
}

If a sprite member shares a name with a global, Scratch cannot represent the collision — it resolves stage and sprite names together. Katnip renames the sprite’s copy to Sprite_name at codegen:

public greeting: str = "Katnip";
sprite Cat {
private greeting: str = "Cat"; # emitted as `Cat_greeting`
events.onFlag() {
looks.say(greeting); # "Cat"
}
}
sprite Dog {
events.onFlag() {
looks.say(greeting); # "Katnip" — no local, so the global
}
}

temp is the closest thing Katnip has to a local variable, and it is important to understand what it actually is: a global with a mangled name.

proc tally() -> void {
temp running: num = 0;
running += 1;
}

That emits one Scratch variable, reused by every call. Scratch has no call-frame storage, so there is nowhere else to put it.

score = 10;
score += 5; # also -= *= /= %=

Compound assignment works through a list or dict index too:

scores[2] += 1;
stock["apple"] += 3;

**= parses but does not work — see Known gaps.

showVariable(score);
hideVariable(score);

The argument must be a plain variable reference — an expression will not compile, because the block takes a variable field, not an input.

Lists have their own methods for this:

scores.show();
scores.hide();

Every variable needs an initializer. The type annotation is optional when the initializer makes the type obvious:

public score = 0; # num, inferred
public scores: list<num> = []; # annotation needed: [] says nothing

If a list literal is made entirely of literals, it is baked straight into the project file — the list already has its contents when the project loads:

public scores: list<num> = [3, 1, 4, 1, 5];

If any element needs a block to compute, the whole list is instead rebuilt by a green-flag script:

public roster: list<num> = [1, double(4), 9]; # rebuilt on the flag

That matters if you have a script that reads the list before the rebuilding script has run. Both are green-flag scripts and Scratch does not order them for you.