# Cache invalidation strategy implemented

- **Pattern:** `ab-001883` (`mobile-offline-storage.sync-conflicts.cache-invalidation`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001883
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001883)

## Why it matters

A cache with no invalidation strategy serves stale data indefinitely. After a server-side update — price change, policy revision, content correction — users continue seeing the cached version until they clear app data or reinstall. ISO 25010:2011 reliability.recoverability requires a defined freshness boundary. Beyond staleness, unbounded caches accumulate sensitive data (PII, session-specific content) that should have been evicted after use. GDPR Art. 32 expects that personal data is not retained beyond its necessary lifetime — a cache without TTL enforcement is a retention policy violation by omission.

## Severity rationale

High because indefinitely-cached data can include stale PII or pricing that misleads users and may violate GDPR Art. 32 data minimization obligations.

## Remediation

Wrap every cache entry with a timestamp and a TTL. Evict on read when the entry is expired, and run a periodic cleanup to prevent storage accumulation.

```tsx
interface CacheEntry<T> { data: T; storedAt: number; ttl: number; }

export class Cache<T> {
  private store = new Map<string, CacheEntry<T>>();

  set(key: string, data: T, ttl = 5 * 60_000) {
    this.store.set(key, { data, storedAt: Date.now(), ttl });
  }

  get(key: string): T | undefined {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (Date.now() - entry.storedAt > entry.ttl) {
      this.store.delete(key);
      return undefined;
    }
    return entry.data;
  }

  invalidate(key: string) { this.store.delete(key); }
}

// Always invalidate after a mutation:
await updateUser(id, data);
userCache.invalidate(`user_${id}`);
```

Set TTLs no higher than 86,400 seconds (24 hours). For data containing PII, use a tighter TTL (≤3,600 seconds) aligned with your data retention policy.

## Detection

- **ID:** `cache-invalidation`
- **Severity:** `high`
- **What to look for:** Examine cache implementation. Look for TTL (time-to-live), versioning, or event-based invalidation. Check that stale cache is cleared when updates occur.
- **Pass criteria:** Count all cache-related storage operations. Cache has at least 1 documented invalidation strategy: TTL-based expiration (with a defined TTL of no more than 86400 seconds / 24 hours), manual invalidation on updates, or server-driven invalidation. At least 1 cache cleanup or expiration mechanism must exist.
- **Fail criteria:** No cache invalidation found. Cached data could be served indefinitely even after it is updated remotely.
- **Skip (N/A) when:** Never — cache invalidation is essential for data freshness.
- **Detail on fail:** `"No cache invalidation strategy found. Cache persists indefinitely, so users may see stale data until they manually clear cache."`
- **Remediation:** Implement TTL-based cache invalidation:

  ```tsx
  interface CacheEntry<T> {
    data: T;
    timestamp: number;
    ttl: number; // milliseconds
  }

  class Cache<T> {
    private store = new Map<string, CacheEntry<T>>();

    async get(key: string, fetch: () => Promise<T>, ttl = 5 * 60 * 1000): Promise<T> {
      const entry = this.store.get(key);

      if (entry && Date.now() - entry.timestamp < entry.ttl) {
        // Cache hit and not expired
        return entry.data;
      }

      // Cache miss or expired, fetch fresh data
      const data = await fetch();
      this.store.set(key, {
        data,
        timestamp: Date.now(),
        ttl,
      });

      return data;
    }

    invalidate(key: string) {
      this.store.delete(key);
    }

    invalidateAll() {
      this.store.clear();
    }
  }

  // Usage:
  const cache = new Cache<User>();

  const user = await cache.get(`user_${id}`, () => fetchUser(id), 10 * 60 * 1000); // 10 min TTL

  // After updating user, invalidate cache
  await updateUser(id, data);
  cache.invalidate(`user_${id}`);
  ```

## External references

- iso-25010 reliability.recoverability
- gdpr Art. 32

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-001883
