diff --git a/src/perf-tests/sort.ts b/src/perf-tests/sort.ts
index 5c490875986c4121737b1a667de596b0456845b0..b5449793e40ed8d7302c71e73d3d6bde7e65bd9b 100644
--- a/src/perf-tests/sort.ts
+++ b/src/perf-tests/sort.ts
@@ -1,11 +1,23 @@
 import * as B from 'benchmark'
 import * as Sort from '../structure/collections/sort'
 
+function shuffle(a: number[]) {
+    for (let i = a.length - 1; i > 0; i--) {
+        const j = Math.floor(Math.random() * (i + 1));
+        const t = a[i];
+        a[i] = a[j];
+        a[j] = t;
+    }
+    return a;
+}
+
 function createTestData(n: number) {
     const data = new Int32Array(n); //new Array(n);
     for (let i = 0; i < n; i++) {
-        data[i] = (n * Math.random()) | 0;
+        data[i] = i;
+        //data[i] = (n * Math.random()) | 0;
     }
+    shuffle(data as any);
     return data;
 }
 
@@ -24,21 +36,51 @@ export function checkSorted(arr: ArrayLike<number>) {
     return true;
 }
 
-const _data = createTestData(10000);
-const data = () => copyArray(_data);
 
-const suite = new B.Suite();
+export function runTest(size: number) {
+    const _data = createTestData(size);
+
+    const dataCopies: number[][] = [];
+    let dataOffset = 0;
+    function prepareData() {
+        dataOffset = 0;
+        for (let i = 0; i < 100; i++) {
+            dataCopies[i] = copyArray(_data);
+        }
+    }
+
+    function getData() {
+        if (dataOffset < dataCopies.length) return dataCopies[dataOffset++];
+        return copyArray(_data);
+    }
+
+    prepareData();
+
+    const suite = new B.Suite();
+
 
-function le(x: number, y: number) { return x - y; }
+    function le(x: number, y: number) { return x - y; }
+
+    function name(n: string) { return `${n} (${size} elems)` }
+
+    // TODO: the data copying skewes the benchmark -- write a simple benchmark util that allows for a preparation step.
+    suite
+        .add(name('native'), () => Array.prototype.sort.call(getData(), le))
+        .add(name('qsort'), () => Sort.sortArray(getData()))
+        //.add(name('qsort'), () => Sort.sort(getData(), 0, _data.length, Sort.arrayLess, Sort.arraySwap))
+        .add(name('native sorted'), () => Array.prototype.sort.call(_data, le))
+        .add(name('qsort sorted'), () => Sort.sortArray(_data))
+        //.add(name('qsort sorted'), () => Sort.sort(_data, 0, _data.length, Sort.arrayLess, Sort.arraySwap))
+        .on('cycle', (e: any) => {
+            prepareData();
+            console.log(String(e.target));
+        })
+        .run();
+    console.log('---------------------');
+}
 
-suite
-    .add('native', () => Array.prototype.sort.call(data(), le))
-    .add('qsort (array asc)', () => Sort.sortArray(data()))
-    .add('qsort (generic)', () => Sort.sort(data(), _data.length, Sort.arrayLess, Sort.arraySwap))
-    .add('native sorted', () => Array.prototype.sort.call(_data, le))
-    .add('qsort sorted (array asc)', () => Sort.sortArray(_data))
-    .add('qsort sorted (generic)', () => Sort.sort(_data, _data.length, Sort.arrayLess, Sort.arraySwap))
-    .on('cycle', (e: any) => {
-        console.log(String(e.target));
-    })
-    .run();
\ No newline at end of file
+//runTest(10);
+//runTest(100);
+//runTest(1000);
+runTest(10000);
+//runTest(100000);
\ No newline at end of file
diff --git a/src/structure/collections/linked-set.ts b/src/structure/collections/linked-set.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9749ede223e770b6e476b697f8339c6e5ece5409
--- /dev/null
+++ b/src/structure/collections/linked-set.ts
@@ -0,0 +1 @@
+// TODO: fixed length doubly linked list used for graph traversal
\ No newline at end of file
diff --git a/src/structure/collections/range-set.ts b/src/structure/collections/range-set.ts
index 93a95a1611bcd970254bf098e86ebcff7fb307eb..cd243cc378b6a39bd4e418f6470afb607ae6d5b7 100644
--- a/src/structure/collections/range-set.ts
+++ b/src/structure/collections/range-set.ts
@@ -10,6 +10,7 @@ interface RangeSet {
     readonly size: number,
     has(x: number): boolean,
     indexOf(x: number): number,
+    elementAt(i: number): number,
     elements(): Iterator<number>
 }
 
@@ -51,6 +52,7 @@ namespace RangeSet {
             for (let i = 0; i < this.size; i++) ret[i] = i + this.min;
             return ret;
         }
+        elementAt(i: number) { return this.min + i; }
         elements() { return Iterator.Range(this.min, this.max); }
 
         constructor(public min: number, public max: number) {
@@ -65,6 +67,7 @@ namespace RangeSet {
         has(x: number) { return x >= this.min && x <= this.max && binarySearch(this.values, x) >= 0; }
         indexOf(x: number) { return x >= this.min && x <= this.max ? binarySearch(this.values, x) : -1; }
         toArray() { return this.values; }
+        elementAt(i: number) { return this.values[i]; }
         elements() { return Iterator.Array(this.values); }
 
         constructor(public values: ArrayLike<number>) {
diff --git a/src/structure/collections/sort.ts b/src/structure/collections/sort.ts
index 0fa91e3470e45585f5bffde8b4a60b4dad37c00b..92ac607acaee0cad34e50d3c1f7f3c142e45397e 100644
--- a/src/structure/collections/sort.ts
+++ b/src/structure/collections/sort.ts
@@ -7,12 +7,7 @@
 export type Comparer<T = any> = (data: T, i: number, j: number) => number
 export type Swapper<T = any> = (data: T, i: number, j: number) => void
 
-type Ctx = {
-    cmp: Comparer,
-    swap: Swapper,
-    parts: number[],
-    data: any
-}
+type Ctx = { cmp: Comparer, swap: Swapper, parts: number[], data: any }
 
 export function arrayLess(arr: ArrayLike<number>, i: number, j: number) {
     return arr[i] - arr[j];
@@ -30,7 +25,7 @@ function medianPivotIndex(data: any, cmp: Comparer, l: number, r: number) {
     else return cmp(data, r, m) > 0 ? cmp(data, m, l) > 0 ? m : l : r;
 }
 
-function partitionGeneric(ctx: Ctx, l: number, r: number) {
+function partition(ctx: Ctx, l: number, r: number) {
     const { cmp, swap, data, parts } = ctx;
     let equals = l + 1, tail = r;
 
@@ -57,33 +52,6 @@ function partitionGeneric(ctx: Ctx, l: number, r: number) {
     parts[1] = tail;
 }
 
-function partitionArrayAsc(data: number[], parts: number[], l: number, r: number) {
-    let equals = l + 1, tail = r;
-
-    // move the median to the 1st spot
-    arraySwap(data, l, medianPivotIndex(data, arrayLess, l, r));
-    const pivot = data[l];
-
-    while (data[tail] > pivot) { --tail; }
-    for (let i = l + 1; i <= tail; i++) {
-        const v = data[i];
-        if (v > pivot) {
-            arraySwap(data, i, tail);
-            --tail;
-            while (data[tail] > pivot) { --tail; }
-            i--;
-        } else if (v === pivot) {
-            arraySwap(data, i, equals);
-            ++equals;
-        }
-    }
-
-    // move all medians to the correct spots
-    for (let i = l; i < equals; i++) { arraySwap(data, i, l + tail - i); }
-    parts[0] = tail - equals + l + 1;
-    parts[1] = tail;
-}
-
 function insertionSort({ data, cmp, swap }: Ctx, start: number, end: number) {
     for (let i = start + 1; i <= end; i++) {
         let j = i - 1;
@@ -102,7 +70,7 @@ function quickSort(ctx: Ctx, low: number, high: number) {
             return;
         }
 
-        partitionGeneric(ctx, low, high);
+        partition(ctx, low, high);
         const li = parts[0], ri = parts[1];
 
         if (li - low < high - ri) {
@@ -115,46 +83,17 @@ function quickSort(ctx: Ctx, low: number, high: number) {
     }
 }
 
-function insertionSortArrayAsc(data: number[], start: number, end: number) {
-    for (let i = start + 1; i <= end; i++) {
-        const key = data[i];
-        let j = i - 1;
-        while (j >= start && data[j] > key) {
-            data[j + 1] = data[j];
-            j = j - 1;
-        }
-        data[j + 1] = key;
-    }
-}
-
-function quickSortArrayAsc(data: number[], parts: number[], low: number, high: number) {
-    while (low < high) {
-        if (high - low < 16) {
-            insertionSortArrayAsc(data, low, high);
-            return;
-        }
-
-        partitionArrayAsc(data, parts, low, high);
-        const li = parts[0], ri = parts[1];
-
-        if (li - low < high - ri) {
-            quickSortArrayAsc(data, parts, low, li - 1);
-            low = ri + 1;
-        } else {
-            quickSortArrayAsc(data, parts, ri + 1, high);
-            high = li - 1;
-        }
-    }
+export function sortArray(data: ArrayLike<number>, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
+    return sortArrayRange(data, 0, data.length, cmp);
 }
 
-export function sortArray(data: ArrayLike<number>, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
-    if (cmp === arrayLess) quickSortArrayAsc(data as any, [0, 0], 0, data.length - 1);
-    else quickSort({ data, cmp, swap: arraySwap, parts: [0, 0] }, 0, data.length - 1);
+export function sortArrayRange(data: ArrayLike<number>, start: number, end: number, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
+    quickSort({ data, cmp, swap: arraySwap, parts: [0, 0] }, start, end - 1);
     return data;
 }
 
-export function sort<T>(data: T, count: number, cmp: Comparer<T>, swap: Swapper<T>): T {
+export function sort<T>(data: T, start: number, end: number, cmp: Comparer<T>, swap: Swapper<T>): T {
     const ctx: Ctx = { data, cmp, swap, parts: [0, 0] };
-    quickSort(ctx, 0, count - 1);
+    quickSort(ctx, start, end - 1);
     return data;
 }
\ No newline at end of file
diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts
index d3b4489d874b7a52854575aece46879a003c6372..7506936f393d798f3f691265126004e69b78abb5 100644
--- a/src/structure/spec/collections.spec.ts
+++ b/src/structure/spec/collections.spec.ts
@@ -82,10 +82,10 @@ describe('qsort-array generic', () => {
             // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
             if (randomize) {
                 for (let i = 0; i < 10; i++) {
-                    expect(Sort.sort(shuffleArray(data), data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
+                    expect(Sort.sort(shuffleArray(data), 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
                 }
             } else {
-                expect(Sort.sort([...data], data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
+                expect(Sort.sort([...data], 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
             }
         });
     }
@@ -108,10 +108,10 @@ describe('qsort-dual array', () => {
             // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
             if (randomize) {
                 for (let i = 0; i < 10; i++) {
-                    expect(Sort.sort(shuffle(src, len, clone, swap), len, cmp, swap)).toEqual(data);
+                    expect(Sort.sort(shuffle(src, len, clone, swap), 0, len, cmp, swap)).toEqual(data);
                 }
             } else {
-                expect(Sort.sort(clone(src), len, cmp, swap)).toEqual(data);
+                expect(Sort.sort(clone(src), 0, len, cmp, swap)).toEqual(data);
             }
         });
     }