diff --git a/src/data_structures/skiplist/lockfree_skiplist.hpp b/src/data_structures/skiplist/lockfree_skiplist.hpp
deleted file mode 100644
index 39d263bee..000000000
--- a/src/data_structures/skiplist/lockfree_skiplist.hpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#pragma once
-
-#include <cstdlib>
-#include <atomic>
-
-#include "utils/mark_ref.hpp"
-
-namespace lockfree
-{
-
-template <class K, class T>
-class SkipList
-{
-public:
-    struct Node
-    {
-        using ref_t = MarkRef<Node>;
-
-        K* key;
-        T* item;
-
-        const uint8_t height;
-
-        static Node* create(uint8_t height, K* key, T* item)
-        {
-            auto size = sizeof(Node) + height * sizeof(std::atomic<ref_t>);
-            auto node = static_cast<Node*>(std::malloc(size));
-            return new (node) Node(height, key, item);
-        }
-
-        static void destroy(Node* node)
-        {
-            node->~SkipNode();
-            free(node);
-        }
-
-    private:
-        Node(uint8_t height, K* key, T* item)
-            : key(key), item(item), height(height)
-        {
-            for(uint8_t i = 0; i < height; ++i)
-                new (&tower[i]) std::atomic<ref_t>(nullptr);
-        }
-
-        // this creates an array of the size zero. we can't put any sensible
-        // value here since we don't know what size it will be untill the
-        // node is allocated. we could make it a SkipNode** but then we would
-        // have two memory allocations, one for node and one for the forward
-        // list. this way we avoid expensive malloc/free calls and also cache
-        // thrashing when following a pointer on the heap
-        std::atomic<ref_t> tower[0];
-    };
-
-    //void list_search(const K& key, 
-
-};
-
-}
diff --git a/src/data_structures/skiplist/new_height.hpp b/src/data_structures/skiplist/new_height.hpp
deleted file mode 100644
index 62fdec924..000000000
--- a/src/data_structures/skiplist/new_height.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include "utils/random/xorshift.hpp"
-
-template <class randomizer_t>
-size_t new_height(int max_height)
-{
-    // get 64 random bits (coin tosses)
-    uint64_t rand = xorshift::next();
-    size_t height = 1;
-
-    // for every head (1) increase the tower height by one until the tail (0)
-    // comes. this gives the following probabilities for tower heights:
-    //
-    // 1/2 1/4 1/8 1/16 1/32 1/64 ...
-    //  1   2   3   4    5    6   ...
-    //
-    while(max_height-- && ((rand >>= 1) & 1))
-        height++;
-
-    return height;
-}
diff --git a/src/data_structures/skiplist/old_skiplist.hpp b/src/data_structures/skiplist/old_skiplist.hpp
deleted file mode 100644
index b3b90f54f..000000000
--- a/src/data_structures/skiplist/old_skiplist.hpp
+++ /dev/null
@@ -1,253 +0,0 @@
-#pragma once
-
-#include <algorithm>
-#include <cstdlib>
-#include <array>
-
-#include "new_height.hpp"
-#include "skipnode.hpp"
-
-// concurrent skiplist based on the implementation described in
-// "A Provably Correct Scalable Concurrent Skip List"
-// https://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf
-
-template <class K,
-          class T,
-          size_t MAX_HEIGHT = 24,
-          class compare=std::less<K>,
-          class lock_type=SpinLock>
-class SkipList
-{
-    using Node = SkipNode<K, T, lock_type>;
-
-public:
-    SkipList()
-        : size_(0),
-          header(Node::create(MAX_HEIGHT, nullptr, nullptr)) {}
-
-    ~SkipList()
-    {
-        for(Node* current = header.load(); current;)
-        {
-            Node* next = current->forward(0);
-            Node::destroy(current);
-            current = next;
-        }
-    }
-
-    size_t size() const
-    {
-        return size_.load();
-    }
-
-    uint8_t height() const
-    {
-        return MAX_HEIGHT;
-    }
-
-//private:
-
-    bool greater(const K* const key, const Node* node)
-    {
-        return node && compare()(*node->key, *key);
-    }
-
-    bool less(const K* const key, const Node* node)
-    {
-        return (node == nullptr) || compare()(*key, *node->key);
-    }
-
-    size_t increment_size(size_t delta)
-    {
-        return size_.fetch_add(delta) + delta;
-    }
-
-    int find_path(Node* from,
-                  int start_level,
-                  const K* const key,
-                  Node* preds[],
-                  Node* succs[])
-    {
-        int lfound = -1;
-        Node* pred = from;
-     
-        for(int level = start_level; level >= 0; --level)
-        {
-            Node* node = pred->forward(level);
-
-            while(greater(key, node))
-                pred = node, node = pred->forward(level);
-
-
-            if(lfound == -1 && !less(key, node))
-                lfound = level;
-
-            preds[level] = pred;
-            succs[level] = node;
-        }
-
-        return lfound;
-    }
-
-    Node* find(const K* const key)
-    {
-        Node* pred = header.load(); 
-        Node* node = nullptr;
-
-        uint8_t level = pred->height;
-        bool found = false;
-
-        while(!found)
-        {
-            // descend down first, facebook says it works better xD but make
-            // some tests when you have time to determine the best strategy
-            for(; level > 0 &&
-                  less(key, node = pred->forward(level - 1)); --level) {}
-
-            if(level == 0)
-                return nullptr;
-
-            --level;
-
-            while(greater(key, node))
-                pred = node, node = node->forward(level);
-
-            found = !less(key, node);
-        }
-
-        return node;
-    }
-
-    template <bool ADDING>
-    bool lock_nodes(uint8_t height,
-                    std::unique_lock<lock_type> guards[MAX_HEIGHT],
-                    Node* preds[MAX_HEIGHT],
-                    Node* succs[MAX_HEIGHT])
-    {
-        Node *prepred, *pred, *succ = nullptr;
-        bool valid = true;
-
-        for(int level = 0; valid && level < height; ++level)
-        {
-            pred = preds[level], succ = succs[level];
-
-            if(pred != prepred)
-                guards[level] = pred->guard(), prepred = pred;
-
-            valid = !pred->marked() && pred->forward(level) == succ;
-
-            if(ADDING)
-                valid = valid && (succ == nullptr || !succ->marked());
-        }
-
-        return valid;
-    }
-
-    bool insert(K* key, T* item)
-    {
-        Node *preds[MAX_HEIGHT], *succs[MAX_HEIGHT];
-
-        while(true)
-        {
-            auto head = header.load();
-            auto lfound = find_path(head, MAX_HEIGHT - 1, key, preds, succs);
-
-            if(lfound != -1)
-            {
-                auto found = succs[lfound];
-                
-                if(!found->marked())
-                {
-                    while(!found->fully_linked()) {}
-                    return false;
-                }
-
-                continue;
-            }
-
-            auto node_height = new_height(MAX_HEIGHT);
-            std::unique_lock<lock_type> guards[MAX_HEIGHT];
-
-            // try to acquire the locks for predecessors up to the height of
-            // the new node. release the locks and try again if someone else
-            // has the locks
-            if(!lock_nodes<true>(node_height, guards, preds, succs))
-                continue;
-
-            // you have the locks, create a new node
-            auto new_node = Node::create(node_height, key, item);
-
-            // link the predecessors and successors, e.g.
-            //
-            // 4 HEAD ... P ------------------------> S ... NULL
-            // 3 HEAD ... ... P -----> NEW ---------> S ... NULL
-            // 2 HEAD ... ... P -----> NEW -----> S ... ... NULL
-            // 1 HEAD ... ... ... P -> NEW -> S ... ... ... NULL
-            for(uint8_t level = 0; level < node_height; ++level)
-            {
-                new_node->forward(level, succs[level]);
-                preds[level]->forward(level, new_node);
-            }
-
-            new_node->set_fully_linked();
-            increment_size(1);
-            
-            return true;
-        }
-    }
-
-    bool ok_delete(Node* node, int level)
-    {
-        return node->fully_linked()
-            && node->height - 1 == level
-            && !node->marked();
-    }
-
-    bool remove(const K* const key)
-    {
-        Node* node = nullptr;
-        std::unique_lock<lock_type> node_guard;
-        bool marked = false;
-        int node_height = 0;
-
-        Node* preds[MAX_HEIGHT], *succs[MAX_HEIGHT];
-    
-        while(true)
-        {
-            auto head = header.load();
-            auto lfound = find_path(head, MAX_HEIGHT - 1, key, preds, succs);
-
-            if(!marked && (lfound == -1 || !ok_delete(succs[lfound], lfound)))
-                    return false;
-
-            if(!marked)
-            {
-                node = succs[lfound];
-                node_height = node->height;
-                node_guard = node->guard();
-
-                if(node->marked())
-                    return false;
-
-                node->set_marked();
-            }
-
-            std::unique_lock<lock_type> guards[MAX_HEIGHT];
-
-            if(!lock_nodes<false>(node_height, guards, preds, succs))
-                continue;
-
-            for(int level = node_height - 1; level >= 0; --level)
-                preds[level]->forward(level, node->forward(level));
-
-            increment_size(-1);
-            break;
-        }
-
-        // TODO recyclee(node);
-        return true;
-    }
-
-    std::atomic<size_t> size_;
-    std::atomic<Node*> header;
-};
diff --git a/src/data_structures/skiplist/skiplist.hpp b/src/data_structures/skiplist/skiplist.hpp
index 8d13e7d34..3b5399be1 100644
--- a/src/data_structures/skiplist/skiplist.hpp
+++ b/src/data_structures/skiplist/skiplist.hpp
@@ -9,6 +9,7 @@
 
 #include "utils/random/fast_binomial.hpp"
 #include "memory/lazy_gc.hpp"
+#include "utils/placeholder.hpp"
 
 /* @brief Concurrent lock-based skiplist with fine grained locking
  *
@@ -145,11 +146,41 @@ public:
     public:
         friend class SkipList;
 
-        value_type data;
-
         const uint8_t height;
         Flags flags;
 
+        const K& key() const
+        {
+            return kv_pair().first;
+        }
+
+        T& value()
+        {
+            return kv_pair().second;
+        }
+
+        const T& value() const
+        {
+            return kv_pair().second;
+        }
+
+        value_type& kv_pair()
+        {
+            return data.get();
+        }
+
+        const value_type& kv_pair() const
+        {
+            return data.get();
+        }
+
+        static Node* sentinel(uint8_t height)
+        {
+            // we have raw memory and we need to construct an object
+            // of type Node on it
+            return new (allocate(height)) Node(height);
+        }
+
         static Node* create(const K& key, const T& item, uint8_t height)
         {
             return create({key, item}, height);
@@ -162,15 +193,7 @@ public:
 
         static Node* create(value_type&& data, uint8_t height)
         {
-            // [      Node      ][Node*][Node*][Node*]...[Node*]
-            //         |            |      |      |         |
-            //         |            0      1      2      height-1
-            // |----------------||-----------------------------|
-            //   space for Node     space for tower pointers
-            //     structure          right after the Node
-            //                             structure
-            auto size = sizeof(Node) + height * sizeof(std::atomic<Node*>);
-            auto node = static_cast<Node*>(std::malloc(size));
+            auto node = allocate(height);
 
             // we have raw memory and we need to construct an object
             // of type Node on it
@@ -194,8 +217,7 @@ public:
         }
 
     private:
-        Node(value_type&& data, uint8_t height)
-            : data(std::move(data)), height(height)
+        Node(uint8_t height) : height(height)
         {
             // here we assume, that the memory for N towers (N = height) has
             // been allocated right after the Node structure so we need to
@@ -204,12 +226,35 @@ public:
                 new (&tower[i]) std::atomic<Node*> {nullptr};
         }
 
+        Node(value_type&& data, uint8_t height) : Node(height)
+        {
+            this->data.set(std::forward<value_type>(data));
+        }
+
         ~Node()
         {
             for(auto i = 0; i < height; ++i)
                 tower[i].~atomic();
         }
 
+        static Node* allocate(uint8_t height)
+        {
+            // [      Node      ][Node*][Node*][Node*]...[Node*]
+            //         |            |      |      |         |
+            //         |            0      1      2      height-1
+            // |----------------||-----------------------------|
+            //   space for Node     space for tower pointers
+            //     structure          right after the Node
+            //                             structure
+            auto size = sizeof(Node) + height * sizeof(std::atomic<Node*>);
+            auto node = static_cast<Node*>(std::malloc(size));
+
+            return node;
+        }
+
+        Placeholder<value_type> data;
+
+
         // this creates an array of the size zero. we can't put any sensible
         // value here since we don't know what size it will be untill the
         // node is allocated. we could make it a Node** but then we would
@@ -234,19 +279,19 @@ public:
         value_type& operator*()
         {
             assert(node != nullptr);
-            return node->data;
+            return node->kv_pair();
         }
 
         value_type* operator->()
         {
             assert(node != nullptr);
-            return &node->data;
+            return &node->kv_pair();
         }
 
         operator value_type&()
         {
             assert(node != nullptr);
-            return node->data;
+            return node->kv_pair();
         }
 
         It& operator++()
@@ -307,7 +352,7 @@ public:
         Iterator(const Iterator&) = default;
     };
 
-    SkipList() : header(Node::create(K(), std::move(T(0)), H)) {}
+    SkipList() : header(Node::sentinel(H)) {}
 
     friend class Accessor;
 
@@ -447,12 +492,12 @@ private:
 
     bool greater(const K& key, const Node* const node)
     {
-        return node && key > node->data.first;
+        return node && key > node->key();
     }
 
     bool less(const K& key, const Node* const node)
     {
-        return (node == nullptr) || key < node->data.first;
+        return (node == nullptr) || key < node->key();
     }
 
     ConstIterator find(const K& key) const
diff --git a/src/data_structures/skiplist/skipnode.hpp b/src/data_structures/skiplist/skipnode.hpp
deleted file mode 100644
index 04c4b92bf..000000000
--- a/src/data_structures/skiplist/skipnode.hpp
+++ /dev/null
@@ -1,135 +0,0 @@
-#pragma once
-
-#include <cstdlib>
-#include <atomic>
-#include <mutex>
-
-#include "threading/sync/spinlock.hpp"
-
-// concurrent skiplist node based on the implementation described in
-// "A Provably Correct Scalable Concurrent Skip List"
-// https://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf
-
-template <class K,
-          class T,
-          class lock_type=SpinLock>
-struct SkipNode
-{
-    using Node = SkipNode<K, T, lock_type>;
-
-    enum flags {
-        MARKED       = 1,
-        FULLY_LINKED = 1 << 1
-    };
-
-    // key against the value is sorted in the skiplist. must be comparable
-    K* key;
-
-    // item on the heap this node points
-    T* item;
-
-    const uint8_t height;
-
-    // use this for creating new nodes. DON'T use the constructor (it's
-    // private anyway)
-    static SkipNode* create(int height, K* key, T* item)
-    {
-        size_t size = sizeof(Node) + height * sizeof(std::atomic<Node*>);
-        
-        auto* node = static_cast<SkipNode*>(malloc(size));
-        new (node) Node(height, key, item);
-        
-        return node;
-    }
-
-    // acquire an exclusive guard on this node, use for concurrent access
-    std::unique_lock<lock_type> guard()
-    {
-        return std::unique_lock<lock_type>(lock);
-    }
-
-    // use this for destroying nodes after you don't need them any more
-    static void destroy(Node* node)
-    {
-        node->~SkipNode();
-        free(node);
-    }
-
-    bool marked() const
-    {
-        return fget() & MARKED;
-    }
-
-    void set_marked()
-    {
-        fset(fget() | MARKED);
-    }
-    
-    bool fully_linked() const
-    {
-        return fget() & FULLY_LINKED;
-    }
-
-    void set_fully_linked()
-    {
-        fset(fget() | FULLY_LINKED);
-    }
-
-    Node* forward(uint8_t level)
-    {
-        return forward_[level].load(std::memory_order_consume);
-    }
-
-    void forward(uint8_t level, Node* next)
-    {
-        forward_[level].store(next, std::memory_order_release);
-    }
-
-private:
-    SkipNode(uint8_t height, K* key, T* item)
-        : key(key), item(item), height(height)
-    {
-        // set the flags to zero at the beginning
-        fset(0);
-
-        // we need to explicitly call the placement new operator over memory
-        // allocated for forward_ pointers, see the notes below
-        for (uint8_t i = 0; i < height; ++i)
-            new (&forward_[i]) std::atomic<Node*>(nullptr);
-    }
-
-    ~SkipNode()
-    {
-        for (uint8_t i = 0; i < height; ++i)
-            forward_[i].~atomic();
-    }
-
-    uint8_t fget() const
-    {
-        // do an atomic load of the flags. if you need to use this value
-        // more than one time in a function it's a good idea to store it
-        // in a stack variable (non atomic) to optimize for performance
-        return flags.load(std::memory_order_consume);
-    }
-
-    void fset(uint8_t value)
-    {
-        // atomically set new flags
-        flags.store(value, std::memory_order_release);
-    }
-
-    std::atomic<uint8_t> flags;
-    lock_type lock;
-    
-    // this creates an array of the size zero locally inside the SkipNode
-    // struct. we can't put any sensible value here since we don't know
-    // what size it will be untill the skipnode is allocated. we could make
-    // it a SkipNode** but then we would have two memory allocations, one for
-    // SkipNode and one for the forward list and malloc calls are expensive!
-
-    // we're gonna cheat here. we'll make this a zero length list and then
-    // allocate enough memory for the SkipNode struct to store more than zero
-    // elements (precisely *height* elements). c++ does not check bounds so we
-    // can access anything we want!
-    std::atomic<Node*> forward_[0];
-};
diff --git a/src/examples/skiplist_sentinel.cpp b/src/examples/skiplist_sentinel.cpp
new file mode 100644
index 000000000..9cf6214aa
--- /dev/null
+++ b/src/examples/skiplist_sentinel.cpp
@@ -0,0 +1,40 @@
+#include <iostream>
+
+#include "data_structures/skiplist/skiplist.hpp"
+#include "storage/indexes/keys/unique_key.hpp"
+
+using std::cout;
+using std::endl;
+
+using skiplist_t = SkipList<UniqueKeyAsc<int>, int>;
+
+void print_skiplist(const skiplist_t::Accessor& skiplist)
+{
+    cout << "---- skiplist now has: ";
+
+    for(auto& kv : skiplist)
+        cout << "(" << kv.first << ", " << kv.second << ") ";
+
+    cout << "----" << endl;
+}
+
+int main(void)
+{
+    skiplist_t skiplist;
+
+    auto accessor = skiplist.access();
+
+    // this has to be here since UniqueKey<> class takes references!
+    int keys[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
+
+    accessor.insert_unique(keys[1], 10);
+    accessor.insert_unique(keys[2], 20);
+    accessor.insert_unique(keys[7], 70);
+    accessor.insert_unique(keys[4], 40);
+    accessor.insert_unique(keys[8], 80);
+    accessor.insert_unique(keys[3], 30);
+
+    print_skiplist(accessor);
+
+    return 0;
+}
diff --git a/src/storage/indexes/keys/unique_key.hpp b/src/storage/indexes/keys/unique_key.hpp
index 2917b195d..d98a48f6c 100644
--- a/src/storage/indexes/keys/unique_key.hpp
+++ b/src/storage/indexes/keys/unique_key.hpp
@@ -32,6 +32,9 @@ private:
     const K& key;
 };
 
+template <class K, class SortOrder>
+constexpr SortOrder UniqueKey<K, SortOrder>::sort_order;
+
 template <class K>
 using UniqueKeyAsc = UniqueKey<K, Ascending<K>>;
 
diff --git a/src/storage/indexes/sort_order.hpp b/src/storage/indexes/sort_order.hpp
index c30ee02eb..82a60f61d 100644
--- a/src/storage/indexes/sort_order.hpp
+++ b/src/storage/indexes/sort_order.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 template <class T>
-class Ascending
+struct Ascending
 {
     constexpr bool operator()(const T& lhs, const T& rhs) const
     {
@@ -10,7 +10,7 @@ class Ascending
 };
 
 template <class T>
-class Descending
+struct Descending
 {
     constexpr bool operator()(const T& lhs, const T& rhs) const
     {
diff --git a/src/utils/placeholder.hpp b/src/utils/placeholder.hpp
index 9d766cd74..2cdd5c2fa 100644
--- a/src/utils/placeholder.hpp
+++ b/src/utils/placeholder.hpp
@@ -24,6 +24,12 @@ public:
         return *data._M_ptr();
     }
 
+    const T& get() const noexcept
+    {
+        assert(initialized);
+        return *data._M_ptr();
+    }
+
     void set(const T& item)
     {
         new (data._M_addr()) T(item);