diff --git a/packages/@glimmer/integration-tests/lib/render-test.ts b/packages/@glimmer/integration-tests/lib/render-test.ts
index d08bffd4a2..6e5217afee 100644
--- a/packages/@glimmer/integration-tests/lib/render-test.ts
+++ b/packages/@glimmer/integration-tests/lib/render-test.ts
@@ -1,6 +1,6 @@
 import { Dict, Maybe, Option, RenderResult, Helper } from '@glimmer/interfaces';
 import { ASTPluginBuilder } from '@glimmer/syntax';
-import { bump, isConst } from '@glimmer/validator';
+import { bump, isConstTagged } from '@glimmer/validator';
 import { clearElement, dict, expect, assign } from '@glimmer/util';
 import { SimpleElement, SimpleNode } from '@simple-dom/interface';
 import {
@@ -393,7 +393,7 @@ export class RenderTest implements IRenderTest {
 
     let self = this.delegate.getSelf(this.context);
 
-    if (!isConst(self)) {
+    if (!isConstTagged(self)) {
       (self as UpdatableRootReference).forceUpdate(this.context);
     }
 
diff --git a/packages/@glimmer/reference/lib/template.ts b/packages/@glimmer/reference/lib/template.ts
index 75f765b8b4..64a356376b 100644
--- a/packages/@glimmer/reference/lib/template.ts
+++ b/packages/@glimmer/reference/lib/template.ts
@@ -10,7 +10,7 @@ import {
   updateTag,
   track,
   Revision,
-  isConst,
+  isConstTagged,
   isConstTag,
   valueForTag,
   validateTag,
@@ -150,7 +150,7 @@ export class HelperRootReference<T = unknown> extends RootReference<T> {
       this.didSetupDebugContext = true;
     }
 
-    if (isConst(args)) {
+    if (isConstTagged(args)) {
       this.compute();
     }
 
diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts
index 0d35bbb510..64ee34e1e7 100644
--- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts
+++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts
@@ -38,7 +38,7 @@ import {
   ModifierManager,
 } from '@glimmer/interfaces';
 import { VersionedPathReference, VersionedReference } from '@glimmer/reference';
-import { CONSTANT_TAG, isConst, isConstTag, Tag } from '@glimmer/validator';
+import { CONSTANT_TAG, isConstTagged, isConstTag, Tag } from '@glimmer/validator';
 import {
   assert,
   dict,
@@ -538,7 +538,7 @@ function setDeferredAttr(
     vm.elements().setStaticAttribute(name, value, namespace);
   } else {
     let attribute = vm.elements().setDynamicAttribute(name, value.value(), trusting, namespace);
-    if (!isConst(value)) {
+    if (!isConstTagged(value)) {
       vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute));
     }
   }
diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts
index 83cc3067a9..214d05b908 100644
--- a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts
+++ b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts
@@ -1,5 +1,5 @@
 import { Reference } from '@glimmer/reference';
-import { Tag, isConst } from '@glimmer/validator';
+import { Tag, isConstTagged } from '@glimmer/validator';
 import {
   check,
   CheckString,
@@ -77,7 +77,7 @@ APPEND_OPCODES.add(Op.AppendText, vm => {
 
   let node = vm.elements().appendDynamicText(value);
 
-  if (!isConst(reference)) {
+  if (!isConstTagged(reference)) {
     vm.updateWith(new DynamicTextContent(node, reference, value));
   }
 });
diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
index 541bed26f1..2e09d53911 100644
--- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
+++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
@@ -1,5 +1,12 @@
 import { Reference, ReferenceCache, VersionedReference } from '@glimmer/reference';
-import { Revision, Tag, isConst, isConstTag, valueForTag, validateTag } from '@glimmer/validator';
+import {
+  Revision,
+  Tag,
+  isConstTagged,
+  isConstTag,
+  valueForTag,
+  validateTag,
+} from '@glimmer/validator';
 import { check, CheckString, CheckElement, CheckOption, CheckNode } from '@glimmer/debug';
 import { Op, Option, ModifierManager } from '@glimmer/interfaces';
 import { $t0 } from '@glimmer/vm';
@@ -43,7 +50,7 @@ APPEND_OPCODES.add(Op.PushRemoteElement, vm => {
   let insertBefore: Maybe<SimpleNode>;
   let guid = guidRef.value() as string;
 
-  if (isConst(elementRef)) {
+  if (isConstTagged(elementRef)) {
     element = check(elementRef.value(), CheckElement);
   } else {
     let cache = new ReferenceCache(elementRef as Reference<SimpleElement>);
@@ -52,7 +59,7 @@ APPEND_OPCODES.add(Op.PushRemoteElement, vm => {
   }
 
   if (insertBeforeRef.value() !== undefined) {
-    if (isConst(insertBeforeRef)) {
+    if (isConstTagged(insertBeforeRef)) {
       insertBefore = check(insertBeforeRef.value(), CheckOption(CheckNode));
     } else {
       let cache = new ReferenceCache(insertBeforeRef as Reference<Option<SimpleNode>>);
@@ -163,7 +170,7 @@ APPEND_OPCODES.add(Op.DynamicAttr, (vm, { op1: _name, op2: trusting, op3: _names
 
   let attribute = vm.elements().setDynamicAttribute(name, value, !!trusting, namespace);
 
-  if (!isConst(reference)) {
+  if (!isConstTagged(reference)) {
     vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute));
   }
 });
diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts
index 68ae88b3a0..f33dd1d8e3 100644
--- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts
+++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts
@@ -1,6 +1,13 @@
 import { CompilableTemplate, Option, Op } from '@glimmer/interfaces';
 import { isModified, ReferenceCache } from '@glimmer/reference';
-import { CONSTANT_TAG, isConst, Revision, Tag, valueForTag, validateTag } from '@glimmer/validator';
+import {
+  CONSTANT_TAG,
+  isConstTagged,
+  Revision,
+  Tag,
+  valueForTag,
+  validateTag,
+} from '@glimmer/validator';
 import { initializeGuid, assert, isHandle, HandleConstants, decodeHandle } from '@glimmer/util';
 import {
   CheckNumber,
@@ -160,7 +167,7 @@ APPEND_OPCODES.add(Op.InvokeYield, vm => {
 APPEND_OPCODES.add(Op.JumpIf, (vm, { op1: target }) => {
   let reference = check(vm.stack.pop(), CheckReference);
 
-  if (isConst(reference)) {
+  if (isConstTagged(reference)) {
     if (reference.value()) {
       vm.goto(target);
     }
@@ -178,7 +185,7 @@ APPEND_OPCODES.add(Op.JumpIf, (vm, { op1: target }) => {
 APPEND_OPCODES.add(Op.JumpUnless, (vm, { op1: target }) => {
   let reference = check(vm.stack.pop(), CheckReference);
 
-  if (isConst(reference)) {
+  if (isConstTagged(reference)) {
     if (!reference.value()) {
       vm.goto(target);
     }
@@ -204,7 +211,7 @@ APPEND_OPCODES.add(Op.JumpEq, (vm, { op1: target, op2: comparison }) => {
 APPEND_OPCODES.add(Op.AssertSame, vm => {
   let reference = check(vm.stack.peek(), CheckReference);
 
-  if (!isConst(reference)) {
+  if (!isConstTagged(reference)) {
     vm.updateWith(Assert.initialize(new ReferenceCache(reference)));
   }
 });
diff --git a/packages/@glimmer/validator/index.ts b/packages/@glimmer/validator/index.ts
index e6bc7c08c4..1434462117 100644
--- a/packages/@glimmer/validator/index.ts
+++ b/packages/@glimmer/validator/index.ts
@@ -17,7 +17,7 @@ export {
   EntityTag,
   EntityTagged,
   INITIAL,
-  isConst,
+  isConstTagged,
   isConstTag,
   Revision,
   Tag,
@@ -39,12 +39,17 @@ export {
   consumeTag,
   isTracking,
   track,
-  trackedData,
   memo,
   untrack,
   isConstMemo,
+  Cache,
+  createCache,
+  isConst,
+  getValue,
 } from './lib/tracking';
 
+export { trackedData } from './lib/tracked-data';
+
 export {
   setAutotrackingTransactionEnv,
   runInAutotrackingTransaction,
diff --git a/packages/@glimmer/validator/lib/tracked-data.ts b/packages/@glimmer/validator/lib/tracked-data.ts
new file mode 100644
index 0000000000..84fe523ce8
--- /dev/null
+++ b/packages/@glimmer/validator/lib/tracked-data.ts
@@ -0,0 +1,42 @@
+import { DEBUG } from '@glimmer/env';
+import { tagFor, dirtyTagFor } from './meta';
+import { assertTagNotConsumed } from './debug';
+import { consumeTag } from './tracking';
+
+export type Getter<T, K extends keyof T> = (self: T) => T[K] | undefined;
+export type Setter<T, K extends keyof T> = (self: T, value: T[K]) => void;
+
+export function trackedData<T extends object, K extends keyof T>(
+  key: K,
+  initializer?: (this: T) => T[K]
+): { getter: Getter<T, K>; setter: Setter<T, K> } {
+  let values = new WeakMap<T, T[K]>();
+  let hasInitializer = typeof initializer === 'function';
+
+  function getter(self: T) {
+    consumeTag(tagFor(self, key));
+
+    let value;
+
+    // If the field has never been initialized, we should initialize it
+    if (hasInitializer && !values.has(self)) {
+      value = initializer!.call(self);
+      values.set(self, value);
+    } else {
+      value = values.get(self);
+    }
+
+    return value;
+  }
+
+  function setter(self: T, value: T[K]): void {
+    if (DEBUG) {
+      assertTagNotConsumed!(tagFor(self, key), self, key, true);
+    }
+
+    dirtyTagFor(self, key);
+    values.set(self, value);
+  }
+
+  return { getter, setter };
+}
diff --git a/packages/@glimmer/validator/lib/tracking.ts b/packages/@glimmer/validator/lib/tracking.ts
index f5a041063b..e3cfa2cc22 100644
--- a/packages/@glimmer/validator/lib/tracking.ts
+++ b/packages/@glimmer/validator/lib/tracking.ts
@@ -1,7 +1,16 @@
 import { DEBUG } from '@glimmer/env';
-import { Tag, combine, CONSTANT_TAG, validateTag, Revision, valueForTag } from './validators';
-import { tagFor, dirtyTagFor } from './meta';
-import { markTagAsConsumed, runInAutotrackingTransaction, assertTagNotConsumed } from './debug';
+import {
+  Tag,
+  combine,
+  CONSTANT_TAG,
+  validateTag,
+  Revision,
+  valueForTag,
+  isConstTag,
+} from './validators';
+
+import { markTagAsConsumed, runInAutotrackingTransaction } from './debug';
+import { symbol } from './utils';
 
 type Option<T> = T | null;
 
@@ -72,88 +81,145 @@ export function endTrackFrame(): Tag {
   return current!.combine();
 }
 
+export function isTracking() {
+  return CURRENT_TRACKER !== null;
+}
+
+export function consumeTag(tag: Tag) {
+  if (CURRENT_TRACKER !== null) {
+    CURRENT_TRACKER.add(tag);
+  }
+}
+
 //////////
 
-const IS_CONST_MAP: WeakMap<object, boolean | undefined> = new WeakMap();
-
-export function memo<T>(cb: () => T, context?: string | false): () => T;
-export function memo<T, U>(cb: (a1: U) => T, context?: string | false): (a1: U) => T;
-export function memo<T, U, V, W>(
-  cb: (a1: U, a2: V, a3: W) => T,
-  context?: string | false
-): (a1: U, a2: V, a3: W) => T;
-export function memo<T, U, V, W, X>(
-  cb: (a1: U, a2: V, a3: W, a4: X) => T,
-  context?: string | false
-): (a1: U, a2: V, a3: W, a4: X) => T;
-export function memo<T, U, V, W, X, Y>(
-  cb: (a1: U, a2: V, a3: W, a4: X, a5: Y) => T,
-  context?: string | false
-): (a1: U, a2: V, a3: W, a4: X, a5: Y) => T;
-export function memo<T, U, V, W, X, Y, Z>(
-  cb: (a1: U, a2: V, a3: W, a4: X, a5: Y, a6: Z) => T,
-  context?: string | false
-): (a1: U, a2: V, a3: W, a4: X, a5: Y, a6: Z) => T;
-
-export function memo<T>(callback: (...args: unknown[]) => T, debuggingContext?: string | false) {
-  let lastValue: T | undefined;
-  let tag: Tag;
-  let snapshot: Revision;
-
-  let memoized = (...args: unknown[]): T => {
-    if (!tag || !validateTag(tag, snapshot)) {
-      beginTrackFrame();
-
-      try {
-        if (DEBUG) {
-          runInAutotrackingTransaction!(() => (lastValue = callback(...args)), debuggingContext);
-        } else {
-          lastValue = callback(...args);
-        }
-      } finally {
-        tag = endTrackFrame();
-        snapshot = valueForTag(tag);
-        consumeTag(tag);
-
-        // If the final tag is constant, then we know for sure that this
-        // memoized function can never change. There are times when this
-        // information is useful externally (i.e. in the append VM, it tells us
-        // whether or not to emit opcodes) so we expose it via a metadata weakmap.
-        if (tag === CONSTANT_TAG) {
-          IS_CONST_MAP.set(memoized, true);
-        } else if (DEBUG) {
-          // In DEBUG, set the value to false explicitly. This way we can throw
-          // if someone attempts to call `isConst(memoized)` before running
-          // `memoized()` at least once.
-          IS_CONST_MAP.set(memoized, false);
-        }
-      }
-    } else {
-      consumeTag(tag);
-    }
+const CACHE_KEY: unique symbol = symbol('CACHE_KEY');
+
+export function memo<T>(callback: () => T, debuggingContext?: string | false) {
+  let cache = createCache(callback, debuggingContext);
+
+  let memoized = () => getValue(cache);
+
+  (memoized as any)[CACHE_KEY] = cache;
+
+  return memoized;
+}
+
+export function isConstMemo(memoized: Function) {
+  let cache = (memoized as any)[CACHE_KEY] as Cache | undefined;
+
+  return cache === undefined ? false : isConst(cache);
+}
 
-    return lastValue!;
+//////////
+
+// public interface
+export interface Cache<T = unknown> {
+  [CACHE_KEY]: T;
+}
+
+const FN: unique symbol = symbol('FN');
+const LAST_VALUE: unique symbol = symbol('LAST_VALUE');
+const TAG: unique symbol = symbol('TAG');
+const SNAPSHOT: unique symbol = symbol('SNAPSHOT');
+const DEBUG_LABEL: unique symbol = symbol('DEBUG_LABEL');
+
+interface InternalCache<T = unknown> {
+  [FN]: (...args: unknown[]) => T;
+  [LAST_VALUE]: T | undefined;
+  [TAG]: Tag | undefined;
+  [SNAPSHOT]: Revision;
+  [DEBUG_LABEL]?: string | false;
+}
+
+export function createCache<T>(fn: () => T, debuggingLabel?: string | false): Cache<T> {
+  if (DEBUG && !(typeof fn === 'function')) {
+    throw new Error(
+      `createCache() must be passed a function as its first parameter. Called with: ${String(fn)}`
+    );
+  }
+
+  let cache: InternalCache<T> = {
+    [FN]: fn,
+    [LAST_VALUE]: undefined,
+    [TAG]: undefined,
+    [SNAPSHOT]: -1,
   };
 
   if (DEBUG) {
-    IS_CONST_MAP.set(memoized, undefined);
+    cache[DEBUG_LABEL] = debuggingLabel;
   }
 
-  return memoized;
+  return (cache as unknown) as Cache<T>;
 }
 
-export function isConstMemo(memoized: Function) {
-  if (DEBUG && IS_CONST_MAP.has(memoized) && IS_CONST_MAP.get(memoized) === undefined) {
+export function getValue<T>(cache: Cache<T>): T {
+  if (!isCache(cache)) {
+    throw new Error(
+      `getValue() can only be used on an instance of a cache created with createCache(). Called with: ${String(
+        cache
+      )}`
+    );
+  }
+
+  let fn = cache[FN];
+  let tag = cache[TAG];
+  let snapshot = cache[SNAPSHOT];
+
+  if (tag === undefined || !validateTag(tag, snapshot)) {
+    beginTrackFrame();
+
+    try {
+      if (DEBUG) {
+        runInAutotrackingTransaction!(() => (cache[LAST_VALUE] = fn()), cache[DEBUG_LABEL]);
+      } else {
+        cache[LAST_VALUE] = fn();
+      }
+    } finally {
+      tag = endTrackFrame();
+      cache[TAG] = tag;
+      cache[SNAPSHOT] = valueForTag(tag);
+      consumeTag(tag);
+    }
+  } else {
+    consumeTag(tag);
+  }
+
+  return cache[LAST_VALUE]!;
+}
+
+export function isConst(cache: Cache) {
+  if (!isCache(cache)) {
+    throw new Error(
+      `isConst() can only be used on an instance of a cache created with createCache(). Called with: ${String(
+        cache
+      )}`
+    );
+  }
+
+  if (DEBUG && cache[TAG] === undefined) {
     throw new Error(
-      'Attempted to call `isConstMemo` on a memoized function, but the function has not been run at least once yet. You cannot know if a memoized function is constant or not until it has been run at least once. Call the function, then pass it to `isConstMemo`.'
+      `isConst() can only be used on a cache once getValue() has been called at least once. Called with cache function:\n\n${String(
+        cache[FN]
+      )}`
     );
   }
 
-  return IS_CONST_MAP.get(memoized) === true;
+  return isConstTag(cache[TAG]!);
+}
+
+function isCache<T>(value: Cache<T> | InternalCache<T>): value is InternalCache<T> {
+  return DEBUG ? FN in value : true;
 }
 
 //////////
 
+// Legacy tracking APIs
+
+// track() shouldn't be necessary at all in the VM once the autotracking
+// refactors are merged, and we should generally be moving away from it. It may
+// be necessary in Ember for a while longer, but I think we'll be able to drop
+// it in favor of cache sooner rather than later.
 export function track(callback: () => void, debuggingContext?: string | false): Tag {
   beginTrackFrame();
 
@@ -172,16 +238,10 @@ export function track(callback: () => void, debuggingContext?: string | false):
   return tag;
 }
 
-export function consumeTag(tag: Tag) {
-  if (CURRENT_TRACKER !== null) {
-    CURRENT_TRACKER.add(tag);
-  }
-}
-
-export function isTracking() {
-  return CURRENT_TRACKER !== null;
-}
-
+// untrack() is currently mainly used to handle places that were previously not
+// tracked, and that tracking now would cause backtracking rerender assertions.
+// I think once we move everyone forward onto modern APIs, we'll probably be
+// able to remove it, but I'm not sure yet.
 export function untrack(callback: () => void) {
   OPEN_TRACK_FRAMES.push(CURRENT_TRACKER);
   CURRENT_TRACKER = null;
@@ -192,43 +252,3 @@ export function untrack(callback: () => void) {
     CURRENT_TRACKER = OPEN_TRACK_FRAMES.pop()!;
   }
 }
-
-//////////
-
-export type Getter<T, K extends keyof T> = (self: T) => T[K] | undefined;
-export type Setter<T, K extends keyof T> = (self: T, value: T[K]) => void;
-
-export function trackedData<T extends object, K extends keyof T>(
-  key: K,
-  initializer?: (this: T) => T[K]
-): { getter: Getter<T, K>; setter: Setter<T, K> } {
-  let values = new WeakMap<T, T[K]>();
-  let hasInitializer = typeof initializer === 'function';
-
-  function getter(self: T) {
-    consumeTag(tagFor(self, key));
-
-    let value;
-
-    // If the field has never been initialized, we should initialize it
-    if (hasInitializer && !values.has(self)) {
-      value = initializer!.call(self);
-      values.set(self, value);
-    } else {
-      value = values.get(self);
-    }
-
-    return value;
-  }
-
-  function setter(self: T, value: T[K]): void {
-    if (DEBUG) {
-      assertTagNotConsumed!(tagFor(self, key), self, key, true);
-    }
-
-    dirtyTagFor(self, key);
-    values.set(self, value);
-  }
-
-  return { getter, setter };
-}
diff --git a/packages/@glimmer/validator/lib/validators.ts b/packages/@glimmer/validator/lib/validators.ts
index 82c56961b0..9c032c470c 100644
--- a/packages/@glimmer/validator/lib/validators.ts
+++ b/packages/@glimmer/validator/lib/validators.ts
@@ -241,7 +241,7 @@ export function createUpdatableTag(): UpdatableTag {
 
 export const CONSTANT_TAG = new MonomorphicTagImpl(MonomorphicTagTypes.Constant) as ConstantTag;
 
-export function isConst({ tag }: Tagged): boolean {
+export function isConstTagged({ tag }: Tagged): boolean {
   return tag === CONSTANT_TAG;
 }
 
diff --git a/packages/@glimmer/validator/test/tracking-test.ts b/packages/@glimmer/validator/test/tracking-test.ts
index 4437ed876c..fcfc0a53e5 100644
--- a/packages/@glimmer/validator/test/tracking-test.ts
+++ b/packages/@glimmer/validator/test/tracking-test.ts
@@ -21,6 +21,9 @@ import {
   untrack,
   validateTag,
   valueForTag,
+  createCache,
+  isConst,
+  getValue,
 } from '..';
 
 module('@glimmer/validator: tracking', () => {
@@ -386,7 +389,165 @@ module('@glimmer/validator: tracking', () => {
 
         assert.throws(
           () => isConstMemo(fn),
-          /Error: Attempted to call `isConstMemo` on a memoized function/
+          /Error: isConst\(\) can only be used on a cache once getValue\(\) has been called at least once/
+        );
+      });
+    }
+  });
+
+  module('tracking cache', () => {
+    test('it memoizes based on tags that are consumed within a track frame', assert => {
+      let tag1 = createTag();
+      let tag2 = createTag();
+      let count = 0;
+
+      let cache = createCache(() => {
+        consumeTag(tag1);
+        consumeTag(tag2);
+
+        return ++count;
+      });
+
+      assert.equal(getValue(cache), 1, 'called correctly the first time');
+      assert.equal(getValue(cache), 1, 'memoized result returned second time');
+
+      dirtyTag(tag1);
+      assert.equal(getValue(cache), 2, 'cache busted when tag1 dirtied');
+      assert.equal(getValue(cache), 2, 'memoized result returned when nothing dirtied');
+
+      dirtyTag(tag2);
+      assert.equal(getValue(cache), 3, 'cache busted when tag2 dirtied');
+      assert.equal(getValue(cache), 3, 'memoized result returned when nothing dirtied');
+    });
+
+    test('it ignores tags consumed within an untrack frame', assert => {
+      let tag1 = createTag();
+      let tag2 = createTag();
+      let count = 0;
+
+      let cache = createCache(() => {
+        consumeTag(tag1);
+
+        untrack(() => consumeTag(tag2));
+
+        return ++count;
+      });
+
+      assert.equal(getValue(cache), 1, 'called correctly the first time');
+      assert.equal(getValue(cache), 1, 'memoized result returned second time');
+
+      dirtyTag(tag1);
+      assert.equal(getValue(cache), 2, 'cache busted when tag1 dirtied');
+      assert.equal(getValue(cache), 2, 'memoized result returned when nothing dirtied');
+
+      dirtyTag(tag2);
+      assert.equal(getValue(cache), 2, 'cache not busted when tag2 dirtied');
+    });
+
+    test('nested memoizations work, and automatically propogate', assert => {
+      let innerTag = createTag();
+      let outerTag = createTag();
+
+      let innerCount = 0;
+      let outerCount = 0;
+
+      let innerCache = createCache(() => {
+        consumeTag(innerTag);
+
+        return ++innerCount;
+      });
+
+      let outerCache = createCache(() => {
+        consumeTag(outerTag);
+
+        return [++outerCount, getValue(innerCache)];
+      });
+
+      assert.deepEqual(
+        getValue(outerCache),
+        [1, 1],
+        'both functions called correctly the first time'
+      );
+      assert.deepEqual(getValue(outerCache), [1, 1], 'memoized result returned correctly');
+
+      dirtyTag(outerTag);
+
+      assert.deepEqual(
+        getValue(outerCache),
+        [2, 1],
+        'outer result updated, inner result still memoized'
+      );
+      assert.deepEqual(getValue(outerCache), [2, 1], 'memoized result returned correctly');
+
+      dirtyTag(innerTag);
+
+      assert.deepEqual(getValue(outerCache), [3, 2], 'both inner and outer result updated');
+      assert.deepEqual(getValue(outerCache), [3, 2], 'memoized result returned correctly');
+    });
+
+    test('isTracking works within a memoized function and untrack frame', assert => {
+      assert.expect(3);
+      assert.notOk(isTracking());
+
+      let cache = createCache(() => {
+        assert.ok(isTracking());
+
+        untrack(() => {
+          assert.notOk(isTracking());
+        });
+      });
+
+      getValue(cache);
+    });
+
+    test('isConst allows users to check if a memoized function is constant', assert => {
+      let tag = createTag();
+
+      let constCache = createCache(() => {
+        // do nothing;
+      });
+
+      let nonConstCache = createCache(() => {
+        consumeTag(tag);
+      });
+
+      getValue(constCache);
+      getValue(nonConstCache);
+
+      assert.ok(isConst(constCache), 'constant cache returns true');
+      assert.notOk(isConst(nonConstCache), 'non-constant cache returns false');
+    });
+
+    if (DEBUG) {
+      test('createCache throws an error in DEBUG mode if users to use with a non-function', assert => {
+        assert.throws(
+          () => createCache(123 as any),
+          /Error: createCache\(\) must be passed a function as its first parameter. Called with: 123/
+        );
+      });
+
+      test('getValue throws an error in DEBUG mode if users to use with a non-cache', assert => {
+        assert.throws(
+          () => getValue(123 as any),
+          /Error: getValue\(\) can only be used on an instance of a cache created with createCache\(\). Called with: 123/
+        );
+      });
+
+      test('isConst throws an error in DEBUG mode if users attempt to check a function before it has been called', assert => {
+        let cache = createCache(() => {
+          // do nothing;
+        });
+
+        assert.throws(
+          () => isConst(cache),
+          /Error: isConst\(\) can only be used on a cache once getValue\(\) has been called at least once/
+        );
+      });
+
+      test('isConst throws an error in DEBUG mode if users attempt to use with a non-cache', assert => {
+        assert.throws(
+          () => isConst(123 as any),
+          /Error: isConst\(\) can only be used on an instance of a cache created with createCache\(\). Called with: 123/
         );
       });
     }