diff --git a/README.md b/README.md
index c2ac9a9..d7e0b74 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,13 @@
 # sparse-array
 
-A Clojure library designed to manipulate sparse *arrays* - multi-dimensional spaces accessed by indices, but containing arbitrary values rather than just numbers. For sparse spaces which contain numbers only, you're better to use a *sparce matrix* library, for example [clojure.core.matrix](https://mikera.github.io/core.matrix/).
+A Clojure library designed to manipulate sparse *arrays* - multi-dimensional spaces accessed by indices, but containing arbitrary values rather than just numbers. For sparse spaces which contain numbers only, you're better to use a *sparse matrix* library, for example [clojure.core.matrix](https://mikera.github.io/core.matrix/).
+
+Arbitrary numbers of dimensions are supported, up to limits imposed by the JVM stack.
 
 ## Conventions:
 
+### Sparse arrays
+
 For the purposes of this library, a sparse array shall be implemented as a map, such that all keys are non-negative members of the set of integers, except for the following keyword keys, all of which are expected to be present:
 
 1. `:dimensions` The number of dimensions in this array, counting the present one (value expected to be a real number);
@@ -30,9 +34,110 @@ Thus an array with a single value 'hello' at coordinates x = 3, y = 4, z = 5 wou
 
 At the present stage of development, where the expectations of an operation are violated, `nil` is returned and no exception is thrown. However, it's probable that later there will be at least the option of thowing specific exceptions, as otherwise debugging could be tricky.
 
+### Dense arrays
+
+For the purposes of conversion, a **dense array** is assumed to be a vector; a two dimensional dense array a vector of vectors; a three dimensional dense array a vector of vectors of vectors, and so on. For any depth `N`, all vectors at depth `N` must have the same arity. If these conventions are not respected conversion may fail.
+
 ## Usage
 
-FIXME
+### make-sparse-array
+`sparse-array.core/make-sparse-array ([& dimensions])`
+
+Make a sparse array with these `dimensions`. Every member of `dimensions` must be a keyword; otherwise, `nil` will be returned.
+
+e.g.
+
+```clojure
+(make-sparse-array :x :y :z)
+
+=> {:dimensions 3, :coord :x, :content (:y :z)}
+
+```
+
+### sparse-array?
+
+`sparse-array.core/sparse-array? ([x])`
+
+`true` if `x` is a sparse array conforming to the conventions established by this library, else `false`.
+
+### put
+
+`sparse-array.core/put ([array value & coordinates])`
+
+Return a sparse array like this `array` but with this `value` at these `coordinates`. Returns `nil` if any coordinate is invalid.
+
+e.g.
+
+```clojure
+(put (put (make-sparse-array :x :y) "hello" 3 4) "goodbye" 4 3)
+
+=> {:dimensions 2,
+     :coord :x,
+     :content (:y),
+     3 {:dimensions 1, :coord :y, :content :data, 4 "hello"},
+     4 {:dimensions 1, :coord :y, :content :data, 3 "goodbye"}}
+```
+
+### get
+
+`sparse-array.core/get ([array & coordinates])`
+
+Return the value in this sparse `array` at these `coordinates`.
+
+### merge-sparse-arrays
+
+`sparse-array.core/merge-sparse-arrays ([a1 a2])`
+
+Return a sparse array taking values from sparse arrays `a1` and `a2`, but preferring values from `a2` where there is a conflict. `a1` and `a2` must have the **same** dimensions in the **same** order, or `nil` will be returned.
+
+e.g.
+
+```clojure
+(merge-sparse-arrays
+  (put (make-sparse-array :x) "hello" 3)
+  (put (make-sparse-array :x) "goodbye" 4)))
+
+=> {:dimensions 1, :coord :x, :content :data, 3 "hello", 4 "goodbye"}
+```
+
+### dense-to-sparse
+
+`sparse-array.core/dense-to-sparse ([x] [x coordinates])`
+
+Return a sparse array representing the content of the dense array `x`, assuming these `coordinates` if specified. *NOTE THAT* if insufficient values of `coordinates` are specified, the resulting sparse array will be malformed.
+
+e.g.
+
+```clojure
+(dense-to-sparse [nil nil nil "hello" nil "goodbye"])
+
+=> {:dimensions 1, :coord :i0, :content :data, 3 "hello", 5 "goodbye"}
+```
+
+### sparse-to-dense
+
+`sparse-array.core/sparse-to-dense ([x] [x arity])`
+
+Return a dense array representing the content of the sparse array `x`.
+
+**NOTE THAT** this has the potential to consume very large amounts of memory.
+
+e.g.
+
+```clojure
+(sparse-to-dense
+  (put
+    (put
+      (make-sparse-array :x :y)
+      "hello" 3 4)
+    "goodbye" 4 3))
+
+=> [[nil nil nil nil nil]
+    [nil nil nil nil nil]
+    [nil nil nil nil nil]
+    [nil nil nil nil "hello"]
+    [nil nil nil "goodbye" nil]]
+```
 
 ## License
 
diff --git a/project.clj b/project.clj
index 7c8bf54..d9c47b7 100644
--- a/project.clj
+++ b/project.clj
@@ -1,5 +1,5 @@
 (defproject sparse-array "0.1.0-SNAPSHOT"
-  :description "FIXME: write description"
+  :description "A Clojure library designed to manipulate sparse *arrays* - multi-dimensional spaces accessed by indices, but containing arbitrary values rather than just numbers. For sparse spaces which contain numbers only, you're better to use a *sparse matrix* library, for example [clojure.core.matrix](https://mikera.github.io/core.matrix/)."
   :url "http://example.com/FIXME"
   :license {:name "Eclipse Public License"
             :url "http://www.eclipse.org/legal/epl-v10.html"}
diff --git a/src/sparse_array/core.clj b/src/sparse_array/core.clj
index a8b55b2..b169df8 100644
--- a/src/sparse_array/core.clj
+++ b/src/sparse_array/core.clj
@@ -1,6 +1,5 @@
 (ns sparse-array.core)
 
-
 (defn make-sparse-array
   "Make a sparse array with these `dimensions`. Every member of `dimensions`
   must be a keyword; otherwise, `nil` will be returned."
@@ -63,7 +62,9 @@
 
 (defn merge-sparse-arrays
   "Return a sparse array taking values from sparse arrays `a1` and `a2`,
-  but preferring values from `a2` where there is a conflict."
+  but preferring values from `a2` where there is a conflict. `a1` and `a2`
+  must have the **same** dimensions in the **same** order, or `nil` will
+  be returned."
   [a1 a2]
   (cond
     (nil? a1) a2
@@ -86,16 +87,78 @@
               (keys a1)
               (keys a2))))))))
 
+(defn dense-dimensions
+  "How many usable dimensions (represented as vectors) does the dense array
+  `x` have?"
+  [x]
+  (if
+    (vector? x)
+    (if
+      (every? vector? x)
+      (inc (apply min (map dense-dimensions x)))
+      ;; `min` is right here, not `max`, because otherwise
+      ;; we will get malformed arrays. Be liberal with what you
+      ;; consume, conservative with what you return!
+      1)
+    0))
+
 (defn dense-to-sparse
   "Return a sparse array representing the content of the dense array `x`,
-  assuming these `coordinates` if specified."
+  assuming these `coordinates` if specified. *NOTE THAT* if insufficient
+  values of `coordinates` are specified, the resulting sparse array will
+  be malformed."
   ([x]
-   :TODO)
+   (dense-to-sparse x (map #(keyword (str "i" %)) (range))))
   ([x coordinates]
-   :TODO))
+   (let
+     [dimensions (dense-dimensions x)]
+     (reduce
+       merge
+       (apply make-sparse-array (take dimensions coordinates))
+       (map
+         (fn [i v] (if (nil? v) nil (hash-map i v)))
+         (range)
+         (if
+           (> dimensions 1)
+           (map #(dense-to-sparse % (rest coordinates)) x)
+           x))))))
+
+(defn arity
+  "Return the arity of the sparse array `x`."
+  [x]
+  (inc (apply max (filter integer? (keys x)))))
+
+(defn child-arity
+  "Return the largest arity among the arities of the next dimension layer of
+  the sparse array `x`."
+  [x]
+  (apply
+    max
+    (cons
+      -1 ;; if no children are sparse arrays, we should return 0ß
+      (map
+        arity
+        (filter sparse-array? (vals x))))))
 
 (defn sparse-to-dense
   "Return a dense array representing the content of the sparse array `x`.
-  If this blows up out of memory, that's strictly your problem."
-  [x]
-  :TODO)
+
+  **NOTE THAT** this has the potential to consume very large amounts of memory."
+  ([x]
+   (sparse-to-dense x (arity x)))
+  ([x arity]
+   (if
+     (map? x)
+     (let [a (child-arity x)]
+       (apply
+         vector
+         (map
+           #(let [v (x %)]
+              (if
+                (= :data (:content x))
+                v
+                (sparse-to-dense v a)))
+           (range arity))))
+     (apply vector (repeat arity nil)))))
+
+
diff --git a/test/sparse_array/core_test.clj b/test/sparse_array/core_test.clj
index 4c7e214..815618f 100644
--- a/test/sparse_array/core_test.clj
+++ b/test/sparse_array/core_test.clj
@@ -65,3 +65,28 @@
           sparse (dense-to-sparse dense)]
       (is (= "hello" (get sparse 3)))
       (is (= "goodbye" (get sparse 5))))))
+
+(deftest dense-dimensions-tests
+  (testing "dense-dimensions"
+    (is (= 0 (dense-dimensions 1)))
+    (is (= 1 (dense-dimensions [1 2 3])))
+    (is (= 2 (dense-dimensions [[1 2 3][2 4 6][3 6 9]])))
+    (is (= 3
+           (dense-dimensions
+             [[[1 2 3][2 4 6][3 6 9]]
+              [[2 4 6][4 16 36][6 12 18]]
+              [[3 6 9][18 96 (* 6 36 3)][100 200 300]]])))))
+
+(deftest sparse-to-dense-tests
+  (testing "Conversion from sparse to dense arrays"
+    (let [expected [[nil nil nil nil nil]
+                    [nil nil nil nil nil]
+                    [nil nil nil nil nil]
+                    [nil nil nil nil "hello"]
+                    [nil nil nil "goodbye" nil]]
+          actual (sparse-to-dense (put
+                                    (put
+                                      (make-sparse-array :x :y)
+                                      "hello" 3 4)
+                                    "goodbye" 4 3))]
+      (is (= actual expected)))))