Double-Ended Iterator and Destructuring

This proposal has not yet been presented to TC39 plenary meetings.


Python and Ruby support (first, *rest, last) = [1, 2, 3, 4], CoffeeScript supports [first, rest..., last] = [1, 2, 3, 4], and Rust supports [first, rest @ .., last] = [1, 2, 3, 4], all resulting in first be 1, last be 4, and rest be [2, 3]. But surprisingly [first,, last] = [1, 2, 3, 4] doesn't work in JavaScript.

And in some cases we really want to get the items from the end, for example getting matchIndex from String.prototype.replace when using a function:

string.replace(pattern, (fullMatch, ...submatches, matchIndex, fullString) => {
  // `matchIndex` is always the second to last param (the full string is the last param).
  // There may be many submatch params, depending on the pattern.

A simple solution is making let [first,, last] = iterable to work as

let [first,] = iterable
let last = rest.pop()

The concern is it requires saving all items in a rest array, although you may only need last. A possible mitigation is supporting [..., last] = iterable which saves the memory of rest, but you still need to consume the entire iterator. In the cases where iterable is a large array or something like Number.range(1, 100000), it's very inefficient. And in case like let [first, ..., last] = repeat(10) (suppose repeat is a generator returns infinite sequence of a same value), theoretically both first and last could be 10.

Possible solution

Instead of the simple solution, we could introduce the double-ended iterator (like Rust std::iter::DoubleEndedIterator). A double-ended iterator could be consumed from both ends.

let a = [1, 2, 3, 4, 5, 6]
let deiter = a.values() // suppose values() would be upgraded to return a double-ended iterator // {value: 1} // {value: 2}'back') // {value: 6} // {value: 3}'back') // {value: 5}'back') // {value: 4}'back') // {done: true} // {done: true}

With double-ended iterators, let [a, b, ..., c, d] = iterable would roughly work as

let iter = iterable[Symbol.deIterator]()
let a =
let b =
let d ='back').value
let c ='back').value


To implement double-ended iterator in userland, we could use a generator with the function.sent feature.

Array.prototype.values = function *values() {
  // only for demo, for real upgrading of Array.prototype.values 
  // to double-ended iterator, it need to deal with the edge cases 
  // of mutating the array while iterating
  for (let start = 0, end = this.length; start < end;) {
    if (function.sent === 'back') yield this[--end]
    else yield this[start++]

Iterator helpers and reverse iterator

Double-ended iterator could have some extra iterator helpers like reversed and reduceRight.

DoubleEndedIterator.prototype.reversed = function *reversed() {
  for (;;) {
    let result
    if (function.sent === 'back') result =
    else result ='back')
    if (result.done) return result.value
    else yield result.value

We could also easily have a default implementation for reverse iterator if the object already supports double-ended iterator.

Object.assign(X.prototype, {
  *[Symbol.reverseIterator]() {
    const iter = this[Symbol.deIterator]()
    for (;;) {
      let result
      if (function.sent === 'back') result =
      else result ='back')
      if (result.done) return result.value
      else yield result.value

Prior art

Previous discussions

Old discussions