diff --git a/src/Makefile b/src/Makefile
index 231761f5e..16172b25f 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -117,7 +117,7 @@ endif
 
 REDIS_SERVER_NAME=redis-server
 REDIS_SENTINEL_NAME=redis-sentinel
-REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o
+REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o
 REDIS_CLI_NAME=redis-cli
 REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
 REDIS_BENCHMARK_NAME=redis-benchmark
diff --git a/src/aof.c b/src/aof.c
index 0af519bfa..f5a90a12c 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -770,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) {
 int rewriteListObject(rio *r, robj *key, robj *o) {
     long long count = 0, items = listTypeLength(o);
 
-    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
-        unsigned char *zl = o->ptr;
-        unsigned char *p = ziplistIndex(zl,0);
-        unsigned char *vstr;
-        unsigned int vlen;
-        long long vlong;
+    if (o->encoding == REDIS_ENCODING_QUICKLIST) {
+        quicklist *list = o->ptr;
+        quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
+        quicklistEntry entry;
 
-        while(ziplistGet(p,&vstr,&vlen,&vlong)) {
+        while (quicklistNext(li,&entry)) {
             if (count == 0) {
                 int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
                     REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
-
                 if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
                 if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
                 if (rioWriteBulkObject(r,key) == 0) return 0;
             }
-            if (vstr) {
-                if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
+
+            if (entry.value) {
+                if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0;
             } else {
-                if (rioWriteBulkLongLong(r,vlong) == 0) return 0;
+                if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0;
             }
-            p = ziplistNext(zl,p);
-            if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
-            items--;
-        }
-    } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
-        list *list = o->ptr;
-        listNode *ln;
-        listIter li;
-
-        listRewind(list,&li);
-        while((ln = listNext(&li))) {
-            robj *eleobj = listNodeValue(ln);
-
-            if (count == 0) {
-                int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
-                    REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
-
-                if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
-                if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
-                if (rioWriteBulkObject(r,key) == 0) return 0;
-            }
-            if (rioWriteBulkObject(r,eleobj) == 0) return 0;
             if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
             items--;
         }
+        quicklistReleaseIterator(li);
     } else {
         redisPanic("Unknown list encoding");
     }
diff --git a/src/object.c b/src/object.c
index 11c77c3c3..f75421ee8 100644
--- a/src/object.c
+++ b/src/object.c
@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) {
     }
 }
 
-robj *createListObject(void) {
-    list *l = listCreate();
+robj *createQuicklistObject(void) {
+    quicklist *l = quicklistCreate();
     robj *o = createObject(REDIS_LIST,l);
-    listSetFreeMethod(l,decrRefCountVoid);
-    o->encoding = REDIS_ENCODING_LINKEDLIST;
+    o->encoding = REDIS_ENCODING_QUICKLIST;
     return o;
 }
 
@@ -242,11 +241,8 @@ void freeStringObject(robj *o) {
 
 void freeListObject(robj *o) {
     switch (o->encoding) {
-    case REDIS_ENCODING_LINKEDLIST:
-        listRelease((list*) o->ptr);
-        break;
-    case REDIS_ENCODING_ZIPLIST:
-        zfree(o->ptr);
+    case REDIS_ENCODING_QUICKLIST:
+        quicklistRelease(o->ptr);
         break;
     default:
         redisPanic("Unknown list encoding type");
@@ -678,7 +674,7 @@ char *strEncoding(int encoding) {
     case REDIS_ENCODING_RAW: return "raw";
     case REDIS_ENCODING_INT: return "int";
     case REDIS_ENCODING_HT: return "hashtable";
-    case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
+    case REDIS_ENCODING_QUICKLIST: return "quicklist";
     case REDIS_ENCODING_ZIPLIST: return "ziplist";
     case REDIS_ENCODING_INTSET: return "intset";
     case REDIS_ENCODING_SKIPLIST: return "skiplist";
diff --git a/src/quicklist.c b/src/quicklist.c
new file mode 100644
index 000000000..415af36c9
--- /dev/null
+++ b/src/quicklist.c
@@ -0,0 +1,2155 @@
+/* quicklist.c - A doubly linked list of ziplists
+ *
+ * Copyright (c) 2014, Matt Stancliff <matt@genges.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must start the above copyright notice,
+ *     this quicklist of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this quicklist of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h> /* for memcpy */
+#include "quicklist.h"
+#include "zmalloc.h"
+#include "ziplist.h"
+#include "util.h" /* for ll2string */
+
+#if defined(REDIS_TEST) || defined(REDIS_TEST_VERBOSE)
+#include <stdio.h> /* for printf (debug printing), snprintf (genstr) */
+#endif
+
+/* If not verbose testing, remove all debug printing. */
+#ifndef REDIS_TEST_VERBOSE
+#define D(...)
+#else
+#define D(...)                                                                 \
+    do {                                                                       \
+        printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__);               \
+        printf(__VA_ARGS__);                                                   \
+        printf("\n");                                                          \
+    } while (0);
+#endif
+
+/* Simple way to give quicklistEntry structs default values with one call. */
+#define initEntry(e)                                                           \
+    do {                                                                       \
+        (e)->zi = (e)->value = NULL;                                           \
+        (e)->longval = -123456789;                                             \
+        (e)->quicklist = NULL;                                                 \
+        (e)->node = NULL;                                                      \
+        (e)->offset = 123456789;                                               \
+        (e)->sz = 0;                                                           \
+    } while (0)
+
+/* Create a new quicklist.
+ * Free with quicklistRelease().
+ *
+ * On error, NULL is returned. Otherwise the pointer to the new quicklist. */
+quicklist *quicklistCreate(void) {
+    struct quicklist *quicklist;
+
+    if ((quicklist = zmalloc(sizeof(*quicklist))) == NULL)
+        return NULL;
+    quicklist->head = quicklist->tail = NULL;
+    quicklist->len = 0;
+    quicklist->count = 0;
+    return quicklist;
+}
+
+static quicklistNode *quicklistCreateNode(void) {
+    quicklistNode *node;
+    if ((node = zmalloc(sizeof(*node))) == NULL)
+        return NULL;
+    node->zl = NULL;
+    node->count = 0;
+    node->next = node->prev = NULL;
+    return node;
+}
+
+/* Return cached quicklist count */
+unsigned int quicklistCount(quicklist *ql) { return ql->count; }
+
+/* Free entire quicklist. */
+void quicklistRelease(quicklist *quicklist) {
+    unsigned long len;
+    quicklistNode *current, *next;
+
+    current = quicklist->head;
+    len = quicklist->len;
+    while (len--) {
+        next = current->next;
+
+        zfree(current->zl);
+        quicklist->count -= current->count;
+
+        zfree(current);
+
+        quicklist->len--;
+        current = next;
+    }
+    zfree(quicklist);
+}
+
+/* Insert 'new_node' after 'old_node' if 'after' is 1.
+ * Insert 'new_node' before 'old_node' if 'after' is 0. */
+static void __quicklistInsertNode(quicklist *quicklist, quicklistNode *old_node,
+                                  quicklistNode *new_node, int after) {
+    if (after) {
+        new_node->prev = old_node;
+        if (old_node) {
+            new_node->next = old_node->next;
+            if (old_node->next)
+                old_node->next->prev = new_node;
+            old_node->next = new_node;
+        }
+        if (quicklist->tail == old_node)
+            quicklist->tail = new_node;
+    } else {
+        new_node->next = old_node;
+        if (old_node) {
+            new_node->prev = old_node->prev;
+            if (old_node->prev)
+                old_node->prev->next = new_node;
+            old_node->prev = new_node;
+        }
+        if (quicklist->head == old_node)
+            quicklist->head = new_node;
+    }
+    /* If this insert creates the first element in this quicklist, we
+     * need to initialize head/tail too. */
+    if (quicklist->len == 0) {
+        quicklist->head = quicklist->tail = new_node;
+    }
+    quicklist->len++;
+}
+
+/* Wrappers for node inserting around existing node. */
+static void _quicklistInsertNodeBefore(quicklist *quicklist,
+                                       quicklistNode *old_node,
+                                       quicklistNode *new_node) {
+    __quicklistInsertNode(quicklist, old_node, new_node, 0);
+}
+
+static void _quicklistInsertNodeAfter(quicklist *quicklist,
+                                      quicklistNode *old_node,
+                                      quicklistNode *new_node) {
+    __quicklistInsertNode(quicklist, old_node, new_node, 1);
+}
+
+/* Add new entry to head of quicklist.
+ *
+ * Returns 'quicklist' argument. */
+quicklist *quicklistPushHead(quicklist *quicklist, const size_t fill,
+                             void *value, size_t sz) {
+    if (quicklist->head && quicklist->head->count < fill) {
+        quicklist->head->zl =
+            ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
+    } else {
+        quicklistNode *node = quicklistCreateNode();
+        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
+
+        _quicklistInsertNodeBefore(quicklist, quicklist->head, node);
+    }
+    quicklist->count++;
+    quicklist->head->count++;
+    return quicklist;
+}
+
+/* Add new node to tail of quicklist.
+ *
+ * Returns 'quicklist' argument. */
+quicklist *quicklistPushTail(quicklist *quicklist, const size_t fill,
+                             void *value, size_t sz) {
+    if (quicklist->tail && quicklist->tail->count < fill) {
+        quicklist->tail->zl =
+            ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
+    } else {
+        quicklistNode *node = quicklistCreateNode();
+        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);
+
+        _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
+    }
+    quicklist->count++;
+    quicklist->tail->count++;
+    return quicklist;
+}
+
+/* Create new node consisting of a pre-formed ziplist.
+ * Used for loading RDBs where entire ziplists have been stored
+ * to be retrieved later. */
+void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) {
+    quicklistNode *node = quicklistCreateNode();
+
+    node->zl = zl;
+    node->count = ziplistLen(node->zl);
+
+    _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
+    quicklist->count += node->count;
+}
+
+/* Append all values of ziplist 'zl' individually into 'quicklist'.
+ *
+ * This allows us to restore old RDB ziplists into new quicklists
+ * with smaller ziplist sizes than the saved RDB ziplist.
+ *
+ * Returns 'quicklist' argument. Frees passed-in ziplist 'zl' */
+quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
+                                            const size_t fill,
+                                            unsigned char *zl) {
+    unsigned char *value;
+    unsigned int sz;
+    long long longval;
+    char longstr[32] = { 0 };
+
+    unsigned char *p = ziplistIndex(zl, 0);
+    while (ziplistGet(p, &value, &sz, &longval)) {
+        if (!value) {
+            /* Write the longval as a string so we can re-add it */
+            sz = ll2string(longstr, sizeof(longstr), longval);
+            value = (unsigned char *)longstr;
+        }
+        quicklistPushTail(quicklist, fill, value, sz);
+        p = ziplistNext(zl, p);
+    }
+    zfree(zl);
+    return quicklist;
+}
+
+/* Create new (potentially multi-node) quicklist from a single existing ziplist.
+ *
+ * Returns new quicklist.  Frees passed-in ziplist 'zl'. */
+quicklist *quicklistCreateFromZiplist(size_t fill, unsigned char *zl) {
+    return quicklistAppendValuesFromZiplist(quicklistCreate(), fill, zl);
+}
+
+#define quicklistDeleteIfEmpty(ql, n)                                          \
+    do {                                                                       \
+        if ((n)->count == 0) {                                                 \
+            __quicklistDelNode((ql), (n));                                     \
+        }                                                                      \
+    } while (0)
+
+static void __quicklistDelNode(quicklist *quicklist, quicklistNode *node) {
+    if (node->next)
+        node->next->prev = node->prev;
+    if (node->prev)
+        node->prev->next = node->next;
+
+    if (node == quicklist->tail)
+        quicklist->tail = node->prev;
+
+    if (node == quicklist->head)
+        quicklist->head = node->next;
+
+    quicklist->count -= node->count;
+
+    zfree(node->zl);
+    zfree(node);
+    quicklist->len--;
+}
+
+/* Delete one entry from list given the node for the entry and a pointer
+ * to the entry in the node.
+ *
+ * Returns 1 if the entire node was deleted, 0 if node still exists.
+ * Also updates in/out param 'p' with the next offset in the ziplist. */
+static int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,
+                             unsigned char **p) {
+    int gone = 0;
+    node->zl = ziplistDelete(node->zl, p);
+    node->count--;
+    if (node->count == 0)
+        gone = 1;
+    quicklist->count--;
+    quicklistDeleteIfEmpty(quicklist, node);
+    /* If we deleted all the nodes, our returned pointer is no longer valid */
+    return gone ? 1 : 0;
+}
+
+/* Delete one element represented by 'entry'
+ *
+ * 'entry' stores enough metadata to delete the proper position in
+ * the correct ziplist in the correct quicklist node. */
+void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
+    quicklistNode *prev = entry->node->prev;
+    quicklistNode *next = entry->node->next;
+    int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,
+                                         entry->node, &entry->zi);
+
+    /* after delete, the zi is now invalid for any future usage. */
+    iter->zi = NULL;
+
+    /* If current node is deleted, we must update iterator node and offset. */
+    if (deleted_node) {
+        if (iter->direction == AL_START_HEAD) {
+            iter->current = next;
+            iter->offset = 0;
+        } else if (iter->direction == AL_START_TAIL) {
+            iter->current = prev;
+            iter->offset = -1;
+        }
+    }
+    /* else if (!deleted_node), no changes needed.
+     * we already reset iter->zi above, and the existing iter->offset
+     * doesn't move again because:
+     *   - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1
+     *   - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0
+     *  if we deleted the last element at offet N and now
+     *  length of this ziplist is N-1, the next call into
+     *  quicklistNext() will jump to the next node. */
+}
+
+/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'.
+ *
+ * Returns 1 if replace happened.
+ * Returns 0 if replace failed and no changes happened. */
+int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
+                            int sz) {
+    quicklistEntry entry;
+    if (quicklistIndex(quicklist, index, &entry)) {
+        entry.node->zl = ziplistDelete(entry.node->zl, &entry.zi);
+        entry.node->zl = ziplistInsert(entry.node->zl, entry.zi, data, sz);
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+/* Given two nodes, try to merge their ziplists.
+ *
+ * This helps us not have a quicklist with 3 element ziplists if
+ * our fill factor can handle much higher levels.
+ *
+ * Note: 'a' must be to the LEFT of 'b'.
+ *
+ * After calling this function, both 'a' and 'b' should be considered
+ * unusable.  The return value from this function must be used
+ * instead of re-using any of the quicklistNode input arguments.
+ *
+ * Returns the input node picked to merge against or NULL if
+ * merging was not possible. */
+static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist,
+                                             quicklistNode *a,
+                                             quicklistNode *b) {
+    /* Merge into node with largest initial count */
+    quicklistNode *target = a->count > b->count ? a : b;
+
+    if (a->count == 0 || b->count == 0)
+        return NULL;
+
+    D("Requested merge (a,b) (%u, %u) and picked target %u", a->count, b->count,
+      target->count);
+
+    int where;
+    unsigned char *p = NULL;
+    if (target == a) {
+        /* If target is node a, we append node b to node a, in-order */
+        where = ZIPLIST_TAIL;
+        p = ziplistIndex(b->zl, 0);
+        D("WILL TRAVERSE B WITH LENGTH: %u, %u", b->count, ziplistLen(b->zl));
+    } else {
+        /* If target b, we prepend node a to node b, in reverse order of a */
+        where = ZIPLIST_HEAD;
+        p = ziplistIndex(a->zl, -1);
+        D("WILL TRAVERSE A WITH LENGTH: %u, %u", a->count, ziplistLen(a->zl));
+    }
+
+    unsigned char *val;
+    unsigned int sz;
+    long long longval;
+    char lv[32] = { 0 };
+    /* NOTE: We could potentially create a built-in ziplist operation
+     * allowing direct merging of two ziplists.  It would be more memory
+     * efficient (one big realloc instead of incremental), but it's more
+     * complex than using the existing ziplist API to read/push as below. */
+    while (ziplistGet(p, &val, &sz, &longval)) {
+        if (!val) {
+            sz = ll2string(lv, sizeof(lv), longval);
+            val = (unsigned char *)lv;
+        }
+        target->zl = ziplistPush(target->zl, val, sz, where);
+        if (target == a) {
+            p = ziplistNext(b->zl, p);
+            b->count--;
+            a->count++;
+        } else {
+            p = ziplistPrev(a->zl, p);
+            a->count--;
+            b->count++;
+        }
+        D("Loop A: %u, B: %u", a->count, b->count);
+    }
+
+    /* At this point, target is populated and not-target needs
+     * to be free'd and removed from the quicklist. */
+    if (target == a) {
+        D("Deleting node B with current count: %d", b->count);
+        __quicklistDelNode(quicklist, b);
+    } else if (target == b) {
+        D("Deleting node A with current count: %d", a->count);
+        __quicklistDelNode(quicklist, a);
+    }
+    return target;
+}
+
+/* Attempt to merge ziplists within two nodes on either side of 'center'.
+ *
+ * We attempt to merge:
+ *   - (center->prev->prev, center->prev)
+ *   - (center->next, center->next->next)
+ *   - (center->prev, center)
+ *   - (center, center->next)
+ */
+static void _quicklistMergeNodes(quicklist *quicklist, const size_t fill,
+                                 quicklistNode *center) {
+    quicklistNode *prev, *prev_prev, *next, *next_next, *target;
+    prev = prev_prev = next = next_next = target = NULL;
+
+    if (center->prev) {
+        prev = center->prev;
+        if (center->prev->prev)
+            prev_prev = center->prev->prev;
+    }
+
+    if (center->next) {
+        next = center->next;
+        if (center->next->next)
+            next_next = center->next->next;
+    }
+
+    /* Try to merge prev_prev and prev */
+    if (prev && prev_prev && (prev->count + prev_prev->count) <= fill) {
+        _quicklistZiplistMerge(quicklist, prev_prev, prev);
+        prev_prev = prev = NULL; /* they could have moved, invalidate them. */
+    }
+
+    /* Try to merge next and next_next */
+    if (next && next_next && (next->count + next_next->count) <= fill) {
+        _quicklistZiplistMerge(quicklist, next, next_next);
+        next = next_next = NULL; /* they could have moved, invalidate them. */
+    }
+
+    /* Try to merge center node and previous node */
+    if (center->prev && (center->count + center->prev->count) <= fill) {
+        target = _quicklistZiplistMerge(quicklist, center->prev, center);
+        center = NULL; /* center could have been deleted, invalidate it. */
+    } else {
+        /* else, we didn't merge here, but target needs to be valid below. */
+        target = center;
+    }
+
+    /* Use result of center merge (or original) to merge with next node. */
+    if (target && target->next &&
+        (target->count + target->next->count) <= fill) {
+        _quicklistZiplistMerge(quicklist, target, target->next);
+    }
+}
+
+/* Split 'node' into two parts, parameterized by 'offset' and 'after'.
+ *
+ * The 'after' argument controls which quicklistNode gets returned.
+ * If 'after'==1, returned node has elements after 'offset'.
+ *                input node keeps elements up to 'offset', including 'offset'.
+ * If 'after'==0, returned node has elements up to 'offset', including 'offset'.
+ *                input node keeps elements after 'offset'.
+ *
+ * If 'after'==1, returned node will have elements _after_ 'offset'.
+ *                The returned node will have elements [OFFSET+1, END].
+ *                The input node keeps elements [0, OFFSET].
+ *
+ * If 'after'==0, returned node will keep elements up to and including 'offset'.
+ *                The returned node will have elements [0, OFFSET].
+ *                The input node keeps elements [OFFSET+1, END].
+ *
+ * The input node keeps all elements not taken by the returned node.
+ *
+ * Returns newly created node or NULL if split not possible. */
+static quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset,
+                                          int after) {
+    size_t zl_sz = ziplistBlobLen(node->zl);
+
+    quicklistNode *new_node = quicklistCreateNode();
+    if (!new_node)
+        return NULL;
+
+    new_node->zl = zmalloc(zl_sz);
+    if (!new_node->zl) {
+        zfree(new_node);
+        return NULL;
+    }
+
+    /* Copy original ziplist so we can split it */
+    memcpy(new_node->zl, node->zl, zl_sz);
+
+    /* -1 here means "continue deleting until the list ends" */
+    int orig_start = after ? offset + 1 : 0;
+    int orig_extent = after ? -1 : offset;
+    int new_start = after ? 0 : offset;
+    int new_extent = after ? offset + 1 : -1;
+
+    D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start,
+      orig_extent, new_start, new_extent);
+
+    node->zl = ziplistDeleteRange(node->zl, orig_start, orig_extent);
+    node->count = ziplistLen(node->zl);
+
+    new_node->zl = ziplistDeleteRange(new_node->zl, new_start, new_extent);
+    new_node->count = ziplistLen(new_node->zl);
+
+    D("After split lengths: orig (%d), new (%d)", node->count, new_node->count);
+    return new_node;
+}
+
+/* Insert a new entry before or after existing entry 'entry'.
+ *
+ * If after==1, the new value is inserted after 'entry', otherwise
+ * the new value is inserted before 'entry'. */
+static void _quicklistInsert(quicklist *quicklist, const size_t fill,
+                             quicklistEntry *entry, void *value,
+                             const size_t sz, int after) {
+    int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;
+    quicklistNode *node = entry->node;
+    quicklistNode *new_node = NULL;
+
+    if (!node) {
+        /* we have no reference node, so let's create only node in the list */
+        D("No node given!");
+        new_node = quicklistCreateNode();
+        new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
+        __quicklistInsertNode(quicklist, NULL, new_node, after);
+        new_node->count++;
+        quicklist->count++;
+        return;
+    }
+
+    /* Populate accounting flags for easier boolean checks later */
+    if (node->count >= fill) {
+        D("Current node is full with count %d with requested fill %lu",
+          node->count, fill);
+        full = 1;
+    }
+
+    if (after && (ziplistNext(node->zl, entry->zi) == NULL)) {
+        D("At Tail of current ziplist");
+        at_tail = 1;
+        if (node->next && node->next->count >= fill) {
+            D("Next node is full too.");
+            full_next = 1;
+        }
+    }
+
+    if (!after && (ziplistPrev(node->zl, entry->zi) == NULL)) {
+        D("At Head");
+        at_head = 1;
+        if (node->prev && node->prev->count >= fill) {
+            D("Prev node is full too.");
+            full_prev = 1;
+        }
+    }
+
+    /* Now determine where and how to insert the new element */
+    if (!full && after) {
+        D("Not full, inserting after current position.");
+        unsigned char *next = ziplistNext(node->zl, entry->zi);
+        if (next == NULL) {
+            node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);
+        } else {
+            node->zl = ziplistInsert(node->zl, next, value, sz);
+        }
+        node->count++;
+    } else if (!full && !after) {
+        D("Not full, inserting before current position.");
+        node->zl = ziplistInsert(node->zl, entry->zi, value, sz);
+        node->count++;
+    } else if (full && at_tail && node->next && !full_next && after) {
+        /* If we are: at tail, next has free space, and inserting after:
+         *   - insert entry at head of next node. */
+        D("Full and tail, but next isn't full; inserting next node head");
+        new_node = node->next;
+        new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);
+        new_node->count++;
+    } else if (full && at_head && node->prev && !full_prev && !after) {
+        /* If we are: at head, previous has free space, and inserting before:
+         *   - insert entry at tail of previous node. */
+        D("Full and head, but prev isn't full, inserting prev node tail");
+        new_node = node->prev;
+        new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);
+        new_node->count++;
+    } else if (full && ((at_tail && node->next && full_next && after) ||
+                        (at_head && node->prev && full_prev && !after))) {
+        /* If we are: full, and our prev/next is full, then:
+         *   - create new node and attach to quicklist */
+        D("\tprovisioning new node...");
+        new_node = quicklistCreateNode();
+        new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
+        new_node->count++;
+        __quicklistInsertNode(quicklist, node, new_node, after);
+    } else if (full) {
+        /* else, node is full we need to split it. */
+        /* covers both after and !after cases */
+        D("\tsplitting node...");
+        new_node = _quicklistSplitNode(node, entry->offset, after);
+        new_node->zl = ziplistPush(new_node->zl, value, sz,
+                                   after ? ZIPLIST_HEAD : ZIPLIST_TAIL);
+        new_node->count++;
+        __quicklistInsertNode(quicklist, node, new_node, after);
+        _quicklistMergeNodes(quicklist, fill, node);
+    }
+
+    quicklist->count++;
+}
+
+void quicklistInsertBefore(quicklist *quicklist, const size_t fill,
+                           quicklistEntry *entry, void *value,
+                           const size_t sz) {
+    _quicklistInsert(quicklist, fill, entry, value, sz, 0);
+}
+
+void quicklistInsertAfter(quicklist *quicklist, const size_t fill,
+                          quicklistEntry *entry, void *value, const size_t sz) {
+    _quicklistInsert(quicklist, fill, entry, value, sz, 1);
+}
+
+/* Delete a range of elements from the quicklist.
+ *
+ * elements may span across multiple quicklistNodes, so we
+ * have to be careful about tracking where we start and end.
+ *
+ * Returns 1 if entries were deleted, 0 if nothing was deleted. */
+int quicklistDelRange(quicklist *quicklist, const long start,
+                      const long count) {
+    if (count <= 0)
+        return 0;
+
+    unsigned long extent = count; /* range is inclusive of start position */
+
+    if (start >= 0 && extent > (quicklist->count - start)) {
+        /* if requesting delete more elements than exist, limit to list size. */
+        extent = quicklist->count - start;
+    } else if (start < 0 && extent > (unsigned long)(-start)) {
+        /* else, if at negative offset, limit max size to rest of list. */
+        extent = -start; /* c.f. LREM -29 29; just delete until end. */
+    }
+
+    quicklistEntry entry;
+    if (!quicklistIndex(quicklist, start, &entry))
+        return 0;
+
+    D("Quicklist delete request for start %ld, count %ld, extent: %ld", start,
+      count, extent);
+    quicklistNode *node = entry.node;
+
+    /* iterate over next nodes until everything is deleted. */
+    while (extent) {
+        quicklistNode *next = node->next;
+
+        unsigned long del;
+        int delete_entire_node = 0;
+        if (entry.offset == 0 && extent >= node->count) {
+            /* If we are deleting more than the count of this node, we
+             * can just delete the entire node without ziplist math. */
+            delete_entire_node = 1;
+            del = node->count;
+        } else if (entry.offset >= 0 && extent >= node->count) {
+            /* If deleting more nodes after this one, calculate delete based
+             * on size of current node. */
+            del = node->count - entry.offset;
+        } else if (entry.offset < 0) {
+            /* If offset is negative, we are in the first run of this loop
+             * and we are deleting the entire range
+             * from this start offset to end of list.  Since the Negative
+             * offset is the number of elements until the tail of the list,
+             * just use it directly as the deletion count. */
+            del = -entry.offset;
+
+            /* If the positive offset is greater than the remaining extent,
+             * we only delete the remaining extent, not the entire offset.
+             */
+            if (del > extent)
+                del = extent;
+        } else {
+            /* else, we are deleting less than the extent of this node, so
+             * use extent directly. */
+            del = extent;
+        }
+
+        D("[%ld]: asking to del: %ld because offset: %d; (ENTIRE NODE: %d), "
+          "node count: %u",
+          extent, del, entry.offset, delete_entire_node, node->count);
+
+        if (delete_entire_node) {
+            __quicklistDelNode(quicklist, node);
+        } else {
+            node->zl = ziplistDeleteRange(node->zl, entry.offset, del);
+            node->count -= del;
+            quicklist->count -= del;
+            quicklistDeleteIfEmpty(quicklist, node);
+        }
+
+        extent -= del;
+
+        node = next;
+
+        entry.offset = 0;
+    }
+    return 1;
+}
+
+/* Passthrough to ziplistCompare() */
+int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len) {
+    return ziplistCompare(p1, p2, p2_len);
+}
+
+/* Returns a quicklist iterator 'iter'. After the initialization every
+ * call to quicklistNext() will return the next element of the quicklist. */
+quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction) {
+    quicklistIter *iter;
+
+    if ((iter = zmalloc(sizeof(*iter))) == NULL)
+        return NULL;
+
+    if (direction == AL_START_HEAD) {
+        iter->current = quicklist->head;
+        iter->offset = 0;
+    } else if (direction == AL_START_TAIL) {
+        iter->current = quicklist->tail;
+        iter->offset = -1;
+    }
+
+    iter->direction = direction;
+    iter->quicklist = quicklist;
+
+    iter->zi = NULL;
+
+    return iter;
+}
+
+/* Initialize an iterator at a specific offset 'idx' and make the iterator
+ * return nodes in 'direction' direction. */
+quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
+                                         const int direction,
+                                         const long long idx) {
+    quicklistEntry entry;
+
+    if (quicklistIndex(quicklist, idx, &entry)) {
+        quicklistIter *base = quicklistGetIterator(quicklist, direction);
+        base->zi = NULL;
+        base->current = entry.node;
+        base->offset = entry.offset;
+        return base;
+    } else {
+        return NULL;
+    }
+}
+
+/* Release iterator */
+void quicklistReleaseIterator(quicklistIter *iter) { zfree(iter); }
+
+/* Get next element in iterator.
+ *
+ * Note: You must NOT insert into the list while iterating over it.
+ * You *may* delete from the list while iterating using the
+ * quicklistDelEntry() function.
+ * If you insert into the quicklist while iterating, you should
+ * re-create the iterator after your addition.
+ *
+ * iter = quicklistGetIterator(quicklist,<direction>);
+ * quicklistEntry entry;
+ * while (quicklistNext(iter, &entry)) {
+ *     if (entry.value)
+ *          [[ use entry.value with entry.sz ]]
+ *     else
+ *          [[ use entry.longval ]]
+ * }
+ *
+ * Populates 'entry' with values for this iteration.
+ * Returns 0 when iteration is complete or if iteration not possible.
+ * If return value is 0, the contents of 'entry' are not valid.
+ */
+int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
+    initEntry(entry);
+
+    if (!iter) {
+        D("Returning because no iter!");
+        return 0;
+    }
+
+    entry->quicklist = iter->quicklist;
+    entry->node = iter->current;
+
+    if (!iter->current) {
+        D("Returning because current node is NULL")
+        return 0;
+    }
+
+    unsigned char *(*nextFn)(unsigned char *, unsigned char *) = NULL;
+    int offset_update = 0;
+
+    if (!iter->zi) {
+        /* If !zi, use current index. */
+        iter->zi = ziplistIndex(iter->current->zl, iter->offset);
+    } else {
+        /* else, use existing iterator offset and get prev/next as necessary. */
+        if (iter->direction == AL_START_HEAD) {
+            nextFn = ziplistNext;
+            offset_update = 1;
+        } else if (iter->direction == AL_START_TAIL) {
+            nextFn = ziplistPrev;
+            offset_update = -1;
+        }
+        iter->zi = nextFn(iter->current->zl, iter->zi);
+        iter->offset += offset_update;
+    }
+
+    entry->zi = iter->zi;
+    entry->offset = iter->offset;
+
+    if (iter->zi) {
+        /* Populate value from existing ziplist position */
+        ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
+        return 1;
+    } else {
+        /* We ran out of ziplist entries.
+         * Pick next node, update offset, then re-run retrieval. */
+        if (iter->direction == AL_START_HEAD) {
+            /* Forward traversal */
+            D("Jumping to start of next node");
+            iter->current = iter->current->next;
+            iter->offset = 0;
+        } else if (iter->direction == AL_START_TAIL) {
+            /* Reverse traversal */
+            D("Jumping to end of previous node");
+            iter->current = iter->current->prev;
+            iter->offset = -1;
+        }
+        iter->zi = NULL;
+        return quicklistNext(iter, entry);
+    }
+}
+
+/* Duplicate the quicklist. On out of memory NULL is returned.
+ * On success a copy of the original quicklist is returned.
+ *
+ * The original quicklist both on success or error is never modified.
+ *
+ * Returns newly allocated quicklist. */
+quicklist *quicklistDup(quicklist *orig) {
+    quicklist *copy;
+    int failure = 0;
+
+    if ((copy = quicklistCreate()) == NULL)
+        return NULL;
+
+    for (quicklistNode *current = orig->head; current;
+         current = current->next) {
+        quicklistNode *node = quicklistCreateNode();
+
+        size_t ziplen = ziplistBlobLen(current->zl);
+        if ((node->zl = zmalloc(ziplen)) == NULL) {
+            failure = 1;
+            break;
+        }
+        memcpy(node->zl, current->zl, ziplen);
+
+        node->count = current->count;
+        copy->count += node->count;
+
+        _quicklistInsertNodeAfter(copy, copy->tail, node);
+    }
+
+    /* copy->count must equal orig->count here */
+
+    if (failure) {
+        quicklistRelease(copy);
+        return NULL;
+    }
+
+    return copy;
+}
+
+/* Populate 'entry' with the element at the specified zero-based index
+ * where 0 is the head, 1 is the element next to head
+ * and so on. Negative integers are used in order to count
+ * from the tail, -1 is the last element, -2 the penultimate
+ * and so on. If the index is out of range 0 is returned.
+ *
+ * Returns 1 if element found
+ * Returns 0 if element not found */
+int quicklistIndex(const quicklist *quicklist, const long long idx,
+                   quicklistEntry *entry) {
+    quicklistNode *n;
+    unsigned long long accum = 0;
+    unsigned long long index;
+    int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */
+
+    initEntry(entry);
+    entry->quicklist = quicklist;
+
+    if (!forward) {
+        index = (-idx) - 1;
+        n = quicklist->tail;
+    } else {
+        index = idx;
+        n = quicklist->head;
+    }
+
+    if (index >= quicklist->count)
+        return 0;
+
+    while (n) {
+        if ((accum + n->count) > index) {
+            break;
+        } else {
+            D("Skipping over (%p) %u at accum %lld", (void *)n, n->count,
+              accum);
+            accum += n->count;
+            n = forward ? n->next : n->prev;
+        }
+    }
+
+    if (!n)
+        return 0;
+
+    D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n,
+      accum, index, index - accum, (-index) - 1 + accum);
+
+    entry->node = n;
+    if (forward) {
+        /* forward = normal head-to-tail offset. */
+        entry->offset = index - accum;
+    } else {
+        /* reverse = need negative offset for tail-to-head, so undo
+         * the result of the original if (index < 0) above. */
+        entry->offset = (-index) - 1 + accum;
+    }
+
+    entry->zi = ziplistIndex(entry->node->zl, entry->offset);
+    ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
+    return 1;
+}
+
+/* Rotate quicklist by moving the tail element to the head. */
+void quicklistRotate(quicklist *quicklist, const size_t fill) {
+    if (quicklist->count <= 1)
+        return;
+
+    /* First, get the tail entry */
+    quicklistNode *tail = quicklist->tail;
+    unsigned char *p = ziplistIndex(tail->zl, -1);
+    unsigned char *value;
+    long long longval;
+    unsigned int sz;
+    char longstr[32] = { 0 };
+    ziplistGet(p, &value, &sz, &longval);
+
+    /* If value found is NULL, then ziplistGet populated longval instead */
+    if (!value) {
+        /* Write the longval as a string so we can re-add it */
+        sz = ll2string(longstr, sizeof(longstr), longval);
+        value = (unsigned char *)longstr;
+    }
+
+    /* Add tail entry to head (must happen before tail is deleted). */
+    quicklistPushHead(quicklist, fill, value, sz);
+
+    /* If quicklist has only one node, the head ziplist is also the
+     * tail ziplist and PushHead() could have reallocated our single ziplist,
+     * which would make our pre-existing 'p' unusable. */
+    if (quicklist->len == 1)
+        p = ziplistIndex(tail->zl, -1);
+
+    /* Remove tail entry. */
+    quicklistDelIndex(quicklist, tail, &p);
+}
+
+/* pop from quicklist and return result in 'data' ptr.  Value of 'data'
+ * is the return value of 'saver' function pointer if the data is NOT a number.
+ *
+ * If the quicklist element is a long long, then the return value is returned in
+ * 'sval'.
+ *
+ * Return value of 0 means no elements available.
+ * Return value of 1 means check 'data' and 'sval' for values.
+ * If 'data' is set, use 'data' and 'sz'.  Otherwise, use 'sval'. */
+int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
+                       unsigned int *sz, long long *sval,
+                       void *(*saver)(unsigned char *data, unsigned int sz)) {
+    unsigned char *p;
+    unsigned char *vstr;
+    unsigned int vlen;
+    long long vlong;
+    int pos = (where == QUICKLIST_HEAD) ? 0 : -1;
+
+    if (quicklist->count == 0)
+        return 0;
+
+    if (data)
+        *data = NULL;
+    if (sz)
+        *sz = 0;
+    if (sval)
+        *sval = -123456789;
+
+    quicklistNode *node;
+    if (where == QUICKLIST_HEAD && quicklist->head) {
+        node = quicklist->head;
+    } else if (where == QUICKLIST_TAIL && quicklist->tail) {
+        node = quicklist->tail;
+    } else {
+        return 0;
+    }
+
+    p = ziplistIndex(node->zl, pos);
+    if (ziplistGet(p, &vstr, &vlen, &vlong)) {
+        if (vstr) {
+            if (data)
+                *data = saver(vstr, vlen);
+            if (sz)
+                *sz = vlen;
+        } else {
+            if (data)
+                *data = NULL;
+            if (sval)
+                *sval = vlong;
+        }
+        quicklistDelIndex(quicklist, node, &p);
+        return 1;
+    }
+    return 0;
+}
+
+/* Return a malloc'd copy of data passed in */
+static void *_quicklistSaver(unsigned char *data, unsigned int sz) {
+    unsigned char *vstr;
+    if (data) {
+        if ((vstr = zmalloc(sz)) == NULL)
+            return 0;
+        memcpy(data, vstr, sz);
+        return vstr;
+    }
+    return NULL;
+}
+
+/* Default pop function
+ *
+ * Returns malloc'd value from quicklist */
+int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
+                 unsigned int *sz, long long *slong) {
+    unsigned char *vstr;
+    unsigned int vlen;
+    long long vlong;
+    if (quicklist->count == 0)
+        return 0;
+    int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
+                                 _quicklistSaver);
+    if (data)
+        *data = vstr;
+    if (slong)
+        *slong = vlong;
+    if (sz)
+        *sz = vlen;
+    return ret;
+}
+
+/* Wrapper to allow argument-based switching between HEAD/TAIL pop */
+void quicklistPush(quicklist *quicklist, const size_t fill, void *value,
+                   const size_t sz, int where) {
+    if (where == QUICKLIST_HEAD) {
+        quicklistPushHead(quicklist, fill, value, sz);
+    } else if (where == QUICKLIST_TAIL) {
+        quicklistPushTail(quicklist, fill, value, sz);
+    }
+}
+
+/* The rest of this file is test cases and test helpers. */
+#ifdef REDIS_TEST
+#include <stdint.h>
+
+#define assert(_e)                                                             \
+    do {                                                                       \
+        if (!(_e)) {                                                           \
+            printf("\n\n=== ASSERTION FAILED ===\n");                          \
+            printf("==> %s:%d '%s' is not true\n", __FILE__, __LINE__, #_e);   \
+            err++;                                                             \
+        }                                                                      \
+    } while (0)
+
+#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
+
+#define OK printf("\tOK\n")
+
+#define ERROR                                                                  \
+    do {                                                                       \
+        printf("\tERROR!\n");                                                  \
+        err++;                                                                 \
+    } while (0)
+
+#define ERR(x, ...)                                                            \
+    do {                                                                       \
+        printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__);               \
+        printf("ERROR! " x "\n", __VA_ARGS__);                                 \
+        err++;                                                                 \
+    } while (0)
+
+#define TEST(name) printf("test — %s\n", name);
+#define TEST_DESC(name, ...) printf("test — " name "\n", __VA_ARGS__);
+
+#define QL_TEST_VERBOSE 0
+
+#define UNUSED(x) (void)(x)
+static void ql_info(quicklist *ql) {
+#if QL_TEST_VERBOSE
+    printf("Container length: %lu\n", ql->len);
+    printf("Container size: %lu\n", ql->count);
+    if (ql->head)
+        printf("\t(zsize head: %d)\n", ziplistLen(ql->head->zl));
+    if (ql->tail)
+        printf("\t(zsize tail: %d)\n", ziplistLen(ql->tail->zl));
+    printf("\n");
+#else
+    UNUSED(ql);
+#endif
+}
+
+/* Iterate over an entire quicklist.
+ * Print the list if 'print' == 1.
+ *
+ * Returns physical count of elements found by iterating over the list. */
+static int _itrprintr(quicklist *ql, int print, int forward) {
+    quicklistIter *iter =
+        quicklistGetIterator(ql, forward ? AL_START_HEAD : AL_START_TAIL);
+    quicklistEntry entry;
+    int i = 0;
+    int p = 0;
+    quicklistNode *prev = NULL;
+    while (quicklistNext(iter, &entry)) {
+        if (entry.node != prev) {
+            /* Count the number of list nodes too */
+            p++;
+            prev = entry.node;
+        }
+        if (print) {
+            printf("[%3d (%2d)]: [%.*s] (%lld)\n", i, p, entry.sz,
+                   (char *)entry.value, entry.longval);
+        }
+        i++;
+    }
+    quicklistReleaseIterator(iter);
+    return i;
+}
+static int itrprintr(quicklist *ql, int print) {
+    return _itrprintr(ql, print, 1);
+}
+
+static int itrprintr_rev(quicklist *ql, int print) {
+    return _itrprintr(ql, print, 0);
+}
+
+#define ql_verify(a, b, c, d, e)                                               \
+    do {                                                                       \
+        err += _ql_verify((a), (b), (c), (d), (e));                            \
+    } while (0)
+
+/* Verify list metadata matches physical list contents. */
+static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
+                      uint32_t head_count, uint32_t tail_count) {
+    int ok = 1;
+
+    ql_info(ql);
+    if (len != ql->len) {
+        yell("quicklist length wrong: expected %d, got %lu", len, ql->len);
+        ok = 0;
+    }
+
+    if (count != ql->count) {
+        yell("quicklist count wrong: expected %d, got %lu", count, ql->count);
+        ok = 0;
+    }
+
+    int loopr = itrprintr(ql, 0);
+    if (loopr != (int)ql->count) {
+        yell("quicklist cached count not match actual count: expected %lu, got "
+             "%d",
+             ql->count, loopr);
+        ok = 0;
+    }
+
+    int rloopr = itrprintr_rev(ql, 0);
+    if (loopr != rloopr) {
+        yell("quicklist has different forward count than reverse count!  "
+             "Forward count is %d, reverse count is %d.",
+             loopr, rloopr);
+        ok = 0;
+    }
+
+    if (ql->len == 0 && ok) {
+        OK;
+        return !ok;
+    }
+
+    if (head_count != ql->head->count &&
+        head_count != ziplistLen(ql->head->zl)) {
+        yell("quicklist head count wrong: expected %d, "
+             "got cached %d vs. actual %d",
+             head_count, ql->head->count, ziplistLen(ql->head->zl));
+        ok = 0;
+    }
+
+    if (tail_count != ql->tail->count &&
+        tail_count != ziplistLen(ql->tail->zl)) {
+        yell("quicklist tail count wrong: expected %d, "
+             "got cached %u vs. actual %d",
+             tail_count, ql->tail->count, ziplistLen(ql->tail->zl));
+        ok = 0;
+    }
+    if (ok)
+        OK;
+    return !ok;
+}
+
+/* Generate new string concatenating integer i against string 'prefix' */
+static char *genstr(char *prefix, int i) {
+    static char result[64] = { 0 };
+    snprintf(result, sizeof(result), "%s%d", prefix, i);
+    return result;
+}
+
+/* Test fill cap */
+#define F 32
+/* main test, but callable from other files */
+int quicklistTest(int argc, char *argv[]) {
+    unsigned int err = 0;
+
+    UNUSED(argc);
+    UNUSED(argv);
+
+    TEST("create list") {
+        quicklist *ql = quicklistCreate();
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("add to tail of empty list") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushTail(ql, F, "hello", 6);
+        /* 1 for head and 1 for tail beacuse 1 node = head = tail */
+        ql_verify(ql, 1, 1, 1, 1);
+        quicklistRelease(ql);
+    }
+
+    TEST("add to head of empty list") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushHead(ql, F, "hello", 6);
+        /* 1 for head and 1 for tail beacuse 1 node = head = tail */
+        ql_verify(ql, 1, 1, 1, 1);
+        quicklistRelease(ql);
+    }
+
+    for (size_t f = 0; f < 32; f++) {
+        TEST_DESC("add to tail 5x at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 5; i++)
+                quicklistPushTail(ql, f, genstr("hello", i), 32);
+            if (ql->count != 5)
+                ERROR;
+            if (f == 32)
+                ql_verify(ql, 1, 5, 5, 5);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 32; f++) {
+        TEST_DESC("add to head 5x at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 5; i++)
+                quicklistPushHead(ql, f, genstr("hello", i), 32);
+            if (ql->count != 5)
+                ERROR;
+            if (f == 32)
+                ql_verify(ql, 1, 5, 5, 5);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 512; f++) {
+        TEST_DESC("add to tail 500x at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 500; i++)
+                quicklistPushTail(ql, f, genstr("hello", i), 32);
+            if (ql->count != 500)
+                ERROR;
+            if (f == 32)
+                ql_verify(ql, 16, 500, 32, 20);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 512; f++) {
+        TEST_DESC("add to head 500x at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 500; i++)
+                quicklistPushHead(ql, f, genstr("hello", i), 32);
+            if (ql->count != 500)
+                ERROR;
+            if (f == 32)
+                ql_verify(ql, 16, 500, 20, 32);
+            quicklistRelease(ql);
+        }
+    }
+
+    TEST("rotate empty") {
+        quicklist *ql = quicklistCreate();
+        quicklistRotate(ql, F);
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    for (size_t f = 0; f < 32; f++) {
+        TEST("rotate one val once") {
+            quicklist *ql = quicklistCreate();
+            quicklistPushHead(ql, F, "hello", 6);
+            quicklistRotate(ql, F);
+            ql_verify(ql, 1, 1, 1, 1);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 1024; f++) {
+        TEST_DESC("rotate 500 val 5000 times at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            quicklistPushHead(ql, f, "900", 3);
+            quicklistPushHead(ql, f, "7000", 4);
+            quicklistPushHead(ql, f, "-1200", 5);
+            quicklistPushHead(ql, f, "42", 2);
+            for (int i = 0; i < 500; i++)
+                quicklistPushHead(ql, f, genstr("hello", i), 32);
+            ql_info(ql);
+            for (int i = 0; i < 5000; i++) {
+                ql_info(ql);
+                quicklistRotate(ql, f);
+            }
+            if (f == 1)
+                ql_verify(ql, 504, 504, 1, 1);
+            else if (f == 2)
+                ql_verify(ql, 252, 504, 2, 2);
+            else if (f == 32)
+                ql_verify(ql, 16, 504, 32, 24);
+            quicklistRelease(ql);
+        }
+    }
+
+    TEST("pop empty") {
+        quicklist *ql = quicklistCreate();
+        quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL);
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("pop 1 string from 1") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushHead(ql, F, genstr("hello", 331), 32);
+        unsigned char *data;
+        unsigned int sz;
+        long long lv;
+        ql_info(ql);
+        quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
+        assert(data != NULL);
+        assert(sz == 32);
+        zfree(data);
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("pop head 1 number from 1") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushHead(ql, F, "55513", 5);
+        unsigned char *data;
+        unsigned int sz;
+        long long lv;
+        ql_info(ql);
+        quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
+        assert(data == NULL);
+        assert(lv == 55513);
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("pop head 500 from 500") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushHead(ql, F, genstr("hello", i), 32);
+        ql_info(ql);
+        for (int i = 0; i < 500; i++) {
+            unsigned char *data;
+            unsigned int sz;
+            long long lv;
+            int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
+            assert(ret == 1);
+            assert(data != NULL);
+            assert(sz == 32);
+            zfree(data);
+        }
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("pop head 5000 from 500") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushHead(ql, F, genstr("hello", i), 32);
+        for (int i = 0; i < 5000; i++) {
+            unsigned char *data;
+            unsigned int sz;
+            long long lv;
+            int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
+            if (i < 500) {
+                assert(ret == 1);
+                assert(data != NULL);
+                assert(sz == 32);
+                zfree(data);
+            } else {
+                assert(ret == 0);
+            }
+        }
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("iterate forward over 500 list") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushHead(ql, F, genstr("hello", i), 32);
+        quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
+        quicklistEntry entry;
+        int i = 499, count = 0;
+        while (quicklistNext(iter, &entry)) {
+            char *h = genstr("hello", i);
+            if (strcmp((char *)entry.value, h))
+                ERR("value [%s] didn't match [%s] at position %d", entry.value,
+                    h, i);
+            i--;
+            count++;
+        }
+        if (count != 500)
+            ERR("Didn't iterate over exactly 500 elements (%d)", i);
+        ql_verify(ql, 16, 500, 20, 32);
+        quicklistReleaseIterator(iter);
+        quicklistRelease(ql);
+    }
+
+    TEST("iterate reverse over 500 list") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushHead(ql, F, genstr("hello", i), 32);
+        quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
+        quicklistEntry entry;
+        int i = 0;
+        while (quicklistNext(iter, &entry)) {
+            char *h = genstr("hello", i);
+            if (strcmp((char *)entry.value, h))
+                ERR("value [%s] didn't match [%s] at position %d", entry.value,
+                    h, i);
+            i++;
+        }
+        if (i != 500)
+            ERR("Didn't iterate over exactly 500 elements (%d)", i);
+        ql_verify(ql, 16, 500, 20, 32);
+        quicklistReleaseIterator(iter);
+        quicklistRelease(ql);
+    }
+
+    TEST("insert before with 0 elements") {
+        quicklist *ql = quicklistCreate();
+        quicklistEntry entry;
+        quicklistIndex(ql, 0, &entry);
+        quicklistInsertBefore(ql, F, &entry, "abc", 4);
+        ql_verify(ql, 1, 1, 1, 1);
+        quicklistRelease(ql);
+    }
+
+    TEST("insert after with 0 elements") {
+        quicklist *ql = quicklistCreate();
+        quicklistEntry entry;
+        quicklistIndex(ql, 0, &entry);
+        quicklistInsertAfter(ql, F, &entry, "abc", 4);
+        ql_verify(ql, 1, 1, 1, 1);
+        quicklistRelease(ql);
+    }
+
+    TEST("insert after 1 element") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushHead(ql, F, "hello", 6);
+        quicklistEntry entry;
+        quicklistIndex(ql, 0, &entry);
+        quicklistInsertAfter(ql, F, &entry, "abc", 4);
+        ql_verify(ql, 1, 2, 2, 2);
+        quicklistRelease(ql);
+    }
+
+    TEST("insert before 1 element") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushHead(ql, F, "hello", 6);
+        quicklistEntry entry;
+        quicklistIndex(ql, 0, &entry);
+        quicklistInsertAfter(ql, F, &entry, "abc", 4);
+        ql_verify(ql, 1, 2, 2, 2);
+        quicklistRelease(ql);
+    }
+
+    for (size_t f = 0; f < 12; f++) {
+        TEST_DESC("insert once in elements while iterating at fill %lu\n", f) {
+            quicklist *ql = quicklistCreate();
+            quicklistPushTail(ql, f, "abc", 3);
+            quicklistPushTail(ql, 1, "def", 3); /* force to unique node */
+            quicklistPushTail(ql, f, "bob", 3); /* force to reset for +3 */
+            quicklistPushTail(ql, f, "foo", 3);
+            quicklistPushTail(ql, f, "zoo", 3);
+
+            itrprintr(ql, 1);
+            /* insert "bar" before "bob" while iterating over list. */
+            quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
+            quicklistEntry entry;
+            while (quicklistNext(iter, &entry)) {
+                if (!strncmp((char *)entry.value, "bob", 3)) {
+                    /* Insert as fill = 1 so it spills into new node. */
+                    quicklistInsertBefore(ql, f, &entry, "bar", 3);
+                    /* NOTE! You can't continue iterating after an insert into
+                     * the list.  You *must* re-create your iterator again if
+                     * you want to traverse all entires. */
+                    break;
+                }
+            }
+            itrprintr(ql, 1);
+
+            /* verify results */
+            quicklistIndex(ql, 0, &entry);
+            if (strncmp((char *)entry.value, "abc", 3))
+                ERR("Value 0 didn't match, instead got: %.*s", entry.sz,
+                    entry.value);
+            quicklistIndex(ql, 1, &entry);
+            if (strncmp((char *)entry.value, "def", 3))
+                ERR("Value 1 didn't match, instead got: %.*s", entry.sz,
+                    entry.value);
+            quicklistIndex(ql, 2, &entry);
+            if (strncmp((char *)entry.value, "bar", 3))
+                ERR("Value 2 didn't match, instead got: %.*s", entry.sz,
+                    entry.value);
+            quicklistIndex(ql, 3, &entry);
+            if (strncmp((char *)entry.value, "bob", 3))
+                ERR("Value 3 didn't match, instead got: %.*s", entry.sz,
+                    entry.value);
+            quicklistIndex(ql, 4, &entry);
+            if (strncmp((char *)entry.value, "foo", 3))
+                ERR("Value 4 didn't match, instead got: %.*s", entry.sz,
+                    entry.value);
+            quicklistIndex(ql, 5, &entry);
+            if (strncmp((char *)entry.value, "zoo", 3))
+                ERR("Value 5 didn't match, instead got: %.*s", entry.sz,
+                    entry.value);
+            quicklistReleaseIterator(iter);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 1024; f++) {
+        TEST_DESC("insert [before] 250 new in middle of 500 elements at fill"
+                  " %ld",
+                  f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 500; i++)
+                quicklistPushTail(ql, f, genstr("hello", i), 32);
+            for (int i = 0; i < 250; i++) {
+                quicklistEntry entry;
+                quicklistIndex(ql, 250, &entry);
+                quicklistInsertBefore(ql, f, &entry, genstr("abc", i), 32);
+            }
+            if (f == 32)
+                ql_verify(ql, 25, 750, 32, 20);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 1024; f++) {
+        TEST_DESC(
+            "insert [after] 250 new in middle of 500 elements at fill %ld", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 500; i++)
+                quicklistPushHead(ql, f, genstr("hello", i), 32);
+            for (int i = 0; i < 250; i++) {
+                quicklistEntry entry;
+                quicklistIndex(ql, 250, &entry);
+                quicklistInsertAfter(ql, f, &entry, genstr("abc", i), 32);
+            }
+
+            if (ql->count != 750)
+                ERR("List size not 750, but rather %ld", ql->count);
+
+            if (f == 32)
+                ql_verify(ql, 26, 750, 20, 32);
+            quicklistRelease(ql);
+        }
+    }
+
+    TEST("duplicate empty list") {
+        quicklist *ql = quicklistCreate();
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklist *copy = quicklistDup(ql);
+        ql_verify(copy, 0, 0, 0, 0);
+        quicklistRelease(ql);
+        quicklistRelease(copy);
+    }
+
+    TEST("duplicate list of 1 element") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushHead(ql, F, genstr("hello", 3), 32);
+        ql_verify(ql, 1, 1, 1, 1);
+        quicklist *copy = quicklistDup(ql);
+        ql_verify(copy, 1, 1, 1, 1);
+        quicklistRelease(ql);
+        quicklistRelease(copy);
+    }
+
+    TEST("duplicate list of 500") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushHead(ql, F, genstr("hello", i), 32);
+        ql_verify(ql, 16, 500, 20, 32);
+
+        quicklist *copy = quicklistDup(ql);
+        ql_verify(copy, 16, 500, 20, 32);
+        quicklistRelease(ql);
+        quicklistRelease(copy);
+    }
+
+    for (size_t f = 0; f < 512; f++) {
+        TEST_DESC("index 1,200 from 500 list at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 500; i++)
+                quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
+            quicklistEntry entry;
+            quicklistIndex(ql, 1, &entry);
+            if (!strcmp((char *)entry.value, "hello2"))
+                OK;
+            else
+                ERR("Value: %s", entry.value);
+            quicklistIndex(ql, 200, &entry);
+            if (!strcmp((char *)entry.value, "hello201"))
+                OK;
+            else
+                ERR("Value: %s", entry.value);
+            quicklistRelease(ql);
+        }
+
+        TEST_DESC("index -1,-2 from 500 list at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 500; i++)
+                quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
+            quicklistEntry entry;
+            quicklistIndex(ql, -1, &entry);
+            if (!strcmp((char *)entry.value, "hello500"))
+                OK;
+            else
+                ERR("Value: %s", entry.value);
+            quicklistIndex(ql, -2, &entry);
+            if (!strcmp((char *)entry.value, "hello499"))
+                OK;
+            else
+                ERR("Value: %s", entry.value);
+            quicklistRelease(ql);
+        }
+
+        TEST_DESC("index -100 from 500 list at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 500; i++)
+                quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
+            quicklistEntry entry;
+            quicklistIndex(ql, -100, &entry);
+            if (!strcmp((char *)entry.value, "hello401"))
+                OK;
+            else
+                ERR("Value: %s", entry.value);
+            quicklistRelease(ql);
+        }
+
+        TEST_DESC("index too big +1 from 50 list at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            for (int i = 0; i < 50; i++)
+                quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
+            quicklistEntry entry;
+            if (quicklistIndex(ql, 50, &entry))
+                ERR("Index found at 50 with 50 list: %.*s", entry.sz,
+                    entry.value);
+            else
+                OK;
+            quicklistRelease(ql);
+        }
+    }
+
+    TEST("delete range empty list") {
+        quicklist *ql = quicklistCreate();
+        quicklistDelRange(ql, 5, 20);
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("delete range of entire node in list of one node") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 32; i++)
+            quicklistPushHead(ql, F, genstr("hello", i), 32);
+        ql_verify(ql, 1, 32, 32, 32);
+        quicklistDelRange(ql, 0, 32);
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("delete range of entire node with overflow counts") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 32; i++)
+            quicklistPushHead(ql, F, genstr("hello", i), 32);
+        ql_verify(ql, 1, 32, 32, 32);
+        quicklistDelRange(ql, 0, 128);
+        ql_verify(ql, 0, 0, 0, 0);
+        quicklistRelease(ql);
+    }
+
+    TEST("delete middle 100 of 500 list") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
+        ql_verify(ql, 16, 500, 32, 20);
+        quicklistDelRange(ql, 200, 100);
+        ql_verify(ql, 14, 400, 32, 20);
+        quicklistRelease(ql);
+    }
+
+    TEST("delete negative 1 from 500 list") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
+        ql_verify(ql, 16, 500, 32, 20);
+        quicklistDelRange(ql, -1, 1);
+        ql_verify(ql, 16, 499, 32, 19);
+        quicklistRelease(ql);
+    }
+
+    TEST("delete negative 1 from 500 list with overflow counts") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
+        ql_verify(ql, 16, 500, 32, 20);
+        quicklistDelRange(ql, -1, 128);
+        ql_verify(ql, 16, 499, 32, 19);
+        quicklistRelease(ql);
+    }
+
+    TEST("delete negative 100 from 500 list") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 500; i++)
+            quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
+        quicklistDelRange(ql, -100, 100);
+        ql_verify(ql, 13, 400, 32, 16);
+        quicklistRelease(ql);
+    }
+
+    TEST("delete -10 count 5 from 50 list") {
+        quicklist *ql = quicklistCreate();
+        for (int i = 0; i < 50; i++)
+            quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
+        ql_verify(ql, 2, 50, 32, 18);
+        quicklistDelRange(ql, -10, 5);
+        ql_verify(ql, 2, 45, 32, 13);
+        quicklistRelease(ql);
+    }
+
+    TEST("numbers only list read") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushTail(ql, F, "1111", 4);
+        quicklistPushTail(ql, F, "2222", 4);
+        quicklistPushTail(ql, F, "3333", 4);
+        quicklistPushTail(ql, F, "4444", 4);
+        ql_verify(ql, 1, 4, 4, 4);
+        quicklistEntry entry;
+        quicklistIndex(ql, 0, &entry);
+        if (entry.longval != 1111)
+            ERR("Not 1111, %lld", entry.longval);
+        quicklistIndex(ql, 1, &entry);
+        if (entry.longval != 2222)
+            ERR("Not 2222, %lld", entry.longval);
+        quicklistIndex(ql, 2, &entry);
+        if (entry.longval != 3333)
+            ERR("Not 3333, %lld", entry.longval);
+        quicklistIndex(ql, 3, &entry);
+        if (entry.longval != 4444)
+            ERR("Not 4444, %lld", entry.longval);
+        if (quicklistIndex(ql, 4, &entry))
+            ERR("Index past elements: %lld", entry.longval);
+        quicklistIndex(ql, -1, &entry);
+        if (entry.longval != 4444)
+            ERR("Not 4444 (reverse), %lld", entry.longval);
+        quicklistIndex(ql, -2, &entry);
+        if (entry.longval != 3333)
+            ERR("Not 3333 (reverse), %lld", entry.longval);
+        quicklistIndex(ql, -3, &entry);
+        if (entry.longval != 2222)
+            ERR("Not 2222 (reverse), %lld", entry.longval);
+        quicklistIndex(ql, -4, &entry);
+        if (entry.longval != 1111)
+            ERR("Not 1111 (reverse), %lld", entry.longval);
+        if (quicklistIndex(ql, -5, &entry))
+            ERR("Index past elements (reverse), %lld", entry.longval);
+        quicklistRelease(ql);
+    }
+
+    TEST("numbers larger list read") {
+        quicklist *ql = quicklistCreate();
+        char num[32];
+        long long nums[5000];
+        for (int i = 0; i < 5000; i++) {
+            nums[i] = -5157318210846258176 + i;
+            int sz = ll2string(num, sizeof(num), nums[i]);
+            quicklistPushTail(ql, F, num, sz);
+        }
+        quicklistPushTail(ql, F, "xxxxxxxxxxxxxxxxxxxx", 20);
+        quicklistEntry entry;
+        for (int i = 0; i < 5000; i++) {
+            quicklistIndex(ql, i, &entry);
+            if (entry.longval != nums[i])
+                ERR("[%d] Not longval %lld but rather %lld", i, nums[i],
+                    entry.longval);
+            entry.longval = 0xdeadbeef;
+        }
+        quicklistIndex(ql, 5000, &entry);
+        if (strncmp((char *)entry.value, "xxxxxxxxxxxxxxxxxxxx", 20))
+            ERR("String val not match: %s", entry.value);
+        ql_verify(ql, 157, 5001, 32, 9);
+        quicklistRelease(ql);
+    }
+
+    TEST("numbers larger list read B") {
+        quicklist *ql = quicklistCreate();
+        quicklistPushTail(ql, F, "99", 2);
+        quicklistPushTail(ql, F, "98", 2);
+        quicklistPushTail(ql, F, "xxxxxxxxxxxxxxxxxxxx", 20);
+        quicklistPushTail(ql, F, "96", 2);
+        quicklistPushTail(ql, F, "95", 2);
+        quicklistReplaceAtIndex(ql, 1, "foo", 3);
+        quicklistReplaceAtIndex(ql, -1, "bar", 3);
+        quicklistRelease(ql);
+        OK;
+    }
+
+    for (size_t f = 0; f < 16; f++) {
+        TEST_DESC("lrem test at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            char *words[] = { "abc", "foo", "bar",  "foobar", "foobared",
+                              "zap", "bar", "test", "foo" };
+            char *result[] = { "abc", "foo",  "foobar", "foobared",
+                               "zap", "test", "foo" };
+            char *resultB[] = { "abc",      "foo", "foobar",
+                                "foobared", "zap", "test" };
+            for (int i = 0; i < 9; i++)
+                quicklistPushTail(ql, f, words[i], strlen(words[i]));
+
+            /* lrem 0 bar */
+            quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
+            quicklistEntry entry;
+            int i = 0;
+            while (quicklistNext(iter, &entry)) {
+                if (quicklistCompare(entry.zi, (unsigned char *)"bar", 3)) {
+                    quicklistDelEntry(iter, &entry);
+                }
+                i++;
+            }
+            quicklistReleaseIterator(iter);
+
+            /* check result of lrem 0 bar */
+            iter = quicklistGetIterator(ql, AL_START_HEAD);
+            i = 0;
+            int ok = 1;
+            while (quicklistNext(iter, &entry)) {
+                /* Result must be: abc, foo, foobar, foobared, zap, test, foo */
+                if (strncmp((char *)entry.value, result[i], entry.sz)) {
+                    ERR("No match at position %d, got %.*s instead of %s", i,
+                        entry.sz, entry.value, result[i]);
+                    ok = 0;
+                }
+                i++;
+            }
+            quicklistReleaseIterator(iter);
+
+            quicklistPushTail(ql, f, "foo", 3);
+
+            /* lrem -2 foo */
+            iter = quicklistGetIterator(ql, AL_START_TAIL);
+            i = 0;
+            int del = 2;
+            while (quicklistNext(iter, &entry)) {
+                if (quicklistCompare(entry.zi, (unsigned char *)"foo", 3)) {
+                    quicklistDelEntry(iter, &entry);
+                    del--;
+                }
+                if (!del)
+                    break;
+                i++;
+            }
+            quicklistReleaseIterator(iter);
+
+            /* check result of lrem -2 foo */
+            /* (we're ignoring the '2' part and still deleting all foo because
+             * we only have two foo) */
+            iter = quicklistGetIterator(ql, AL_START_TAIL);
+            i = 0;
+            size_t resB = sizeof(resultB) / sizeof(*resultB);
+            while (quicklistNext(iter, &entry)) {
+                /* Result must be: abc, foo, foobar, foobared, zap, test, foo */
+                if (strncmp((char *)entry.value, resultB[resB - 1 - i],
+                            entry.sz)) {
+                    ERR("No match at position %d, got %.*s instead of %s", i,
+                        entry.sz, entry.value, resultB[resB - 1 - i]);
+                    ok = 0;
+                }
+                i++;
+            }
+
+            quicklistReleaseIterator(iter);
+            /* final result of all tests */
+            if (ok)
+                OK;
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 16; f++) {
+        TEST_DESC("iterate reverse + delete at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            quicklistPushTail(ql, f, "abc", 3);
+            quicklistPushTail(ql, f, "def", 3);
+            quicklistPushTail(ql, f, "hij", 3);
+            quicklistPushTail(ql, f, "jkl", 3);
+            quicklistPushTail(ql, f, "oop", 3);
+
+            quicklistEntry entry;
+            quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
+            int i = 0;
+            while (quicklistNext(iter, &entry)) {
+                if (quicklistCompare(entry.zi, (unsigned char *)"hij", 3)) {
+                    quicklistDelEntry(iter, &entry);
+                }
+                i++;
+            }
+            quicklistReleaseIterator(iter);
+
+            if (i != 5)
+                ERR("Didn't iterate 5 times, iterated %d times.", i);
+
+            /* Check results after deletion of "hij" */
+            iter = quicklistGetIterator(ql, AL_START_HEAD);
+            i = 0;
+            char *vals[] = { "abc", "def", "jkl", "oop" };
+            while (quicklistNext(iter, &entry)) {
+                if (!quicklistCompare(entry.zi, (unsigned char *)vals[i], 3)) {
+                    ERR("Value at %d didn't match %s\n", i, vals[i]);
+                }
+                i++;
+            }
+            quicklistReleaseIterator(iter);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 800; f++) {
+        TEST_DESC("iterator at index test at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            char num[32];
+            long long nums[5000];
+            for (int i = 0; i < 760; i++) {
+                nums[i] = -5157318210846258176 + i;
+                int sz = ll2string(num, sizeof(num), nums[i]);
+                quicklistPushTail(ql, f, num, sz);
+            }
+
+            quicklistEntry entry;
+            quicklistIter *iter =
+                quicklistGetIteratorAtIdx(ql, AL_START_HEAD, 437);
+            int i = 437;
+            while (quicklistNext(iter, &entry)) {
+                if (entry.longval != nums[i])
+                    ERR("Expected %lld, but got %lld", entry.longval, nums[i]);
+                i++;
+            }
+            quicklistReleaseIterator(iter);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 40; f++) {
+        TEST_DESC("ltrim test A at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            char num[32];
+            long long nums[5000];
+            for (int i = 0; i < 32; i++) {
+                nums[i] = -5157318210846258176 + i;
+                int sz = ll2string(num, sizeof(num), nums[i]);
+                quicklistPushTail(ql, f, num, sz);
+            }
+            if (f == 32)
+                ql_verify(ql, 1, 32, 32, 32);
+            /* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
+            quicklistDelRange(ql, 0, 25);
+            quicklistDelRange(ql, 0, 0);
+            quicklistEntry entry;
+            for (int i = 0; i < 7; i++) {
+                quicklistIndex(ql, i, &entry);
+                if (entry.longval != nums[25 + i])
+                    ERR("Deleted invalid range!  Expected %lld but got %lld",
+                        entry.longval, nums[25 + i]);
+            }
+            if (f == 32)
+                ql_verify(ql, 1, 7, 7, 7);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 40; f++) {
+        TEST_DESC("ltrim test B at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            char num[32];
+            long long nums[5000];
+            for (int i = 0; i < 33; i++) {
+                nums[i] = i;
+                int sz = ll2string(num, sizeof(num), nums[i]);
+                quicklistPushTail(ql, f, num, sz);
+            }
+            if (f == 32)
+                ql_verify(ql, 2, 33, 32, 1);
+            /* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */
+            quicklistDelRange(ql, 0, 5);
+            quicklistDelRange(ql, -16, 16);
+            if (f == 32)
+                ql_verify(ql, 1, 12, 12, 12);
+            quicklistEntry entry;
+            quicklistIndex(ql, 0, &entry);
+            if (entry.longval != 5)
+                ERR("A: longval not 5, but %lld", entry.longval);
+            else
+                OK;
+            quicklistIndex(ql, -1, &entry);
+            if (entry.longval != 16)
+                ERR("B! got instead: %lld", entry.longval);
+            else
+                OK;
+            quicklistPushTail(ql, f, "bobobob", 7);
+            quicklistIndex(ql, -1, &entry);
+            if (strncmp((char *)entry.value, "bobobob", 7))
+                ERR("Tail doesn't match bobobob, it's %.*s instead", entry.sz,
+                    entry.value);
+            for (int i = 0; i < 12; i++) {
+                quicklistIndex(ql, i, &entry);
+                if (entry.longval != nums[5 + i])
+                    ERR("Deleted invalid range!  Expected %lld but got %lld",
+                        entry.longval, nums[5 + i]);
+            }
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 40; f++) {
+        TEST_DESC("ltrim test C at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            char num[32];
+            long long nums[5000];
+            for (int i = 0; i < 33; i++) {
+                nums[i] = -5157318210846258176 + i;
+                int sz = ll2string(num, sizeof(num), nums[i]);
+                quicklistPushTail(ql, f, num, sz);
+            }
+            if (f == 32)
+                ql_verify(ql, 2, 33, 32, 1);
+            /* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
+            quicklistDelRange(ql, 0, 3);
+            quicklistDelRange(ql, -29, 4000); /* make sure not loop forever */
+            if (f == 32)
+                ql_verify(ql, 1, 1, 1, 1);
+            quicklistEntry entry;
+            quicklistIndex(ql, 0, &entry);
+            if (entry.longval != -5157318210846258173)
+                ERROR;
+            else
+                OK;
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 40; f++) {
+        TEST_DESC("ltrim test D at fill %lu", f) {
+            quicklist *ql = quicklistCreate();
+            char num[32];
+            long long nums[5000];
+            for (int i = 0; i < 33; i++) {
+                nums[i] = -5157318210846258176 + i;
+                int sz = ll2string(num, sizeof(num), nums[i]);
+                quicklistPushTail(ql, f, num, sz);
+            }
+            if (f == 32)
+                ql_verify(ql, 2, 33, 32, 1);
+            quicklistDelRange(ql, -12, 3);
+            if (ql->count != 30)
+                ERR("Didn't delete exactly three elements!  Count is: %lu",
+                    ql->count);
+            quicklistRelease(ql);
+        }
+    }
+
+    for (size_t f = 0; f < 72; f++) {
+        TEST_DESC("create quicklist from ziplist at fill %lu", f) {
+            unsigned char *zl = ziplistNew();
+            long long nums[32];
+            char num[32];
+            for (int i = 0; i < 33; i++) {
+                nums[i] = -5157318210846258176 + i;
+                int sz = ll2string(num, sizeof(num), nums[i]);
+                zl = ziplistPush(zl, (unsigned char *)num, sz, ZIPLIST_TAIL);
+            }
+            for (int i = 0; i < 33; i++) {
+                zl = ziplistPush(zl, (unsigned char *)genstr("hello", i), 32,
+                                 ZIPLIST_TAIL);
+            }
+            quicklist *ql = quicklistCreateFromZiplist(f, zl);
+            if (f == 1)
+                ql_verify(ql, 66, 66, 1, 1);
+            else if (f == 32)
+                ql_verify(ql, 3, 66, 32, 2);
+            else if (f == 66)
+                ql_verify(ql, 1, 66, 66, 66);
+            quicklistRelease(ql);
+        }
+    }
+
+    if (!err)
+        printf("ALL TESTS PASSED!\n");
+    else
+        ERR("Sorry, not all tests passed!  In fact, %d tests failed.", err);
+
+    return err;
+}
+#endif
diff --git a/src/quicklist.h b/src/quicklist.h
new file mode 100644
index 000000000..93b38d880
--- /dev/null
+++ b/src/quicklist.h
@@ -0,0 +1,120 @@
+/* quicklist.h - A generic doubly linked quicklist implementation
+ *
+ * Copyright (c) 2014, Matt Stancliff <matt@genges.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this quicklist of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this quicklist of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __QUICKLIST_H__
+#define __QUICKLIST_H__
+
+/* Node, quicklist, and Iterator are the only data structures used currently. */
+
+typedef struct quicklistNode {
+    struct quicklistNode *prev;
+    struct quicklistNode *next;
+    unsigned char *zl;
+    unsigned int count; /* cached count of items in ziplist */
+} quicklistNode;
+
+typedef struct quicklist {
+    quicklistNode *head;
+    quicklistNode *tail;
+    unsigned long len;   /* number of quicklistNodes */
+    unsigned long count; /* total count of all entries in all ziplists */
+} quicklist;
+
+typedef struct quicklistIter {
+    const quicklist *quicklist;
+    quicklistNode *current;
+    unsigned char *zi;
+    long offset; /* offset in current ziplist */
+    int direction;
+} quicklistIter;
+
+typedef struct quicklistEntry {
+    const quicklist *quicklist;
+    quicklistNode *node;
+    unsigned char *zi;
+    unsigned char *value;
+    unsigned int sz;
+    long long longval;
+    int offset;
+} quicklistEntry;
+
+#define QUICKLIST_HEAD 0
+#define QUICKLIST_TAIL -1
+
+/* Prototypes */
+quicklist *quicklistCreate(void);
+void quicklistRelease(quicklist *quicklist);
+quicklist *quicklistPushHead(quicklist *quicklist, const size_t fill,
+                             void *value, const size_t sz);
+quicklist *quicklistPushTail(quicklist *quicklist, const size_t fill,
+                             void *value, const size_t sz);
+void quicklistPush(quicklist *quicklist, const size_t fill, void *value,
+                   const size_t sz, int where);
+void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
+quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
+                                            const size_t fill,
+                                            unsigned char *zl);
+quicklist *quicklistCreateFromZiplist(size_t fill, unsigned char *zl);
+void quicklistInsertAfter(quicklist *quicklist, const size_t fill,
+                          quicklistEntry *node, void *value, const size_t sz);
+void quicklistInsertBefore(quicklist *quicklist, const size_t fill,
+                           quicklistEntry *node, void *value, const size_t sz);
+void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
+int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
+                            int sz);
+int quicklistDelRange(quicklist *quicklist, const long start, const long stop);
+quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction);
+quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
+                                         int direction, const long long idx);
+int quicklistNext(quicklistIter *iter, quicklistEntry *node);
+void quicklistReleaseIterator(quicklistIter *iter);
+quicklist *quicklistDup(quicklist *orig);
+int quicklistIndex(const quicklist *quicklist, const long long index,
+                   quicklistEntry *entry);
+void quicklistRewind(quicklist *quicklist, quicklistIter *li);
+void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
+void quicklistRotate(quicklist *quicklist, const size_t fill);
+int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
+                       unsigned int *sz, long long *sval,
+                       void *(*saver)(unsigned char *data, unsigned int sz));
+int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
+                 unsigned int *sz, long long *slong);
+unsigned int quicklistCount(quicklist *ql);
+int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
+
+#ifdef REDIS_TEST
+int quicklistTest(int argc, char *argv[]);
+#endif
+
+/* Directions for iterators */
+#define AL_START_HEAD 0
+#define AL_START_TAIL 1
+
+#endif /* __QUICKLIST_H__ */
diff --git a/src/rdb.c b/src/rdb.c
index 325e5a62f..70989897a 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -433,9 +433,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
     case REDIS_STRING:
         return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
     case REDIS_LIST:
-        if (o->encoding == REDIS_ENCODING_ZIPLIST)
-            return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
-        else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
+        if (o->encoding == REDIS_ENCODING_QUICKLIST)
             return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
         else
             redisPanic("Unknown list encoding");
@@ -477,7 +475,7 @@ int rdbLoadObjectType(rio *rdb) {
 
 /* Save a Redis object. Returns -1 on error, number of bytes written on success. */
 int rdbSaveObject(rio *rdb, robj *o) {
-    int n, nwritten = 0;
+    int n = 0, nwritten = 0;
 
     if (o->type == REDIS_STRING) {
         /* Save a string value */
@@ -485,25 +483,23 @@ int rdbSaveObject(rio *rdb, robj *o) {
         nwritten += n;
     } else if (o->type == REDIS_LIST) {
         /* Save a list value */
-        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
-            size_t l = ziplistBlobLen((unsigned char*)o->ptr);
+        if (o->encoding == REDIS_ENCODING_QUICKLIST) {
+            quicklist *list = o->ptr;
+            quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
+            quicklistEntry entry;
 
-            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
-            nwritten += n;
-        } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
-            list *list = o->ptr;
-            listIter li;
-            listNode *ln;
-
-            if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1;
+            if ((n = rdbSaveLen(rdb,quicklistCount(list))) == -1) return -1;
             nwritten += n;
 
-            listRewind(list,&li);
-            while((ln = listNext(&li))) {
-                robj *eleobj = listNodeValue(ln);
-                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
+            while (quicklistNext(li,&entry)) {
+                if (entry.value) {
+                    if ((n = rdbSaveRawString(rdb,entry.value,entry.sz)) == -1) return -1;
+                } else {
+                    if ((n = rdbSaveLongLongAsStringObject(rdb,entry.longval)) == -1) return -1;
+                }
                 nwritten += n;
             }
+            quicklistReleaseIterator(li);
         } else {
             redisPanic("Unknown list encoding");
         }
@@ -831,33 +827,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
         /* Read list value */
         if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
 
-        /* Use a real list when there are too many entries */
-        if (len > server.list_max_ziplist_entries) {
-            o = createListObject();
-        } else {
-            o = createZiplistObject();
-        }
+        o = createQuicklistObject();
 
         /* Load every single element of the list */
         while(len--) {
             if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
-
-            /* If we are using a ziplist and the value is too big, convert
-             * the object to a real list. */
-            if (o->encoding == REDIS_ENCODING_ZIPLIST &&
-                sdsEncodedObject(ele) &&
-                sdslen(ele->ptr) > server.list_max_ziplist_value)
-                    listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
-
-            if (o->encoding == REDIS_ENCODING_ZIPLIST) {
-                dec = getDecodedObject(ele);
-                o->ptr = ziplistPush(o->ptr,dec->ptr,sdslen(dec->ptr),REDIS_TAIL);
-                decrRefCount(dec);
-                decrRefCount(ele);
-            } else {
-                ele = tryObjectEncoding(ele);
-                listAddNodeTail(o->ptr,ele);
-            }
+            dec = getDecodedObject(ele);
+            size_t len = sdslen(dec->ptr);
+            size_t zlen = server.list_max_ziplist_entries;
+            o->ptr = quicklistPushTail(o->ptr, zlen, dec->ptr, len);
+            decrRefCount(dec);
+            decrRefCount(ele);
         }
     } else if (rdbtype == REDIS_RDB_TYPE_SET) {
         /* Read list/set value */
@@ -1048,8 +1028,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
             case REDIS_RDB_TYPE_LIST_ZIPLIST:
                 o->type = REDIS_LIST;
                 o->encoding = REDIS_ENCODING_ZIPLIST;
-                if (ziplistLen(o->ptr) > server.list_max_ziplist_entries)
-                    listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
+                listTypeConvert(o,REDIS_ENCODING_QUICKLIST);
                 break;
             case REDIS_RDB_TYPE_SET_INTSET:
                 o->type = REDIS_SET;
diff --git a/src/redis.c b/src/redis.c
index 01912b79b..ee88eddb5 100644
--- a/src/redis.c
+++ b/src/redis.c
@@ -3659,6 +3659,8 @@ int main(int argc, char **argv) {
     if (argc == 3 && !strcasecmp(argv[1], "test")) {
         if (!strcasecmp(argv[2], "ziplist")) {
             return ziplistTest(argc, argv);
+        } else if (!strcasecmp(argv[2], "quicklist")) {
+            quicklistTest(argc, argv);
         } else if (!strcasecmp(argv[2], "intset")) {
             return intsetTest(argc, argv);
         } else if (!strcasecmp(argv[2], "zipmap")) {
diff --git a/src/redis.h b/src/redis.h
index 1720c24bd..38c072478 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -65,6 +65,7 @@ typedef long long mstime_t; /* millisecond time type. */
 #include "util.h"    /* Misc functions useful in many places */
 #include "latency.h" /* Latency monitor API */
 #include "sparkline.h" /* ASII graphs API */
+#include "quicklist.h"
 
 /* Following includes allow test functions to be called from Redis main() */
 #include "zipmap.h"
@@ -204,6 +205,7 @@ typedef long long mstime_t; /* millisecond time type. */
 #define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
 #define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
 #define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
+#define REDIS_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
 
 /* Defines related to the dump file format. To store 32 bits lengths for short
  * keys requires a lot of space, so we check the most significant 2 bits of
@@ -964,15 +966,13 @@ typedef struct {
     robj *subject;
     unsigned char encoding;
     unsigned char direction; /* Iteration direction */
-    unsigned char *zi;
-    listNode *ln;
+    quicklistIter *iter;
 } listTypeIterator;
 
 /* Structure for an entry while iterating over a list. */
 typedef struct {
     listTypeIterator *li;
-    unsigned char *zi;  /* Entry in ziplist */
-    listNode *ln;       /* Entry in linked list */
+    quicklistEntry entry; /* Entry in quicklist */
 } listTypeEntry;
 
 /* Structure to hold set iteration abstraction. */
@@ -1099,7 +1099,7 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry);
 robj *listTypeGet(listTypeEntry *entry);
 void listTypeInsert(listTypeEntry *entry, robj *value, int where);
 int listTypeEqual(listTypeEntry *entry, robj *o);
-void listTypeDelete(listTypeEntry *entry);
+void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
 void listTypeConvert(robj *subject, int enc);
 void unblockClientWaitingData(redisClient *c);
 void handleClientsBlockedOnLists(void);
@@ -1137,7 +1137,7 @@ robj *getDecodedObject(robj *o);
 size_t stringObjectLen(robj *o);
 robj *createStringObjectFromLongLong(long long value);
 robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
-robj *createListObject(void);
+robj *createQuicklistObject(void);
 robj *createZiplistObject(void);
 robj *createSetObject(void);
 robj *createIntsetObject(void);
diff --git a/src/sort.c b/src/sort.c
index 2b3276448..74b27cb67 100644
--- a/src/sort.c
+++ b/src/sort.c
@@ -220,7 +220,7 @@ void sortCommand(redisClient *c) {
     if (sortval)
         incrRefCount(sortval);
     else
-        sortval = createListObject();
+        sortval = createQuicklistObject();
 
     /* The SORT command has an SQL-alike syntax, parse it */
     while(j < c->argc) {
@@ -420,6 +420,7 @@ void sortCommand(redisClient *c) {
     } else {
         redisPanic("Unknown type");
     }
+    printf("j: %d; vectorlen: %d\n", j, vectorlen);
     redisAssertWithInfo(c,sortval,j == vectorlen);
 
     /* Now it's time to load the right scores in the sorting vector */
@@ -509,7 +510,7 @@ void sortCommand(redisClient *c) {
             }
         }
     } else {
-        robj *sobj = createZiplistObject();
+        robj *sobj = createQuicklistObject();
 
         /* STORE option specified, set the sorting result as a List object */
         for (j = start; j <= end; j++) {
diff --git a/src/t_list.c b/src/t_list.c
index fc27331f5..b40e0089a 100644
--- a/src/t_list.c
+++ b/src/t_list.c
@@ -33,75 +33,40 @@
  * List API
  *----------------------------------------------------------------------------*/
 
-/* Check the argument length to see if it requires us to convert the ziplist
- * to a real list. Only check raw-encoded objects because integer encoded
- * objects are never too long. */
-void listTypeTryConversion(robj *subject, robj *value) {
-    if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
-    if (sdsEncodedObject(value) &&
-        sdslen(value->ptr) > server.list_max_ziplist_value)
-            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
-}
-
 /* The function pushes an element to the specified list object 'subject',
  * at head or tail position as specified by 'where'.
  *
  * There is no need for the caller to increment the refcount of 'value' as
  * the function takes care of it if needed. */
 void listTypePush(robj *subject, robj *value, int where) {
-    /* Check if we need to convert the ziplist */
-    listTypeTryConversion(subject,value);
-    if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
-        ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
-            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
-
-    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
-        int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
+    if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
+        int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
         value = getDecodedObject(value);
-        subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
+        size_t len = sdslen(value->ptr);
+        size_t zlen = server.list_max_ziplist_entries;
+        /* If this value is greater than our allowed values, create it in
+         * an isolated ziplist */
+        quicklistPush(subject->ptr, zlen, value->ptr, len, pos);
         decrRefCount(value);
-    } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
-        if (where == REDIS_HEAD) {
-            listAddNodeHead(subject->ptr,value);
-        } else {
-            listAddNodeTail(subject->ptr,value);
-        }
-        incrRefCount(value);
     } else {
         redisPanic("Unknown list encoding");
     }
 }
 
+void *listPopSaver(unsigned char *data, unsigned int sz) {
+    return createStringObject((char*)data,sz);
+}
+
 robj *listTypePop(robj *subject, int where) {
+    long long vlong;
     robj *value = NULL;
-    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
-        unsigned char *p;
-        unsigned char *vstr;
-        unsigned int vlen;
-        long long vlong;
-        int pos = (where == REDIS_HEAD) ? 0 : -1;
-        p = ziplistIndex(subject->ptr,pos);
-        if (ziplistGet(p,&vstr,&vlen,&vlong)) {
-            if (vstr) {
-                value = createStringObject((char*)vstr,vlen);
-            } else {
+
+    int ql_where = where == REDIS_HEAD ? QUICKLIST_HEAD : QUICKLIST_TAIL;
+    if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
+        if (quicklistPopCustom(subject->ptr, ql_where, (unsigned char **)&value,
+                               NULL, &vlong, listPopSaver)) {
+            if (!value)
                 value = createStringObjectFromLongLong(vlong);
-            }
-            /* We only need to delete an element when it exists */
-            subject->ptr = ziplistDelete(subject->ptr,&p);
-        }
-    } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
-        list *list = subject->ptr;
-        listNode *ln;
-        if (where == REDIS_HEAD) {
-            ln = listFirst(list);
-        } else {
-            ln = listLast(list);
-        }
-        if (ln != NULL) {
-            value = listNodeValue(ln);
-            incrRefCount(value);
-            listDelNode(list,ln);
         }
     } else {
         redisPanic("Unknown list encoding");
@@ -110,25 +75,28 @@ robj *listTypePop(robj *subject, int where) {
 }
 
 unsigned long listTypeLength(robj *subject) {
-    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
-        return ziplistLen(subject->ptr);
-    } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
-        return listLength((list*)subject->ptr);
+    if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
+        return quicklistCount(subject->ptr);
     } else {
         redisPanic("Unknown list encoding");
     }
 }
 
 /* Initialize an iterator at the specified index. */
-listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction) {
+listTypeIterator *listTypeInitIterator(robj *subject, long index,
+                                       unsigned char direction) {
     listTypeIterator *li = zmalloc(sizeof(listTypeIterator));
     li->subject = subject;
     li->encoding = subject->encoding;
     li->direction = direction;
-    if (li->encoding == REDIS_ENCODING_ZIPLIST) {
-        li->zi = ziplistIndex(subject->ptr,index);
-    } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
-        li->ln = listIndex(subject->ptr,index);
+    li->iter = NULL;
+    /* REDIS_HEAD means start at TAIL and move *towards* head.
+     * REDIS_TAIL means start at HEAD and move *towards tail. */
+    int iter_direction =
+        direction == REDIS_HEAD ? AL_START_TAIL : AL_START_HEAD;
+    if (li->encoding == REDIS_ENCODING_QUICKLIST) {
+        li->iter = quicklistGetIteratorAtIdx(li->subject->ptr,
+                                             iter_direction, index);
     } else {
         redisPanic("Unknown list encoding");
     }
@@ -137,6 +105,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char
 
 /* Clean up the iterator. */
 void listTypeReleaseIterator(listTypeIterator *li) {
+    zfree(li->iter);
     zfree(li);
 }
 
@@ -148,24 +117,8 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
     redisAssert(li->subject->encoding == li->encoding);
 
     entry->li = li;
-    if (li->encoding == REDIS_ENCODING_ZIPLIST) {
-        entry->zi = li->zi;
-        if (entry->zi != NULL) {
-            if (li->direction == REDIS_TAIL)
-                li->zi = ziplistNext(li->subject->ptr,li->zi);
-            else
-                li->zi = ziplistPrev(li->subject->ptr,li->zi);
-            return 1;
-        }
-    } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
-        entry->ln = li->ln;
-        if (entry->ln != NULL) {
-            if (li->direction == REDIS_TAIL)
-                li->ln = li->ln->next;
-            else
-                li->ln = li->ln->prev;
-            return 1;
-        }
+    if (li->encoding == REDIS_ENCODING_QUICKLIST) {
+        return quicklistNext(li->iter, &entry->entry);
     } else {
         redisPanic("Unknown list encoding");
     }
@@ -174,24 +127,14 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
 
 /* Return entry or NULL at the current position of the iterator. */
 robj *listTypeGet(listTypeEntry *entry) {
-    listTypeIterator *li = entry->li;
     robj *value = NULL;
-    if (li->encoding == REDIS_ENCODING_ZIPLIST) {
-        unsigned char *vstr;
-        unsigned int vlen;
-        long long vlong;
-        redisAssert(entry->zi != NULL);
-        if (ziplistGet(entry->zi,&vstr,&vlen,&vlong)) {
-            if (vstr) {
-                value = createStringObject((char*)vstr,vlen);
-            } else {
-                value = createStringObjectFromLongLong(vlong);
-            }
+    if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
+        if (entry->entry.value) {
+            value = createStringObject((char *)entry->entry.value,
+                                       entry->entry.sz);
+        } else {
+            value = createStringObjectFromLongLong(entry->entry.longval);
         }
-    } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
-        redisAssert(entry->ln != NULL);
-        value = listNodeValue(entry->ln);
-        incrRefCount(value);
     } else {
         redisPanic("Unknown list encoding");
     }
@@ -199,30 +142,19 @@ robj *listTypeGet(listTypeEntry *entry) {
 }
 
 void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
-    robj *subject = entry->li->subject;
-    if (entry->li->encoding == REDIS_ENCODING_ZIPLIST) {
+    if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
         value = getDecodedObject(value);
+        sds str = value->ptr;
+        size_t len = sdslen(str);
+        size_t zlen = server.list_max_ziplist_entries;
         if (where == REDIS_TAIL) {
-            unsigned char *next = ziplistNext(subject->ptr,entry->zi);
-
-            /* When we insert after the current element, but the current element
-             * is the tail of the list, we need to do a push. */
-            if (next == NULL) {
-                subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),REDIS_TAIL);
-            } else {
-                subject->ptr = ziplistInsert(subject->ptr,next,value->ptr,sdslen(value->ptr));
-            }
-        } else {
-            subject->ptr = ziplistInsert(subject->ptr,entry->zi,value->ptr,sdslen(value->ptr));
+            quicklistInsertAfter((quicklist *)entry->entry.quicklist, zlen,
+                                 &entry->entry, str, len);
+        } else if (where == REDIS_HEAD) {
+            quicklistInsertBefore((quicklist *)entry->entry.quicklist, zlen,
+                                  &entry->entry, str, len);
         }
         decrRefCount(value);
-    } else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
-        if (where == REDIS_TAIL) {
-            listInsertNode(subject->ptr,entry->ln,value,AL_START_TAIL);
-        } else {
-            listInsertNode(subject->ptr,entry->ln,value,AL_START_HEAD);
-        }
-        incrRefCount(value);
     } else {
         redisPanic("Unknown list encoding");
     }
@@ -230,59 +162,33 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
 
 /* Compare the given object with the entry at the current position. */
 int listTypeEqual(listTypeEntry *entry, robj *o) {
-    listTypeIterator *li = entry->li;
-    if (li->encoding == REDIS_ENCODING_ZIPLIST) {
+    if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
         redisAssertWithInfo(NULL,o,sdsEncodedObject(o));
-        return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr));
-    } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
-        return equalStringObjects(o,listNodeValue(entry->ln));
+        return quicklistCompare(entry->entry.zi,o->ptr,sdslen(o->ptr));
     } else {
         redisPanic("Unknown list encoding");
     }
 }
 
 /* Delete the element pointed to. */
-void listTypeDelete(listTypeEntry *entry) {
-    listTypeIterator *li = entry->li;
-    if (li->encoding == REDIS_ENCODING_ZIPLIST) {
-        unsigned char *p = entry->zi;
-        li->subject->ptr = ziplistDelete(li->subject->ptr,&p);
-
-        /* Update position of the iterator depending on the direction */
-        if (li->direction == REDIS_TAIL)
-            li->zi = p;
-        else
-            li->zi = ziplistPrev(li->subject->ptr,p);
-    } else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
-        listNode *next;
-        if (li->direction == REDIS_TAIL)
-            next = entry->ln->next;
-        else
-            next = entry->ln->prev;
-        listDelNode(li->subject->ptr,entry->ln);
-        li->ln = next;
+void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry) {
+    if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
+        quicklistDelEntry(iter->iter, &entry->entry);
     } else {
         redisPanic("Unknown list encoding");
     }
 }
 
+/* Create a quicklist from a single ziplist */
 void listTypeConvert(robj *subject, int enc) {
-    listTypeIterator *li;
-    listTypeEntry entry;
-    redisAssertWithInfo(NULL,subject,subject->type == REDIS_LIST);
+    redisAssertWithInfo(NULL,subject,subject->type==REDIS_LIST);
+    redisAssertWithInfo(NULL,subject,subject->encoding==REDIS_ENCODING_ZIPLIST);
 
-    if (enc == REDIS_ENCODING_LINKEDLIST) {
-        list *l = listCreate();
-        listSetFreeMethod(l,decrRefCountVoid);
+    if (enc == REDIS_ENCODING_QUICKLIST) {
+        size_t zlen = server.list_max_ziplist_entries;
 
-        /* listTypeGet returns a robj with incremented refcount */
-        li = listTypeInitIterator(subject,0,REDIS_TAIL);
-        while (listTypeNext(li,&entry)) listAddNodeTail(l,listTypeGet(&entry));
-        listTypeReleaseIterator(li);
-
-        subject->encoding = REDIS_ENCODING_LINKEDLIST;
-        zfree(subject->ptr);
-        subject->ptr = l;
+        subject->encoding = REDIS_ENCODING_QUICKLIST;
+        subject->ptr = quicklistCreateFromZiplist(zlen, subject->ptr);
     } else {
         redisPanic("Unsupported list conversion");
     }
@@ -304,7 +210,7 @@ void pushGenericCommand(redisClient *c, int where) {
     for (j = 2; j < c->argc; j++) {
         c->argv[j] = tryObjectEncoding(c->argv[j]);
         if (!lobj) {
-            lobj = createZiplistObject();
+            lobj = createQuicklistObject();
             dbAdd(c->db,c->argv[1],lobj);
         }
         listTypePush(lobj,c->argv[j],where);
@@ -338,13 +244,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
         checkType(c,subject,REDIS_LIST)) return;
 
     if (refval != NULL) {
-        /* We're not sure if this value can be inserted yet, but we cannot
-         * convert the list inside the iterator. We don't want to loop over
-         * the list twice (once to see if the value can be inserted and once
-         * to do the actual insert), so we assume this value can be inserted
-         * and convert the ziplist to a regular list if necessary. */
-        listTypeTryConversion(subject,val);
-
         /* Seek refval from head to tail */
         iter = listTypeInitIterator(subject,0,REDIS_TAIL);
         while (listTypeNext(iter,&entry)) {
@@ -357,10 +256,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
         listTypeReleaseIterator(iter);
 
         if (inserted) {
-            /* Check if the length exceeds the ziplist length threshold. */
-            if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
-                ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
-                    listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
             signalModifiedKey(c->db,c->argv[1]);
             notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
                                 c->argv[1],c->db->id);
@@ -418,31 +313,19 @@ void lindexCommand(redisClient *c) {
     if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
         return;
 
-    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
-        unsigned char *p;
-        unsigned char *vstr;
-        unsigned int vlen;
-        long long vlong;
-        p = ziplistIndex(o->ptr,index);
-        if (ziplistGet(p,&vstr,&vlen,&vlong)) {
-            if (vstr) {
-                value = createStringObject((char*)vstr,vlen);
+    if (o->encoding == REDIS_ENCODING_QUICKLIST) {
+        quicklistEntry entry;
+        if (quicklistIndex(o->ptr, index, &entry)) {
+            if (entry.value) {
+                value = createStringObject((char*)entry.value,entry.sz);
             } else {
-                value = createStringObjectFromLongLong(vlong);
+                value = createStringObjectFromLongLong(entry.longval);
             }
             addReplyBulk(c,value);
             decrRefCount(value);
         } else {
             addReply(c,shared.nullbulk);
         }
-    } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
-        listNode *ln = listIndex(o->ptr,index);
-        if (ln != NULL) {
-            value = listNodeValue(ln);
-            addReplyBulk(c,value);
-        } else {
-            addReply(c,shared.nullbulk);
-        }
     } else {
         redisPanic("Unknown list encoding");
     }
@@ -452,35 +335,18 @@ void lsetCommand(redisClient *c) {
     robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
     if (o == NULL || checkType(c,o,REDIS_LIST)) return;
     long index;
-    robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
+    robj *value = c->argv[3];
 
     if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
         return;
 
-    listTypeTryConversion(o,value);
-    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
-        unsigned char *p, *zl = o->ptr;
-        p = ziplistIndex(zl,index);
-        if (p == NULL) {
+    if (o->encoding == REDIS_ENCODING_QUICKLIST) {
+        quicklist *ql = o->ptr;
+        int replaced = quicklistReplaceAtIndex(ql, index,
+                                               value->ptr, sdslen(value->ptr));
+        if (!replaced) {
             addReply(c,shared.outofrangeerr);
         } else {
-            o->ptr = ziplistDelete(o->ptr,&p);
-            value = getDecodedObject(value);
-            o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
-            decrRefCount(value);
-            addReply(c,shared.ok);
-            signalModifiedKey(c->db,c->argv[1]);
-            notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
-            server.dirty++;
-        }
-    } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
-        listNode *ln = listIndex(o->ptr,index);
-        if (ln == NULL) {
-            addReply(c,shared.outofrangeerr);
-        } else {
-            decrRefCount((robj*)listNodeValue(ln));
-            listNodeValue(ln) = value;
-            incrRefCount(value);
             addReply(c,shared.ok);
             signalModifiedKey(c->db,c->argv[1]);
             notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
@@ -549,43 +415,28 @@ void lrangeCommand(redisClient *c) {
 
     /* Return the result in form of a multi-bulk reply */
     addReplyMultiBulkLen(c,rangelen);
-    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
-        unsigned char *p = ziplistIndex(o->ptr,start);
-        unsigned char *vstr;
-        unsigned int vlen;
-        long long vlong;
+    if (o->encoding == REDIS_ENCODING_QUICKLIST) {
+        listTypeIterator *iter = listTypeInitIterator(o, start, REDIS_TAIL);
 
         while(rangelen--) {
-            ziplistGet(p,&vstr,&vlen,&vlong);
-            if (vstr) {
-                addReplyBulkCBuffer(c,vstr,vlen);
+            listTypeEntry entry;
+            listTypeNext(iter, &entry);
+            quicklistEntry *qe = &entry.entry;
+            if (qe->value) {
+                addReplyBulkCBuffer(c,qe->value,qe->sz);
             } else {
-                addReplyBulkLongLong(c,vlong);
+                addReplyBulkLongLong(c,qe->longval);
             }
-            p = ziplistNext(o->ptr,p);
-        }
-    } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
-        listNode *ln;
-
-        /* If we are nearest to the end of the list, reach the element
-         * starting from tail and going backward, as it is faster. */
-        if (start > llen/2) start -= llen;
-        ln = listIndex(o->ptr,start);
-
-        while(rangelen--) {
-            addReplyBulk(c,ln->value);
-            ln = ln->next;
         }
+        listTypeReleaseIterator(iter);
     } else {
-        redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
+        redisPanic("List encoding is not QUICKLIST!");
     }
 }
 
 void ltrimCommand(redisClient *c) {
     robj *o;
-    long start, end, llen, j, ltrim, rtrim;
-    list *list;
-    listNode *ln;
+    long start, end, llen, ltrim, rtrim;
 
     if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
         (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
@@ -612,19 +463,9 @@ void ltrimCommand(redisClient *c) {
     }
 
     /* Remove list elements to perform the trim */
-    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
-        o->ptr = ziplistDeleteRange(o->ptr,0,ltrim);
-        o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim);
-    } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
-        list = o->ptr;
-        for (j = 0; j < ltrim; j++) {
-            ln = listFirst(list);
-            listDelNode(list,ln);
-        }
-        for (j = 0; j < rtrim; j++) {
-            ln = listLast(list);
-            listDelNode(list,ln);
-        }
+    if (o->encoding == REDIS_ENCODING_QUICKLIST) {
+        quicklistDelRange(o->ptr,0,ltrim);
+        quicklistDelRange(o->ptr,-rtrim,rtrim);
     } else {
         redisPanic("Unknown list encoding");
     }
@@ -641,10 +482,9 @@ void ltrimCommand(redisClient *c) {
 
 void lremCommand(redisClient *c) {
     robj *subject, *obj;
-    obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
+    obj = c->argv[3];
     long toremove;
     long removed = 0;
-    listTypeEntry entry;
 
     if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != REDIS_OK))
         return;
@@ -652,10 +492,6 @@ void lremCommand(redisClient *c) {
     subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
     if (subject == NULL || checkType(c,subject,REDIS_LIST)) return;
 
-    /* Make sure obj is raw when we're dealing with a ziplist */
-    if (subject->encoding == REDIS_ENCODING_ZIPLIST)
-        obj = getDecodedObject(obj);
-
     listTypeIterator *li;
     if (toremove < 0) {
         toremove = -toremove;
@@ -664,9 +500,10 @@ void lremCommand(redisClient *c) {
         li = listTypeInitIterator(subject,0,REDIS_TAIL);
     }
 
+    listTypeEntry entry;
     while (listTypeNext(li,&entry)) {
         if (listTypeEqual(&entry,obj)) {
-            listTypeDelete(&entry);
+            listTypeDelete(li, &entry);
             server.dirty++;
             removed++;
             if (toremove && removed == toremove) break;
@@ -674,11 +511,10 @@ void lremCommand(redisClient *c) {
     }
     listTypeReleaseIterator(li);
 
-    /* Clean up raw encoded object */
-    if (subject->encoding == REDIS_ENCODING_ZIPLIST)
-        decrRefCount(obj);
+    if (listTypeLength(subject) == 0) {
+        dbDelete(c->db,c->argv[1]);
+    }
 
-    if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
     addReplyLongLong(c,removed);
     if (removed) signalModifiedKey(c->db,c->argv[1]);
 }
@@ -702,7 +538,7 @@ void lremCommand(redisClient *c) {
 void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
     /* Create the list if the key does not exist */
     if (!dstobj) {
-        dstobj = createZiplistObject();
+        dstobj = createQuicklistObject();
         dbAdd(c->db,dstkey,dstobj);
     }
     signalModifiedKey(c->db,dstkey);
@@ -1010,7 +846,9 @@ void handleClientsBlockedOnLists(void) {
                     }
                 }
 
-                if (listTypeLength(o) == 0) dbDelete(rl->db,rl->key);
+                if (listTypeLength(o) == 0) {
+                    dbDelete(rl->db,rl->key);
+                }
                 /* We don't call signalModifiedKey() as it was already called
                  * when an element was pushed on the list. */
             }
diff --git a/tests/support/test.tcl b/tests/support/test.tcl
index 7d390cc47..31371c567 100644
--- a/tests/support/test.tcl
+++ b/tests/support/test.tcl
@@ -19,9 +19,12 @@ proc assert_match {pattern value} {
     }
 }
 
-proc assert_equal {expected value} {
+proc assert_equal {expected value {detail ""}} {
     if {$expected ne $value} {
-        error "assertion:Expected '$value' to be equal to '$expected'"
+        if {$detail ne ""} {
+            set detail " (detail: $detail)"
+        }
+        error "assertion:Expected '$value' to be equal to '$expected'$detail"
     }
 }
 
diff --git a/tests/unit/aofrw.tcl b/tests/unit/aofrw.tcl
index a2d74168f..4fdbdc6c6 100644
--- a/tests/unit/aofrw.tcl
+++ b/tests/unit/aofrw.tcl
@@ -77,10 +77,10 @@ start_server {tags {"aofrw"}} {
     }
 
     foreach d {string int} {
-        foreach e {ziplist linkedlist} {
+        foreach e {quicklist} {
             test "AOF rewrite of list with $e encoding, $d data" {
                 r flushall
-                if {$e eq {ziplist}} {set len 10} else {set len 1000}
+                set len 1000
                 for {set j 0} {$j < $len} {incr j} {
                     if {$d eq {string}} {
                         set data [randstring 0 16 alpha]
diff --git a/tests/unit/sort.tcl b/tests/unit/sort.tcl
index 8ebd75965..5ae48d450 100644
--- a/tests/unit/sort.tcl
+++ b/tests/unit/sort.tcl
@@ -36,9 +36,9 @@ start_server {
     }
 
     foreach {num cmd enc title} {
-        16 lpush ziplist "Ziplist"
-        1000 lpush linkedlist "Linked list"
-        10000 lpush linkedlist "Big Linked list"
+        16 lpush quicklist "Old Ziplist"
+        1000 lpush quicklist "Old Linked list"
+        10000 lpush quicklist "Old Big Linked list"
         16 sadd intset "Intset"
         1000 sadd hashtable "Hash table"
         10000 sadd hashtable "Big Hash table"
@@ -85,14 +85,14 @@ start_server {
         r sort tosort BY weight_* store sort-res
         assert_equal $result [r lrange sort-res 0 -1]
         assert_equal 16 [r llen sort-res]
-        assert_encoding ziplist sort-res
+        assert_encoding quicklist sort-res
     }
 
     test "SORT BY hash field STORE" {
         r sort tosort BY wobj_*->weight store sort-res
         assert_equal $result [r lrange sort-res 0 -1]
         assert_equal 16 [r llen sort-res]
-        assert_encoding ziplist sort-res
+        assert_encoding quicklist sort-res
     }
 
     test "SORT extracts STORE correctly" {
diff --git a/tests/unit/type/list-2.tcl b/tests/unit/type/list-2.tcl
index bf6a055eb..d25873912 100644
--- a/tests/unit/type/list-2.tcl
+++ b/tests/unit/type/list-2.tcl
@@ -2,7 +2,7 @@ start_server {
     tags {"list"}
     overrides {
         "list-max-ziplist-value" 16
-        "list-max-ziplist-entries" 256
+        "list-max-ziplist-entries" 4
     }
 } {
     source "tests/unit/type/list-common.tcl"
@@ -28,14 +28,18 @@ start_server {
                 for {set i 0} {$i < 1000} {incr i} {
                     set min [expr {int(rand()*$startlen)}]
                     set max [expr {$min+int(rand()*$startlen)}]
+                    set before_len [llength $mylist]
+                    set before_len_r [r llen mylist]
                     set mylist [lrange $mylist $min $max]
                     r ltrim mylist $min $max
-                    assert_equal $mylist [r lrange mylist 0 -1]
+                    assert_equal $mylist [r lrange mylist 0 -1] "failed trim"
 
+                    set starting [r llen mylist]
                     for {set j [r llen mylist]} {$j < $startlen} {incr j} {
                         set str [randomInt 9223372036854775807]
                         r rpush mylist $str
                         lappend mylist $str
+                        assert_equal $mylist [r lrange mylist 0 -1] "failed append match"
                     }
                 }
             }
diff --git a/tests/unit/type/list-3.tcl b/tests/unit/type/list-3.tcl
index 94f9a0b79..81ef9b3e5 100644
--- a/tests/unit/type/list-3.tcl
+++ b/tests/unit/type/list-3.tcl
@@ -2,7 +2,7 @@ start_server {
     tags {list ziplist}
     overrides {
         "list-max-ziplist-value" 200000
-        "list-max-ziplist-entries" 256
+        "list-max-ziplist-entries" 16
     }
 } {
     test {Explicit regression for a list bug} {
diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl
index c8e26602b..0c0c07761 100644
--- a/tests/unit/type/list.tcl
+++ b/tests/unit/type/list.tcl
@@ -2,24 +2,24 @@ start_server {
     tags {"list"}
     overrides {
         "list-max-ziplist-value" 16
-        "list-max-ziplist-entries" 256
+        "list-max-ziplist-entries" 5
     }
 } {
     source "tests/unit/type/list-common.tcl"
 
     test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} {
         # first lpush then rpush
-        assert_equal 1 [r lpush myziplist1 a]
-        assert_equal 2 [r rpush myziplist1 b]
-        assert_equal 3 [r rpush myziplist1 c]
+        assert_equal 1 [r lpush myziplist1 aa]
+        assert_equal 2 [r rpush myziplist1 bb]
+        assert_equal 3 [r rpush myziplist1 cc]
         assert_equal 3 [r llen myziplist1]
-        assert_equal a [r lindex myziplist1 0]
-        assert_equal b [r lindex myziplist1 1]
-        assert_equal c [r lindex myziplist1 2]
+        assert_equal aa [r lindex myziplist1 0]
+        assert_equal bb [r lindex myziplist1 1]
+        assert_equal cc [r lindex myziplist1 2]
         assert_equal {} [r lindex myziplist2 3]
-        assert_equal c [r rpop myziplist1]
-        assert_equal a [r lpop myziplist1]
-        assert_encoding ziplist myziplist1
+        assert_equal cc [r rpop myziplist1]
+        assert_equal aa [r lpop myziplist1]
+        assert_encoding quicklist myziplist1
 
         # first rpush then lpush
         assert_equal 1 [r rpush myziplist2 a]
@@ -32,13 +32,13 @@ start_server {
         assert_equal {} [r lindex myziplist2 3]
         assert_equal a [r rpop myziplist2]
         assert_equal c [r lpop myziplist2]
-        assert_encoding ziplist myziplist2
+        assert_encoding quicklist myziplist2
     }
 
     test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - regular list} {
         # first lpush then rpush
         assert_equal 1 [r lpush mylist1 $largevalue(linkedlist)]
-        assert_encoding linkedlist mylist1
+        assert_encoding quicklist mylist1
         assert_equal 2 [r rpush mylist1 b]
         assert_equal 3 [r rpush mylist1 c]
         assert_equal 3 [r llen mylist1]
@@ -51,7 +51,7 @@ start_server {
 
         # first rpush then lpush
         assert_equal 1 [r rpush mylist2 $largevalue(linkedlist)]
-        assert_encoding linkedlist mylist2
+        assert_encoding quicklist mylist2
         assert_equal 2 [r lpush mylist2 b]
         assert_equal 3 [r lpush mylist2 c]
         assert_equal 3 [r llen mylist2]
@@ -74,34 +74,22 @@ start_server {
         assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1]
     }
 
-    test {DEL a list - ziplist} {
-        assert_equal 1 [r del myziplist2]
-        assert_equal 0 [r exists myziplist2]
-        assert_equal 0 [r llen myziplist2]
-    }
-
-    test {DEL a list - regular list} {
+    test {DEL a list} {
         assert_equal 1 [r del mylist2]
         assert_equal 0 [r exists mylist2]
         assert_equal 0 [r llen mylist2]
     }
 
-    proc create_ziplist {key entries} {
+    proc create_list {key entries} {
         r del $key
         foreach entry $entries { r rpush $key $entry }
-        assert_encoding ziplist $key
-    }
-
-    proc create_linkedlist {key entries} {
-        r del $key
-        foreach entry $entries { r rpush $key $entry }
-        assert_encoding linkedlist $key
+        assert_encoding quicklist $key
     }
 
     foreach {type large} [array get largevalue] {
         test "BLPOP, BRPOP: single existing list - $type" {
             set rd [redis_deferring_client]
-            create_$type blist "a b $large c d"
+            create_list blist "a b $large c d"
 
             $rd blpop blist 1
             assert_equal {blist a} [$rd read]
@@ -116,8 +104,8 @@ start_server {
 
         test "BLPOP, BRPOP: multiple existing lists - $type" {
             set rd [redis_deferring_client]
-            create_$type blist1 "a $large c"
-            create_$type blist2 "d $large f"
+            create_list blist1 "a $large c"
+            create_list blist2 "d $large f"
 
             $rd blpop blist1 blist2 1
             assert_equal {blist1 a} [$rd read]
@@ -137,7 +125,7 @@ start_server {
         test "BLPOP, BRPOP: second list has an entry - $type" {
             set rd [redis_deferring_client]
             r del blist1
-            create_$type blist2 "d $large f"
+            create_list blist2 "d $large f"
 
             $rd blpop blist1 blist2 1
             assert_equal {blist2 d} [$rd read]
@@ -151,7 +139,7 @@ start_server {
             r del target
 
             set rd [redis_deferring_client]
-            create_$type blist "a b $large c d"
+            create_list blist "a b $large c d"
 
             $rd brpoplpush blist target 1
             assert_equal d [$rd read]
@@ -517,28 +505,28 @@ start_server {
 
     foreach {type large} [array get largevalue] {
         test "LPUSHX, RPUSHX - $type" {
-            create_$type xlist "$large c"
+            create_list xlist "$large c"
             assert_equal 3 [r rpushx xlist d]
             assert_equal 4 [r lpushx xlist a]
             assert_equal "a $large c d" [r lrange xlist 0 -1]
         }
 
         test "LINSERT - $type" {
-            create_$type xlist "a $large c d"
-            assert_equal 5 [r linsert xlist before c zz]
-            assert_equal "a $large zz c d" [r lrange xlist 0 10]
-            assert_equal 6 [r linsert xlist after c yy]
-            assert_equal "a $large zz c yy d" [r lrange xlist 0 10]
-            assert_equal 7 [r linsert xlist after d dd]
-            assert_equal -1 [r linsert xlist after bad ddd]
-            assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10]
-            assert_equal 8 [r linsert xlist before a aa]
-            assert_equal -1 [r linsert xlist before bad aaa]
-            assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10]
+            create_list xlist "a $large c d"
+            assert_equal 5 [r linsert xlist before c zz] "before c"
+            assert_equal "a $large zz c d" [r lrange xlist 0 10] "lrangeA"
+            assert_equal 6 [r linsert xlist after c yy] "after c"
+            assert_equal "a $large zz c yy d" [r lrange xlist 0 10] "lrangeB"
+            assert_equal 7 [r linsert xlist after d dd] "after d"
+            assert_equal -1 [r linsert xlist after bad ddd] "after bad"
+            assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeC"
+            assert_equal 8 [r linsert xlist before a aa] "before a"
+            assert_equal -1 [r linsert xlist before bad aaa] "before bad"
+            assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeD"
 
             # check inserting integer encoded value
-            assert_equal 9 [r linsert xlist before aa 42]
-            assert_equal 42 [r lrange xlist 0 0]
+            assert_equal 9 [r linsert xlist before aa 42] "before aa"
+            assert_equal 42 [r lrange xlist 0 0] "lrangeE"
         }
     }
 
@@ -547,55 +535,7 @@ start_server {
         set e
     } {*ERR*syntax*error*}
 
-    test {LPUSHX, RPUSHX convert from ziplist to list} {
-        set large $largevalue(linkedlist)
-
-        # convert when a large value is pushed
-        create_ziplist xlist a
-        assert_equal 2 [r rpushx xlist $large]
-        assert_encoding linkedlist xlist
-        create_ziplist xlist a
-        assert_equal 2 [r lpushx xlist $large]
-        assert_encoding linkedlist xlist
-
-        # convert when the length threshold is exceeded
-        create_ziplist xlist [lrepeat 256 a]
-        assert_equal 257 [r rpushx xlist b]
-        assert_encoding linkedlist xlist
-        create_ziplist xlist [lrepeat 256 a]
-        assert_equal 257 [r lpushx xlist b]
-        assert_encoding linkedlist xlist
-    }
-
-    test {LINSERT convert from ziplist to list} {
-        set large $largevalue(linkedlist)
-
-        # convert when a large value is inserted
-        create_ziplist xlist a
-        assert_equal 2 [r linsert xlist before a $large]
-        assert_encoding linkedlist xlist
-        create_ziplist xlist a
-        assert_equal 2 [r linsert xlist after a $large]
-        assert_encoding linkedlist xlist
-
-        # convert when the length threshold is exceeded
-        create_ziplist xlist [lrepeat 256 a]
-        assert_equal 257 [r linsert xlist before a a]
-        assert_encoding linkedlist xlist
-        create_ziplist xlist [lrepeat 256 a]
-        assert_equal 257 [r linsert xlist after a a]
-        assert_encoding linkedlist xlist
-
-        # don't convert when the value could not be inserted
-        create_ziplist xlist [lrepeat 256 a]
-        assert_equal -1 [r linsert xlist before foo a]
-        assert_encoding ziplist xlist
-        create_ziplist xlist [lrepeat 256 a]
-        assert_equal -1 [r linsert xlist after foo a]
-        assert_encoding ziplist xlist
-    }
-
-    foreach {type num} {ziplist 250 linkedlist 500} {
+    foreach {type num} {quicklist 250 quicklist 500} {
         proc check_numbered_list_consistency {key} {
             set len [r llen $key]
             for {set i 0} {$i < $len} {incr i} {
@@ -664,16 +604,16 @@ start_server {
     foreach {type large} [array get largevalue] {
         test "RPOPLPUSH base case - $type" {
             r del mylist1 mylist2
-            create_$type mylist1 "a $large c d"
+            create_list mylist1 "a $large c d"
             assert_equal d [r rpoplpush mylist1 mylist2]
             assert_equal c [r rpoplpush mylist1 mylist2]
             assert_equal "a $large" [r lrange mylist1 0 -1]
             assert_equal "c d" [r lrange mylist2 0 -1]
-            assert_encoding ziplist mylist2
+            assert_encoding quicklist mylist2
         }
 
         test "RPOPLPUSH with the same list as src and dst - $type" {
-            create_$type mylist "a $large c"
+            create_list mylist "a $large c"
             assert_equal "a $large c" [r lrange mylist 0 -1]
             assert_equal c [r rpoplpush mylist mylist]
             assert_equal "c a $large" [r lrange mylist 0 -1]
@@ -681,8 +621,8 @@ start_server {
 
         foreach {othertype otherlarge} [array get largevalue] {
             test "RPOPLPUSH with $type source and existing target $othertype" {
-                create_$type srclist "a b c $large"
-                create_$othertype dstlist "$otherlarge"
+                create_list srclist "a b c $large"
+                create_list dstlist "$otherlarge"
                 assert_equal $large [r rpoplpush srclist dstlist]
                 assert_equal c [r rpoplpush srclist dstlist]
                 assert_equal "a b" [r lrange srclist 0 -1]
@@ -691,7 +631,7 @@ start_server {
                 # When we rpoplpush'ed a large value, dstlist should be
                 # converted to the same encoding as srclist.
                 if {$type eq "linkedlist"} {
-                    assert_encoding linkedlist dstlist
+                    assert_encoding quicklist dstlist
                 }
             }
         }
@@ -713,7 +653,7 @@ start_server {
     }
 
     test {RPOPLPUSH against non list dst key} {
-        create_ziplist srclist {a b c d}
+        create_list srclist {a b c d}
         r set dstlist x
         assert_error WRONGTYPE* {r rpoplpush srclist dstlist}
         assert_type string dstlist
@@ -727,7 +667,7 @@ start_server {
 
     foreach {type large} [array get largevalue] {
         test "Basic LPOP/RPOP - $type" {
-            create_$type mylist "$large 1 2"
+            create_list mylist "$large 1 2"
             assert_equal $large [r lpop mylist]
             assert_equal 2 [r rpop mylist]
             assert_equal 1 [r lpop mylist]
@@ -745,7 +685,7 @@ start_server {
         assert_error WRONGTYPE* {r rpop notalist}
     }
 
-    foreach {type num} {ziplist 250 linkedlist 500} {
+    foreach {type num} {quicklist 250 quicklist 500} {
         test "Mass RPOP/LPOP - $type" {
             r del mylist
             set sum1 0
@@ -765,24 +705,24 @@ start_server {
 
     foreach {type large} [array get largevalue] {
         test "LRANGE basics - $type" {
-            create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
+            create_list mylist "$large 1 2 3 4 5 6 7 8 9"
             assert_equal {1 2 3 4 5 6 7 8} [r lrange mylist 1 -2]
             assert_equal {7 8 9} [r lrange mylist -3 -1]
             assert_equal {4} [r lrange mylist 4 4]
         }
 
         test "LRANGE inverted indexes - $type" {
-            create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
+            create_list mylist "$large 1 2 3 4 5 6 7 8 9"
             assert_equal {} [r lrange mylist 6 2]
         }
 
         test "LRANGE out of range indexes including the full list - $type" {
-            create_$type mylist "$large 1 2 3"
+            create_list mylist "$large 1 2 3"
             assert_equal "$large 1 2 3" [r lrange mylist -1000 1000]
         }
 
         test "LRANGE out of range negative end index - $type" {
-            create_$type mylist "$large 1 2 3"
+            create_list mylist "$large 1 2 3"
             assert_equal $large [r lrange mylist 0 -4]
             assert_equal {} [r lrange mylist 0 -5]
         }
@@ -796,7 +736,7 @@ start_server {
         proc trim_list {type min max} {
             upvar 1 large large
             r del mylist
-            create_$type mylist "1 2 3 4 $large"
+            create_list mylist "1 2 3 4 $large"
             r ltrim mylist $min $max
             r lrange mylist 0 -1
         }
@@ -825,7 +765,7 @@ start_server {
 
     foreach {type large} [array get largevalue] {
         test "LSET - $type" {
-            create_$type mylist "99 98 $large 96 95"
+            create_list mylist "99 98 $large 96 95"
             r lset mylist 1 foo
             r lset mylist -1 bar
             assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1]
@@ -847,7 +787,7 @@ start_server {
 
     foreach {type e} [array get largevalue] {
         test "LREM remove all the occurrences - $type" {
-            create_$type mylist "$e foo bar foobar foobared zap bar test foo"
+            create_list mylist "$e foo bar foobar foobared zap bar test foo"
             assert_equal 2 [r lrem mylist 0 bar]
             assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1]
         }
@@ -863,7 +803,7 @@ start_server {
         }
 
         test "LREM starting from tail with negative count - $type" {
-            create_$type mylist "$e foo bar foobar foobared zap bar test foo foo"
+            create_list mylist "$e foo bar foobar foobared zap bar test foo foo"
             assert_equal 1 [r lrem mylist -1 bar]
             assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1]
         }
@@ -874,7 +814,7 @@ start_server {
         }
 
         test "LREM deleting objects that may be int encoded - $type" {
-            create_$type myotherlist "$e 1 2 3"
+            create_list myotherlist "$e 1 2 3"
             assert_equal 1 [r lrem myotherlist 1 2]
             assert_equal 3 [r llen myotherlist]
         }