diff --git a/package-lock.json b/package-lock.json
index 73e896c..ea8a001 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,14 +1,15 @@
 {
   "name": "optimism",
-  "version": "0.17.5",
+  "version": "0.18.0-pre.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "optimism",
-      "version": "0.17.5",
+      "version": "0.18.0-pre.0",
       "license": "MIT",
       "dependencies": {
+        "@wry/caches": "^1.0.0",
         "@wry/context": "^0.7.0",
         "@wry/trie": "^0.4.3",
         "tslib": "^2.3.0"
@@ -35,6 +36,17 @@
       "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==",
       "dev": true
     },
+    "node_modules/@wry/caches": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.0.tgz",
+      "integrity": "sha512-FHRUDe2tqrXAj6A/1D39No68lFWbbnh+NCpG9J/6idhL/2Mb/AaxBTYg/sbUVImEo8a4mWeOewUlB1W7uLjByA==",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/@wry/context": {
       "version": "0.7.3",
       "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz",
@@ -1194,6 +1206,14 @@
       "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==",
       "dev": true
     },
+    "@wry/caches": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.0.tgz",
+      "integrity": "sha512-FHRUDe2tqrXAj6A/1D39No68lFWbbnh+NCpG9J/6idhL/2Mb/AaxBTYg/sbUVImEo8a4mWeOewUlB1W7uLjByA==",
+      "requires": {
+        "tslib": "^2.3.0"
+      }
+    },
     "@wry/context": {
       "version": "0.7.3",
       "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz",
diff --git a/package.json b/package.json
index 9a3a2e7..5f136e2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "optimism",
-  "version": "0.17.5",
+  "version": "0.18.0-pre.0",
   "author": "Ben Newman <ben@benjamn.com>",
   "description": "Composable reactive caching with efficient invalidation.",
   "keywords": [
@@ -47,6 +47,7 @@
     "typescript": "^5.0.2"
   },
   "dependencies": {
+    "@wry/caches": "^1.0.0",
     "@wry/context": "^0.7.0",
     "@wry/trie": "^0.4.3",
     "tslib": "^2.3.0"
diff --git a/src/cache.ts b/src/cache.ts
deleted file mode 100644
index 9338ab5..0000000
--- a/src/cache.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-interface Node<K, V> {
-  key: K;
-  value: V;
-  newer: Node<K, V> | null;
-  older: Node<K, V> | null;
-}
-
-function defaultDispose() {}
-
-export class Cache<K = any, V = any> {
-  private map = new Map<K, Node<K, V>>();
-  private newest: Node<K, V> | null = null;
-  private oldest: Node<K, V> | null = null;
-
-  constructor(
-    private max = Infinity,
-    public dispose: (value: V, key: K) => void = defaultDispose,
-  ) {}
-
-  public has(key: K): boolean {
-    return this.map.has(key);
-  }
-
-  public get(key: K): V | undefined {
-    const node = this.getNode(key);
-    return node && node.value;
-  }
-
-  private getNode(key: K): Node<K, V> | undefined {
-    const node = this.map.get(key);
-
-    if (node && node !== this.newest) {
-      const { older, newer } = node;
-
-      if (newer) {
-        newer.older = older;
-      }
-
-      if (older) {
-        older.newer = newer;
-      }
-
-      node.older = this.newest;
-      node.older!.newer = node;
-
-      node.newer = null;
-      this.newest = node;
-
-      if (node === this.oldest) {
-        this.oldest = newer;
-      }
-    }
-
-    return node;
-  }
-
-  public set(key: K, value: V): V {
-    let node = this.getNode(key);
-    if (node) {
-      return node.value = value;
-    }
-
-    node = {
-      key,
-      value,
-      newer: null,
-      older: this.newest
-    };
-
-    if (this.newest) {
-      this.newest.newer = node;
-    }
-
-    this.newest = node;
-    this.oldest = this.oldest || node;
-
-    this.map.set(key, node);
-
-    return node.value;
-  }
-
-  public clean() {
-    while (this.oldest && this.map.size > this.max) {
-      this.delete(this.oldest.key);
-    }
-  }
-
-  public delete(key: K): boolean {
-    const node = this.map.get(key);
-    if (node) {
-      if (node === this.newest) {
-        this.newest = node.older;
-      }
-
-      if (node === this.oldest) {
-        this.oldest = node.newer;
-      }
-
-      if (node.newer) {
-        node.newer.older = node.older;
-      }
-
-      if (node.older) {
-        node.older.newer = node.newer;
-      }
-
-      this.map.delete(key);
-      this.dispose(node.value, key);
-
-      return true;
-    }
-
-    return false;
-  }
-}
diff --git a/src/helpers.ts b/src/helpers.ts
index 1a4dad5..0c01ebc 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -1,3 +1,5 @@
+export type NoInfer<T> = [T][T extends any ? 0 : never];
+
 export const {
   hasOwnProperty,
 } = Object.prototype;
diff --git a/src/index.ts b/src/index.ts
index 6b0ae5e..7a7a0b4 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,8 +1,9 @@
 import { Trie } from "@wry/trie";
 
-import { Cache } from "./cache.js";
+import { StrongCache, CommonCache } from "@wry/caches";
 import { Entry, AnyEntry } from "./entry.js";
 import { parentEntrySlot } from "./context.js";
+import type { NoInfer } from "./helpers.js";
 
 // These helper functions are important for making optimism work with
 // asynchronous code. In order to register parent-child dependencies,
@@ -55,7 +56,7 @@ export type OptimisticWrapperFunction<
   readonly size: number;
 
   // Snapshot of wrap options used to create this wrapper function.
-  options: OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey>;
+  options: OptionsWithCacheInstance<TArgs, TKeyArgs, TCacheKey>;
 
   // "Dirty" any cached Entry stored for the given arguments, marking that Entry
   // and its ancestors as potentially needing to be recomputed. The .dirty(...)
@@ -89,10 +90,16 @@ export type OptimisticWrapperFunction<
   makeCacheKey: (...args: TKeyArgs) => TCacheKey;
 };
 
+export { CommonCache }
+export interface CommonCacheConstructor<TCacheKey, TResult, TArgs extends any[]> extends Function {
+  new <K extends TCacheKey, V extends Entry<TArgs, TResult>>(max?: number, dispose?: (value: V, key?: K) => void): CommonCache<K,V>;
+}
+
 export type OptimisticWrapOptions<
   TArgs extends any[],
   TKeyArgs extends any[] = TArgs,
   TCacheKey = any,
+  TResult = any,
 > = {
   // The maximum number of cache entries that should be retained before the
   // cache begins evicting the oldest ones.
@@ -103,13 +110,24 @@ export type OptimisticWrapOptions<
   // The makeCacheKey function takes the same arguments that were passed to
   // the wrapper function and returns a single value that can be used as a key
   // in a Map to identify the cached result.
-  makeCacheKey?: (...args: TKeyArgs) => TCacheKey;
+  makeCacheKey?: (...args: NoInfer<TKeyArgs>) => TCacheKey;
   // If provided, the subscribe function should either return an unsubscribe
   // function or return nothing.
   subscribe?: (...args: TArgs) => void | (() => any);
+  cache?: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>
+    | CommonCacheConstructor<NoInfer<TCacheKey>, NoInfer<TResult>, NoInfer<TArgs>>;
 };
 
-const caches = new Set<Cache<any, AnyEntry>>();
+export interface OptionsWithCacheInstance<
+  TArgs extends any[],
+  TKeyArgs extends any[] = TArgs,
+  TCacheKey = any,
+  TResult = any,
+> extends OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult> {
+  cache: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>;
+};
+
+const caches = new Set<CommonCache<any, AnyEntry>>();
 
 export function wrap<
   TArgs extends any[],
@@ -118,14 +136,15 @@ export function wrap<
   TCacheKey = any,
 >(originalFunction: (...args: TArgs) => TResult, {
   max = Math.pow(2, 16),
-  makeCacheKey = defaultMakeCacheKey,
+  makeCacheKey = (defaultMakeCacheKey as () => TCacheKey),
   keyArgs,
   subscribe,
-}: OptimisticWrapOptions<TArgs, TKeyArgs> = Object.create(null)) {
-  const cache = new Cache<TCacheKey, Entry<TArgs, TResult>>(
-    max,
-    entry => entry.dispose(),
-  );
+  cache: cacheOption = StrongCache,
+}: OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult> = Object.create(null)) {
+  const cache: CommonCache<TCacheKey, Entry<TArgs, TResult>> =
+    typeof cacheOption === "function"
+      ? new cacheOption(max, entry => entry.dispose())
+      : cacheOption;
 
   const optimistic = function (): TResult {
     const key = makeCacheKey.apply(
@@ -168,9 +187,7 @@ export function wrap<
   } as OptimisticWrapperFunction<TArgs, TResult, TKeyArgs, TCacheKey>;
 
   Object.defineProperty(optimistic, "size", {
-    get() {
-      return cache["map"].size;
-    },
+    get: () => cache.size,
     configurable: false,
     enumerable: false,
   });
@@ -180,6 +197,7 @@ export function wrap<
     makeCacheKey,
     keyArgs,
     subscribe,
+    cache,
   });
 
   function dirtyKey(key: TCacheKey) {
diff --git a/src/tests/api.ts b/src/tests/api.ts
index 1d6b852..985765a 100644
--- a/src/tests/api.ts
+++ b/src/tests/api.ts
@@ -4,6 +4,7 @@ import {
   wrap,
   defaultMakeCacheKey,
   OptimisticWrapperFunction,
+  CommonCache,
 } from "../index";
 import { wrapYieldingFiberMethods } from '@wry/context';
 import { dep } from "../dep";
@@ -36,6 +37,68 @@ describe("optimism", function () {
     assert.strictEqual(test("a"), "aNaCl");
   });
 
+  it("can manually specify a cache instance", () => {
+    class Cache<K, V> implements CommonCache<K, V> {
+      private _cache = new Map<K, V>()
+      has = this._cache.has.bind(this._cache);
+      get = this._cache.get.bind(this._cache);
+      delete = this._cache.delete.bind(this._cache);
+      get size(){ return this._cache.size }
+      set(key: K, value: V): V {
+        this._cache.set(key, value);
+        return value;
+      }
+      clean(){};
+    }
+
+    const cache = new Cache<String, any>();
+
+    const wrapped = wrap(
+      (obj: { value: string }) => obj.value + " transformed",
+      {
+        cache,
+        makeCacheKey(obj) {
+          return obj.value;
+        },
+      }
+    );
+    assert.ok(cache instanceof Cache);
+    assert.strictEqual(wrapped({ value: "test" }), "test transformed");
+    assert.strictEqual(wrapped({ value: "test" }), "test transformed");
+    cache.get("test").value[0] = "test modified";
+    assert.strictEqual(wrapped({ value: "test" }), "test modified");
+  });
+
+  it("can manually specify a cache constructor", () => {
+    class Cache<K, V> implements CommonCache<K, V> {
+      private _cache = new Map<K, V>()
+      has = this._cache.has.bind(this._cache);
+      get = this._cache.get.bind(this._cache);
+      delete = this._cache.delete.bind(this._cache);
+      get size(){ return this._cache.size }
+      set(key: K, value: V): V {
+        this._cache.set(key, value);
+        return value;
+      }
+      clean(){};
+    }
+
+    const wrapped = wrap(
+      (obj: { value: string }) => obj.value + " transformed",
+      {
+        cache: Cache,
+        makeCacheKey(obj) {
+          return obj.value;
+        },
+      }
+    );
+    assert.ok(wrapped.options.cache instanceof Cache);
+    assert.strictEqual(wrapped({ value: "test" }), "test transformed");
+    assert.strictEqual(wrapped({ value: "test" }), "test transformed");
+    wrapped.options.cache.get("test").value[0] = "test modified";
+    assert.strictEqual(wrapped({ value: "test" }), "test modified");
+  });
+
   it("works with two layers of functions", function () {
     const files: { [key: string]: string } = {
       "a.js": "a",
@@ -692,7 +755,7 @@ describe("optimism", function () {
     assert.strictEqual(sumFirst.forget(9), false);
   });
 
-  it("exposes optimistic.size property, returning cache.map.size", function () {
+  it("exposes optimistic.{size,options.cache.size} properties", function () {
     const d = dep<string>();
     const fib = wrap((n: number): number => {
       d("shared");
@@ -703,7 +766,12 @@ describe("optimism", function () {
       },
     });
 
-    assert.strictEqual(fib.size, 0);
+    function size() {
+      assert.strictEqual(fib.options.cache.size, fib.size);
+      return fib.size;
+    }
+
+    assert.strictEqual(size(), 0);
 
     assert.strictEqual(fib(0), 0);
     assert.strictEqual(fib(1), 1);
@@ -715,22 +783,22 @@ describe("optimism", function () {
     assert.strictEqual(fib(7), 13);
     assert.strictEqual(fib(8), 21);
 
-    assert.strictEqual(fib.size, 9);
+    assert.strictEqual(size(), 9);
 
     fib.dirty(6);
     // Merely dirtying an Entry does not remove it from the LRU cache.
-    assert.strictEqual(fib.size, 9);
+    assert.strictEqual(size(), 9);
 
     fib.forget(6);
     // Forgetting an Entry both dirties it and removes it from the LRU cache.
-    assert.strictEqual(fib.size, 8);
+    assert.strictEqual(size(), 8);
 
     fib.forget(4);
-    assert.strictEqual(fib.size, 7);
+    assert.strictEqual(size(), 7);
 
     // This way of calling d.dirty causes any parent Entry objects to be
     // forgotten (removed from the LRU cache).
     d.dirty("shared", "forget");
-    assert.strictEqual(fib.size, 0);
+    assert.strictEqual(size(), 0);
   });
 });
diff --git a/src/tests/cache.ts b/src/tests/cache.ts
index 89d6778..17a5223 100644
--- a/src/tests/cache.ts
+++ b/src/tests/cache.ts
@@ -1,9 +1,9 @@
 import * as assert from "assert";
-import { Cache } from "../cache";
+import { StrongCache as Cache } from "@wry/caches";
 
 describe("least-recently-used cache", function () {
   it("can hold lots of elements", function () {
-    const cache = new Cache;
+    const cache = new Cache();
     const count = 1000000;
 
     for (let i = 0; i < count; ++i) {
@@ -72,8 +72,10 @@ describe("least-recently-used cache", function () {
 
       if (sequence.length > 0) {
         assert.strictEqual((cache as any).newest.key, sequence[0]);
-        assert.strictEqual((cache as any).oldest.key,
-                           sequence[sequence.length - 1]);
+        assert.strictEqual(
+          (cache as any).oldest.key,
+          sequence[sequence.length - 1]
+        );
       }
     }