Trace Cache Keys, Deletions, and Repopulation in JavaScript

Computer Science

Trace Cache Keys, Deletions, and Repopulation in JavaScript

JavaScriptNode.jsNode.js built-in http module

Trace Cache Keys, Deletions, and Repopulation in JavaScript

Build and investigate a small Node.js cache service that makes cache behavior visible. You will follow a request from its input through key construction, observe reads and writes, prove whether deletion succeeded, and identify when another layer or cache instance repopulates the value.

3 modules9 lessonsComputer ScienceJavaScriptNode.jsNode.js built-in http moduleNode.js built-in URL APIcurl

What you learn by building this

  • Trace how a request becomes a cache key and identify mismatches between read, write, and delete paths
  • Use cache hit, miss, set, and delete evidence to determine what actually happened
  • Separate a successful deletion from a value that was immediately recreated
  • Recognize symptoms of multiple cache instances or layered cache behavior
  • Run focused HTTP requests against the finished diagnostic example and explain the observed results

Learning Journey

1

Make Cache Behavior Visible

3 lessons

Create the standalone cache model and a tiny HTTP surface that can be run immediately. The learner first gains direct evidence for keys, hits, misses, writes, and deletes before investigating misleading behavior.

2

Prove Deletion Versus Repopulation

3 lessons

Use the running cache service to establish what deletion proves and then introduce controlled repopulation. The project becomes a diagnostic experiment rather than a cache that merely appears to work.

3

Find Multiple Instances and Layers

3 lessons

Extend the example just enough to reproduce the confusing case where one cache does not see another cache's deletion. The finished project lets the learner distinguish a bad key from separate cache state.

Public lesson

Create an Observable In-Memory Cache

Observable In-Memory Cache

A Map already stores key–value pairs, but it does not explain what happened when your code uses it. A missing key and a key whose value is undefined can also be easy to confuse when you only inspect the returned value.

You will wrap Map so each operation returns a structured event and stores that event in an evidence history. There is no server involved: the cache and its observations live in memory.

Predict

What will happen?

First, predict the evidence

Imagine this sequence:

cache.set("theme", "dark");
cache.get("theme");
cache.get("language");
cache.delete("theme");
cache.clear();

What should each operation be able to tell you?

In particular:

  • How can a get show both the value and whether the key existed?
  • How can delete distinguish “the key was deleted” from “nothing changed”?
  • What should clear report, since it affects more than one key?

Use those questions to shape the event objects rather than relying on plain strings such as "deleted".

Tasks

Add the cache

In the existing JavaScript file for the practice project, add this class skeleton. The constructor and record helper are provided because they only hold state and centralize history recording. The four cache operations are yours to complete.

class ObservableCache {
  constructor() {
    this.store = new Map();
    this.history = [];
  }

  record(event) {
    this.history.push(event);
    return event;
  }

  get(key) {
    // Check whether the key exists separately from reading its value.
    // Return and record an event shaped like:
    // {
    //   operation: "get",
    //   key,
    //   result: { value: ..., hit: ... }
    // }
  }

  set(key, value) {
    // Record whether this key was already present before changing it.
    // Return and record an event shaped like:
    // {
    //   operation: "set",
    //   key,
    //   result: { value: ..., replaced: ... }
    // }
  }

  delete(key) {
    // Map.delete already gives you a useful boolean result.
    // Return and record an event shaped like:
    // {
    //   operation: "delete",
    //   key,
    //   result: { deleted: ... }
    // }
  }

  clear() {
    // Capture the keys before clearing them. This makes the evidence show
    // exactly which keys were affected.
    // Return and record an event shaped like:
    // {
    //   operation: "clear",
    //   key: null,
    //   result: { keys: [...], cleared: ... }
    // }
  }
}

The important detail in get is to use both has and get:

const hit = this.store.has(key);
const value = this.store.get(key);

Checking only value would not tell you whether a missing key and a stored undefined are different cases.

For set, check whether the key exists before calling set. Otherwise every set would look like a replacement.

For clear, Map.clear() does not return the removed keys. Take a snapshot first:

const keys = [...this.store.keys()];

Then clear the map and record that snapshot.

Each method should finish by passing its event to this.record(event). That gives the caller immediate evidence while also preserving the complete operation history.

Tasks

Try a small scenario

Below the class, add this usage code:

const cache = new ObservableCache();

const firstSet = cache.set("theme", "dark");
const firstGet = cache.get("theme");
const missingGet = cache.get("language");
const firstDelete = cache.delete("theme");
const secondDelete = cache.delete("theme");

console.log(firstSet);
console.log(firstGet);
console.log(missingGet);
console.log(firstDelete);
console.log(secondDelete);
console.log(cache.history);

Run the project using its documented command.

Inspect the output and compare it with these expectations:

  • The first set reports replaced: false.
  • The get("theme") reports { value: "dark", hit: true }.
  • The missing get("language") reports hit: false.
  • The first delete reports deleted: true.
  • The second delete reports deleted: false.
  • cache.history contains the same five events in operation order.

The missing lookup is the useful surprise here: its evidence should remain informative even though its value is undefined.

Tasks

Check replacement and clearing

Extend the scenario:

cache.set("theme", "light");
cache.set("fontSize", 16);

const clearEvent = cache.clear();

console.log(clearEvent);
console.log(cache.history);
console.log(cache.store.size);

Now check that:

  • Setting "theme" the second time reports replaced: true.
  • The clear event lists the keys that existed immediately before clearing.
  • clearEvent.result.cleared equals the number of listed keys.
  • cache.store.size is 0.
  • The clear event is also the final item in cache.history.

If the clear event lists no keys, the snapshot was probably taken after clear(), which is too late. If replacement is always false, the presence check is probably happening after the write.

What the wrapper changes

Without the wrapper, callers get primitive answers:

map.get("theme");       // perhaps a value, perhaps undefined
map.delete("theme");    // true or false
map.clear();             // no useful return value

That is enough for storage, but not enough for inspection. The cache now makes each action explain itself:

{
  operation: "delete",
  key: "theme",
  result: { deleted: true }
}

The history array also makes the cache observable after the fact. You can inspect the sequence of writes, reads, misses, deletes, and clears without adding a web server or changing the underlying Map behavior.

Course Outline

3 modules · 9 lessons

Make Cache Behavior Visible

Prove Deletion Versus Repopulation

Find Multiple Instances and Layers

Learn by building your own version.

Remix this public project to open the workspace, follow the guided build, and let the AI mentor teach you through the work instead of doing it for you.