6.5 Save and Load

Aligned outcomes:

SLO 3

Describe, design, implement, and test structured programs using currently accepted methodology.

Learning Objectives

By the end of this section, you will be able to:

In this section, you will learn to:
  • Save data to persistent storage using storeItem().
  • Load saved data using getItem().
  • Delete saved data using removeItem().
  • Implement a high-score system that survives browser reloads.
  • Save an object or array of related data under one key instead of several separate keys.

The courtyard game from §6.1-6.4 already tracks a score. This section makes that score mean something across visits: a high score that survives closing the browser tab.

6.5.1 Persistent Storage

storeItem(name, value) saves a value under a key. getItem(name) retrieves a previously stored value. removeItem(name) deletes a stored value. Data saved this way persists even after the browser is closed — pick a key name once (like 'highScore'), and it survives reloads until you explicitly remove it or overwrite it.

This is called persistent storage — the data stays around even when the program isn't running. In a web browser, moSHion uses the browser's localStorage, a tiny built-in database only your website can access.

Definition 6.5.1: Persistent Storage

Data that remains available even after the program is closed and the browser is restarted. In moSHion, this is implemented using the browser's localStorage.

Try It Now 6.5.1

Open your browser's developer tools (F12) and find the Application or Storage tab. Run this sketch — it saves the courtyard game's starting score as a test value — and watch the entry appear under Local Storage.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution

After running this once, open Application > Local Storage, and you should see an entry with key 'courtyardHighScore' and value 0. This data will persist even after closing and reopening the browser — the next Try It Now loads it back.

6.5.2 Saving Data

storeItem(name, value) saves a value under a key name you choose. You do not need to save every frame — save once when something meaningful happens.

The key name is like a label on a file folder. Save with storeItem('courtyardHighScore', 12) and later with storeItem('courtyardHighScore', 18), and the second call overwrites the first. Use unique key names for different pieces of data.

Try It Now 6.5.2

Take the courtyard game from §6.4 and save score under the key 'courtyardHighScore' every time it changes — right inside both overlaps callbacks.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution
Editor
runs in a sandboxed frame
▶ Press Run to see the output…

Every coin or bomb now overwrites 'courtyardHighScore' with the current score. That's not quite what a high score means yet — it's saving every score, not just the best one. §6.5.3 loads it back so you can compare.

6.5.3 Loading Data

getItem(name) retrieves a previously stored value. If nothing has been saved under that key, it returns undefined. The standard pattern loads saved data in setup() with the || operator to provide a default:

highScore = getItem('courtyardHighScore') || 0;
Definition 6.5.2: getItem

A function that retrieves a previously stored value by key name. Returns undefined if no value has been saved under that key.

Try It Now 6.5.3

Fix §6.5.2's mistake: load the real high score in setup, and only storeItem when the current score actually beats it. Draw the high score alongside the current score.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution
Editor
runs in a sandboxed frame
▶ Press Run to see the output…

Now highScore only updates — and only saves — when the current score actually beats the stored one. Refresh the page and highScore loads back from where it left off, while score correctly restarts at 0.

6.5.4 Deleting Data

removeItem(name) deletes a stored value by key name. After calling it, getItem returns undefined again for that key. Use this for reset buttons, "new game" options, or clearing test data during development.

Definition 6.5.3: removeItem

A function that deletes a previously stored value by key name. After removal, getItem returns undefined for that key.

Try It Now 6.5.4

Add a reset key: pressing R clears highScore back to 0 both in memory and in storage.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution

Add this line inside draw, anywhere after the input handling:

  if (kb.presses('r')) {
    highScore = 0;
    removeItem('courtyardHighScore');
  }

Press R and the on-screen high score drops to 0 immediately. getItem('courtyardHighScore') would now return undefined until the next coin pickup saves a fresh value.

6.5.5 When to Save

You do not need to save every frame — save once when something meaningful happens. §6.5.3's version already does this correctly: storeItem only runs inside the if (score > highScore) branch, not on every coin pickup and never inside draw's main body.

Think of saving like taking notes in class. You don't write down every word the teacher says — you write down the important things. Same with storeItem: save the important moments, not every frame.

Try It Now 6.5.5

Look back at §6.5.2's first attempt, which called storeItem on every coin and bomb regardless of whether it beat the high score. Why was that version wasteful even though it "worked" in the sense of not crashing?

Solution

It called storeItem far more often than necessary — every single pickup, whether or not the score was actually a new record — which means unnecessary writes to browser storage (slower than a plain variable assignment) and, worse, it was overwriting 'courtyardHighScore' with the current score rather than the best score, so a run that peaked at 12 and then lost 15 points to bombs would end up saving something lower than the real high point reached. §6.5.3's if (score > highScore) check fixes both problems with the same line.

6.5.6 Saving Structured Data

Every save so far has been a single number under a single key. storeItem isn't limited to that — pass a whole object or array as the value, and getItem hands back an equivalent structure. That's what makes it practical to save several related pieces of data — a high score and a total-coins count and whatever else the game accumulates — under one key, instead of juggling a separate key (and a separate getItem/storeItem pair) for every field.

Definition: Structured Save Data

An object or array passed directly to storeItem (rather than a single number or string). getItem returns it as an equivalent object or array, not a string you'd need to parse yourself — moSHion's storage layer serializes and deserializes it for you.

Try It Now 6.5.6

Combine highScore and a running totalCoins count (coins ever collected, never reset) into one saved object under a single key, 'courtyardStats', instead of two separate storeItem keys.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution
Editor
runs in a sandboxed frame
▶ Press Run to see the output…

One key, 'courtyardStats', now carries both numbers. getItem('courtyardStats') || { highScore: 0, totalCoins: 0 } in setup either restores the whole object from a previous visit or hands back a fresh one with sensible defaults — the same ||-default pattern from §6.5.3, just applied to an object instead of a bare number.

6.5.7 Challenge: Extend It Yourself

No starter code this time — you build it. Add a second saved statistic: the total number of coins ever collected across all play sessions (not reset when the page reloads, and never decreasing — unlike score, it only ever goes up). Load it in setup, increment and save it inside the coin overlaps callback, and display it as a third line of on-screen text.

Hint (try the Challenge yourself first!)

Give it its own key, like 'courtyardTotalCoins', separate from 'courtyardHighScore' — two different pieces of data need two different key names, per the Insight Note in §6.5.2.

Problem Set 6.5

Problem 1. What does storeItem('courtyardHighScore', 100) do? Where does the data go?

Solution

Step 1 — Parse the call: storeItem(name, value) takes two arguments: a key name you choose and the value to save. Here the key is 'courtyardHighScore' and the value is the number 100.

Step 2 — Where the data goes: The value 100 is written to persistent storage — in a web browser, moSHion saves it to the browser's localStorage, a tiny built-in database that only your website can access. If you open the developer tools (F12) and look under Application > Local Storage, you would see an entry with key 'courtyardHighScore' and value 100.

Step 3 — Why it "persists": Unlike a regular variable, which disappears the moment the sketch stops running, this entry survives closing the tab and restarting the browser. It stays there until you overwrite it with another storeItem call on the same key, or delete it with removeItem('courtyardHighScore').

Answer: It saves the value 100 under the key 'courtyardHighScore' in the browser's localStorage (persistent storage), where it survives page reloads and browser restarts until it is overwritten or removed.

Problem 2. What does getItem('courtyardHighScore') return if nothing has been saved under that key?

Solution

Step 1 — Recall what getItem promises: getItem(name) retrieves a previously stored value by key name. But that only makes sense when something has actually been saved — the question is what happens when the key is empty.

Step 2 — The empty case: Per Definition 6.5.2, if nothing has ever been saved under 'courtyardHighScore', there is no value to hand back, so the function returns undefined — JavaScript's way of saying "no value exists here."

Step 3 — Why this matters in practice: This is exactly why the standard loading pattern pairs getItem with || 0. Since undefined is falsy, the || fallback supplies a sensible default instead of leaving your variable holding undefined (see 6.5.4).

Answer: It returns undefined.

Problem 3. What does removeItem('courtyardHighScore') do? After calling it, what would getItem('courtyardHighScore') return?

Solution

Step 1 — What removeItem does: removeItem(name) deletes the stored value under the given key. After calling removeItem('courtyardHighScore'), the entry that was in localStorage is gone entirely — it's not set to 0 or to an empty string; the key simply no longer exists.

Step 2 — What getItem returns afterwards: With the entry deleted, the key looks to getItem exactly as if nothing had ever been saved under it. So getItem('courtyardHighScore') returns undefined again — the same result you'd get on a brand-new browser that had never run the game.

Answer: It deletes the stored value for that key. Afterwards, getItem('courtyardHighScore') returns undefined until something is saved under that key again.

Problem 4. Explain the code highScore = getItem('courtyardHighScore') || 0. What does the || 0 part do?

Solution

Step 1 — Read the left side: getItem('courtyardHighScore') returns either the previously saved high score (a number) or undefined if nothing has been saved yet — for example, on the very first run of the game on a new browser.

Step 2 — Understand the || operator: The expression a || b evaluates to a when a is truthy, and falls back to b when a is falsy. Values like undefined, 0, and null are falsy. So if the key has data, that saved number is used; if getItem returns undefined, the expression falls through to 0.

Step 3 — Why the default matters: Without || 0, highScore would be undefined on a first run, and then score > highScore (the new-record check from §6.5.3) would compare against undefined and never be true — a new high score could never be recorded. The || 0 guarantees highScore starts at a usable number.

Answer: It loads the saved high score from storage, and the || 0 part supplies a default of 0 when nothing has been saved yet (i.e., when getItem returns undefined), so highScore always ends up holding a usable number.

Problem 5. Why should you NOT call storeItem every frame? When should you call it instead?

Solution

Step 1 — What "every frame" means: The draw function runs about 60 times per second. A storeItem call in draw's main body would therefore fire roughly 60 storage writes every second, whether or not anything actually changed.

Step 2 — Why that's wasteful: A write to browser storage is much slower than a plain variable assignment — it goes through the browser's localStorage database, not just memory. Almost all of those writes save the exact same value as the previous frame, so you pay the cost of persistence 60 times per second for essentially no benefit.

Step 3 — When you should save instead: Save once when something meaningful happens — at the moment the value changes in a way worth keeping. In the courtyard game that means inside the event callback (e.g., the coin overlaps handler), and specifically inside the if (score > highScore) branch so storage is only touched on a genuine new record, exactly as §6.5.3 does.

Answer: You should not call storeItem every frame because draw runs ~60 times per second and storage writes are far slower than variable assignments — you'd be rewriting unchanged data constantly. Call it only when something meaningful happens: once, at the moment the value actually changes in a way worth persisting (such as when a new high score is achieved).

Problem 6. What was wrong with §6.5.2's first version of the save logic, and how did §6.5.3 fix it?

Solution

Step 1 — Identify the bug: §6.5.2's version called storeItem('courtyardHighScore', score) inside both overlaps callbacks, on every coin and bomb pickup, with no check of whether the score was actually a record. That means the key held the latest score, not the best score.

Step 2 — See the consequence: A run that peaked at 12 coins and then lost 15 points to bombs would end with 'courtyardHighScore' storing a value lower than the true high point the player reached — so the "high score" wasn't high at all. It also wrote to storage on every single pickup whether or not that was needed, which is the wasteful behavior from 6.5.5.

Step 3 — How §6.5.3 fixed it: Two changes. First, setup loads the real saved best with highScore = getItem('courtyardHighScore') || 0. Second, the save now happens only inside the coin callback's if (score > highScore) branch, which updates the in-memory highScore and calls storeItem only when the current score genuinely beats the stored one. Bombs no longer touch storage at all — they can only lower score, never raise the record.

Answer: §6.5.2 saved the current score on every pickup, so 'courtyardHighScore' ended up holding the latest score rather than the best score (and wrote to storage far more often than necessary). §6.5.3 loaded the true high score in setup with getItem(...) || 0 and saved only inside if (score > highScore), so storage is updated solely when a genuine new record occurs.

Problem 7. What happens if you call storeItem('courtyardHighScore', 50) and then storeItem('courtyardHighScore', 30)? Which value is stored?

Solution

Step 1 — Notice both calls use the same key: storeItem saves a value under a key name, and per the Insight Note in §6.5.2, saving again under the same key overwrites the previous value. One key holds one value — there is no history of past saves.

Step 2 — Trace the sequence: The first call writes 50 under 'courtyardHighScore'. The second call then replaces that entry with 30. Nothing remembers the 50; it's gone from storage.

Answer: The second call overwrites the first, so the value 30 is stored under 'courtyardHighScore'.

Problem 8. Write a sketch that tracks the number of times the player has jumped, saving the count to storage so it survives a page refresh.

Solution

Step 1 — Plan the logic: The count must (a) be loaded from storage in setup with the || 0 default pattern so it survives a refresh, (b) increment only when a jump actually happens — that's the existing jump condition kb.presses(' ') && player.colliding(ground) — and (c) be saved right there, at the meaningful moment, following §6.5.5's "don't save every frame" rule. Give it its own key name, 'courtyardJumpCount'.

Step 2 — Write the sketch:

Editor
runs in a sandboxed frame
▶ Press Run to see the output…

Step 3 — Check that it survives a refresh: Every real jump (space pressed while grounded) immediately increments jumpCount and writes the new total to storage — one save per jump, never per frame. On reload, setup runs getItem('courtyardJumpCount') || 0, restoring the previous total; the || 0 covers the first-ever run when the key doesn't exist yet.

Answer: The sketch above: it loads jumpCount in setup with getItem('courtyardJumpCount') || 0, increments it inside the jump condition, and saves it with storeItem right there, so the jump total persists across page refreshes.

Problem 9. Why is storeItem('courtyardStats', stats) (one object, one key) better than storeItem('courtyardHighScore', ...) plus storeItem('courtyardTotalCoins', ...) (two separate keys) once you have more than one related number to save?

Solution

Step 1 — Count the moving parts in each approach: With two separate keys, saving means two storeItem calls and loading means two getItem calls (each with its own || default). Every additional related field would add yet another key and another load/save pair to juggle. With one object, it's one storeItem('courtyardStats', stats) to save everything and one getItem('courtyardStats') || { highScore: 0, totalCoins: 0 } to load it all.

Step 2 — Consistency: The two numbers are related — they describe the same player's history — so they belong together. Saving one object means both fields are written in a single operation and restored together. With two separate keys, a forgotten save (or an interruption between the two writes) can leave them out of sync: for example, a freshly saved high score paired with a stale coin total.

Step 3 — Simplicity and the structured-data bonus: One key name to remember instead of two (or more), and per the Structured Save Data definition, getItem hands back the whole object directly — moSHion serializes and deserializes it for you, so stats.highScore works immediately with no parsing.

Answer: One object under one key takes a single save and a single load no matter how many related fields it holds, guarantees the fields stay consistent with each other (saved and restored together), and keeps the code simpler — versus maintaining a separate key plus getItem/storeItem pair and default for every individual number.

Problem 10. If stats is { highScore: 12, totalCoins: 40 }, what does getItem('courtyardStats') return after a page refresh — a string you'd need to parse, or the object itself?

Solution

Step 1 — Recall the Structured Save Data definition: An object passed to storeItem is returned by getItem as an equivalent object, not as a string. moSHion's storage layer serializes the object when saving and deserializes it when loading, so you never parse it yourself.

Step 2 — Apply it to this case: After the refresh, getItem('courtyardStats') returns the object { highScore: 12, totalCoins: 40 } — a real object with working properties. Code like stats.highScore or stats.totalCoins += 1 works immediately, which is exactly why §6.5.6's setup line can feed the result straight into stats.

Answer: It returns the object itself — { highScore: 12, totalCoins: 40 } — not a string needing parsing, because moSHion's storage layer serializes and deserializes structured save data automatically.

Key Terms

Term Definition
getItem A function that retrieves a previously stored value by key name; returns undefined if not found
Persistent storage Data that remains available after the program is closed and the browser is restarted
removeItem A function that deletes a stored value by key name
storeItem A function that saves a value under a key name to persistent browser storage
Structured save data An object or array passed to storeItem, returned as an equivalent object or array by getItem — not a string needing manual parsing