For AI agents: the complete documentation index is available at /tc39-atlas/en/llms.txt, the full documentation bundle is available at /tc39-atlas/en/llms-full.txt, and this page is available as Markdown at /tc39-atlas/en/proposals/year/2027/proposal-joint-iteration.md.
  • English
  • Joint Iteration S4

    Proposal details
    Proposal overview

    This proposal addresses the need to synchronize the advancement of multiple iterators by adding Iterator.zip and Iterator.zipKeyed methods, which combine corresponding values from multiple iterables into arrays or objects. It supports modes like "shortest", "longest", and "strict" via options, with padding for shorter inputs in "longest" mode.

    Note

    The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.

    Joint Iteration

    A TC39 proposal to synchronise the advancement of multiple iterators, often called zip.

    Stage: 4. See tc39/ecma262#3802 for the integration PR.

    Demo: https://tc39.es/proposal-joint-iteration/demo/

    Specification: https://tc39.es/proposal-joint-iteration/

    motivation

    Often you have 2 or more iterators that are positionally aligned (the first value yielded by the first iterator corresponds to the first value yielded by the other iterators, and so on), and you would like to operate on the corresponding values together. A common solution to this is zip, which produces an iterator of the combined values. zipWith allows combination of values in some way other than tupling. Some languages express zipWith as a variadic map.

    presentations to committee

    proposal

    This proposal adds two methods: Iterator.zip and Iterator.zipKeyed. zip takes an iterable of iterables and produces an iterable of arrays where position corresponds to position in the passed iterable. zipKeyed takes an object whose values are iterables and produces an iterable of objects where keys correspond to keys in the passed object.

    Iterator.zip([
      [0, 1, 2],
      [3, 4, 5],
    ]).toArray()
    
    /*
    Produces:
    [
      [0, 3],
      [1, 4],
      [2, 5],
    ]
    */
    
    Iterator.zipKeyed({
      a: [0, 1, 2],
      b: [3, 4, 5, 6],
      c: [7, 8, 9],
    }).toArray()
    
    /*
    Produces:
    [
      { a: 0, b: 3, c: 7 },
      { a: 1, b: 4, c: 8 },
      { a: 2, b: 5, c: 9 },
    ]
    */

    Both methods take an options bag as a second argument which allows specifying a mode of "shortest" (the default), "longest", or "strict".

    For "longest", the options bag can also define padding to be used for shorter inputs by providing an iterable or object (for zip and zipKeyed respectively):

    Iterator.zipKeyed({
      a: [0, 1, 2],
      b: [3, 4, 5, 6],
      c: [7, 8, 9],
    }, {
      mode: 'longest',
      padding: { c: 10 },
    }).toArray()
    
    /*
    Produces:
    [
      { a: 0,         b: 3, c: 7  },
      { a: 1,         b: 4, c: 8  },
      { a: 2,         b: 5, c: 9  },
      { a: undefined, b: 6, c: 10 },
    ];
    */

    considered design space

    1. do we support just 2 iterators or something else? 2+? 1+? 0+? Decision: 0+.
      1. if 0 is allowed, is that considered never-ending or already completed? Decision: Already completed.
      2. should the iterators be passed positionally (combining to arrays) or named (combining to objects)? Decision: Both, as seperate APIs.
      3. do we take the iterators as varargs or as an iterable/object? Decision: Iterable/object.
        1. varargs eliminates design space for potentially passing an options bag or a combining function
    2. do we support iterators and iterables like Iterator.from and flatMap? Decision: Just iterables.
      1. if so, which string handling do we match? Iterator.from iterates strings; flatMap rejects strings
    3. if an iterator completes, do we still advance the other iterators? Decision: Depends on the mode.
      1. do we return them? Decision: Yes, except with mode: longest.
    4. if an iterator fails to advance, do we still advance the other iterators? Decision: Yes.
      1. if so, do we return an AggregateError? Only if 2+ failures? Decision: No, first error swallows any subsequent.
    5. do we want -With variants for combining the values in other ways than tupling? Decision: Not in this proposal.
      1. what about always requiring the combiner?
    6. do we want a zipLongest/zipFilled/zipAll? Decision: Yes, as an option.
      1. if so, do we want a filler element or to call a function to provide the filler? Decision: Per-iterable filler elements.
      2. what about a variant that matches the length of a privileged iterator (this)? Decision: No.
    7. do we want a zipEqual/zipStrict that throws if they do not complete after the same number of yields? Decision: Yes, as an option.

    prior art

    other languages

    languageshortestlongestprivilegedstrict-With3+ sources1 source0 sources
    C++std::ranges::views::zip::zip_transformyesyes
    Clojurevariadic mapyesyesyes
    ElmList.map2yesyes
    HaskellzipzipWithyes
    OCamlzipcombinemap2yes
    Pythonzipitertools.zip_longestzip(..., strict=True)yesyesyes, empty
    RubyEnumerable#zipzipyesyes
    RustIterator::zip
    Scalazipit.zipAll(jt, x, y)
    Swiftzip

    JS libraries

    libraryshortestlongestprivilegedstrict-With3+ sources1 source0 sources
    @iterable-iterator/zipzipzipLongestyesyes
    @softwareventures/iteratorzipOnce
    extra-iterablezipzipzipyesyesyes, empty
    immutable.jsSeq::zipzipWithyesyes
    iter-opszipyesyesyes, empty
    iter-toolszipzipAllyesyesyes, empty
    iterablefuzipzipAllyesyesyes, empty
    iterarezip
    itertools-tszipzipFilled, zipLongestzipEqualyesyesyes, empty
    ixjszipyesyesyes, empty
    lodashzipzipWithyesyesyes, empty
    ramdazipzipWith
    sequencyzip
    wuzipzipLongestzipWithyesyes
    zipiteratorszipiterators