remove(index) {
    
        // special case: no nodes in the list
        if (this[tail] === null) {
            throw new RangeError(`Index ${index} does not exist in the list.`);
        }

        // special case: `index` is an invalid value
        if (index < 0) {
            throw new RangeError(`Index ${index} does not exist in the list.`);
        }

        /*
         * The `current` variable is used to iterate over the list nodes.
         * It starts out pointing to the head and is overwritten inside
         * of the loop below.
         */
        let current = this[tail].next;

        // special case: removing the first node
        if (index === 0) {

            // if there's only one node, null out `this[tail]`
            if (current.next === this[tail]) {
                this[tail] = null;
            } else {
                /*
                 * The tail doesn't change when there is more than one item
                 * in the list. Just skip over `current` by setting
                 * `this[tail].next` to `current.next`.
                 */
                this[tail].next = current.next;
            }

            // return the data at the previous head of the list
            return current.data;
        }

        /*
         * The `previous` variable keeps track of the node just before
         * `current` in the loop below. This is necessary because removing
         * an node means updating the previous node's `next` pointer.
         */
        let previous = null;

        /*
         * The `i` variable is used to track how deep into the list we've
         * gone. This is important because it's the only way to know when
         * we've hit the `index` to remove.
         */
        let i = 0;

        /*
         * Traverse the list, keeping track of the previous position so
         * that we can remove the node once it's found. The loop is exited
         * when either the start of the list is encountered or `i` is no
         * longer less than `index` (meaning we have found the node to remove).
         */
        do {

            // save the value of current
            previous = current;

            // traverse to the next node
            current = current.next;

            // increment the count
            i++;

        } while ((current !== this[tail].next) && (i < index));

        /*
         * If `current` isn't `this[tail].next`, then that means we've found the node
         * to remove.
         */
        if (current !== this[tail].next) {

            // skip over the node to remove
            previous.next = current.next;

            // return the value that was just removed from the list
            return current.data;
        }

        /*
         * If we've made it this far, it means `index` is a value that
         * doesn't exist in the list, so throw an error.
         */
        throw new RangeError(`Index ${index} does not exist in the list.`);
    }
Removes the node from the given location in the list.
@param {int} index The zero-based index of the node to remove.
@returns {*} The data in the given position in the list.
@throws {RangeError} If index is out of range.