JavaScript developers often need to process two or more sequences together.

For example, you might have a list of product IDs and a list of prices:

const productIds = [101, 102, 103];
const prices = [499, 799, 1299];

A common requirement is to combine the values at the same position:

101 → 499
102 → 799
103 → 1299

Before Iterator.zip(), developers commonly used array indexes, map(), manual loops, or utility functions to solve this problem.

Chrome 153 adds support for the Joint Iteration proposal, including Iterator.zip() and Iterator.zipKeyed(). Iterator.zip() combines multiple iterables and produces values from matching positions. By default, it stops when the shortest input is exhausted.

This article explains how Iterator.zip() works, where it is useful, how its different modes behave, and what to consider before using it in production code.

What Is Iterator.zip()?

Iterator.zip() takes multiple iterable objects and combines their values position by position.

A simple example is:

const names = ["John", "Sarah", "Mike"];
const scores = [85, 92, 78];

const result = Iterator.zip(names, scores);

console.log([...result]);

The result is:

[
  ["John", 85],
  ["Sarah", 92],
  ["Mike", 78]
]

The important point is that Iterator.zip() returns an iterator, not a normal array.

That means the values can be consumed lazily.

For example:

const names = ["John", "Sarah", "Mike"];
const scores = [85, 92, 78];

const zipped = Iterator.zip(names, scores);

for (const [name, score] of zipped) {
    console.log(`${name}: ${score}`);
}

Output:

John: 85
Sarah: 92
Mike: 78

If you actually need an array, you can materialize the iterator:

const rows = [...Iterator.zip(names, scores)];

console.log(rows);

Why Do We Need Iterator.zip()?

JavaScript already provides several ways to combine arrays.

For example:

const names = ["John", "Sarah", "Mike"];
const scores = [85, 92, 78];

const result = names.map((name, index) => {
    return [name, scores[index]];
});

This works.

But it has a few limitations.

You need to manage the index manually, and the code becomes more complicated when the inputs are generators or other iterables rather than arrays.

Consider two generators:

function* generateIds() {
    yield 101;
    yield 102;
    yield 103;
}

function* generatePrices() {
    yield 499;
    yield 799;
    yield 1299;
}

With Iterator.zip():

const result = Iterator.zip(
    generateIds(),
    generatePrices()
);

for (const [id, price] of result) {
    console.log(id, price);
}

There is no array indexing involved.

The API works directly with iterables.

How the Default Mode Works

The default mode is "shortest".

That means iteration stops as soon as the shortest input iterable is exhausted. Chrome's documentation describes "shortest" as the default behavior.

Consider:

const users = ["Alice", "Bob", "Charlie"];
const roles = ["Admin", "Editor"];

const result = Iterator.zip(users, roles);

console.log([...result]);

The result is:

[
  ["Alice", "Admin"],
  ["Bob", "Editor"]
]

Charlie does not appear because the roles iterable ended first.

This behavior is useful when the two sequences represent paired data and incomplete pairs should simply be ignored.

Using the Longest Mode

Sometimes you do not want to stop when the first iterable ends.

You may want to continue until every iterable has been consumed.

For that situation, use "longest".

const users = ["Alice", "Bob", "Charlie"];
const roles = ["Admin", "Editor"];

const result = Iterator.zip(users, roles, {
    mode: "longest"
});

console.log([...result]);

The output contains an additional entry for Charlie.

Because the second iterable has no corresponding value, the missing value needs to be represented using padding.

For example:

const users = ["Alice", "Bob", "Charlie"];
const roles = ["Admin", "Editor"];

const result = Iterator.zip(users, roles, {
    mode: "longest",
    padding: [null]
});

console.log([...result]);

Conceptually, the result becomes:

[
  ["Alice", "Admin"],
  ["Bob", "Editor"],
  ["Charlie", null]
]

This is useful when missing values are meaningful and you want to retain every item from the longest sequence.

Using Strict Mode

Sometimes mismatched lengths indicate a data error.

For example, imagine a data-import process where every product must have a corresponding price.

You do not want this:

Products: 100
Prices:    98

to silently produce partial results.

Use "strict":

const products = ["Laptop", "Mouse", "Keyboard"];
const prices = [75000, 1500];

const result = Iterator.zip(products, prices, {
    mode: "strict"
});

If the input iterables do not have equal lengths, strict mode throws a TypeError.

This can be useful when unequal lengths represent invalid application data rather than an expected situation.

Comparing the Three Modes

Mode

Behavior

Useful When

shortest

Stops when the first iterable ends

Partial pairs are acceptable

longest

Continues until all iterables end

Missing values need to be retained

strict

Throws if lengths differ

Input lengths must match

The default is:

mode: "shortest"

So you do not need to specify it explicitly unless making the behavior clearer for readers.

Working With More Than Two Iterables

Iterator.zip() is not limited to two inputs.

You can combine three or more iterables:

const ids = [101, 102, 103];
const names = ["Laptop", "Mouse", "Keyboard"];
const prices = [75000, 1500, 3000];

const products = Iterator.zip(
    ids,
    names,
    prices
);

for (const [id, name, price] of products) {
    console.log({
        id,
        name,
        price
    });
}

The result is effectively:

101, Laptop,   75000
102, Mouse,     1500
103, Keyboard,  3000

This can make data transformation code easier to read than nested indexing.

Iterator.zip() With Generators

One of the more interesting use cases is combining lazy data sources.

Consider:

function* generateOrderIds() {
    for (let i = 1001; i <= 1005; i++) {
        yield i;
    }
}

function* generateStatuses() {
    yield "Pending";
    yield "Completed";
    yield "Pending";
    yield "Cancelled";
    yield "Completed";
}

You can combine them directly:

const orders = Iterator.zip(
    generateOrderIds(),
    generateStatuses()
);

for (const [orderId, status] of orders) {
    console.log(`Order ${orderId}: ${status}`);
}

This avoids creating intermediate arrays.

That matters when the underlying data is generated incrementally or when processing large sequences.

Iterator.zip() Is Lazy

Consider:

function* numbers() {
    console.log("Generating 1");
    yield 1;

    console.log("Generating 2");
    yield 2;

    console.log("Generating 3");
    yield 3;
}

const result = Iterator.zip(
    numbers(),
    ["A", "B", "C"]
);

At this point, the iterator has not necessarily produced all the paired values.

When you consume it:

console.log(result.next());

the iterator advances and produces the next pair.

This is different from immediately building an entire result array.

For large or streaming-style processing, lazy iteration can be useful because you can process values as they become available instead of materializing the entire result first.

Converting the Result to an Array

Sometimes you need normal array methods such as filter(), map(), or sort().

In that case, convert the iterator:

const result = [...Iterator.zip(
    [101, 102, 103],
    ["A", "B", "C"]
)];

const filtered = result.filter(
    ([id, name]) => id > 101
);

console.log(filtered);

The important distinction is:

Iterator.zip(...)

returns an iterator.

While:

[...Iterator.zip(...)]

creates an array.

Do not automatically convert every iterator to an array if you do not need the complete result in memory.

A Practical API Example

Suppose an application receives two related sequences:

const productIds = [101, 102, 103, 104];
const quantities = [2, 1, 5, 3];

You can calculate line quantities like this:

const items = Iterator.zip(
    productIds,
    quantities
);

for (const [productId, quantity] of items) {
    console.log({
        productId,
        quantity
    });
}

You can then process each pair:

for (const [productId, quantity] of items) {
    if (quantity <= 0) {
        continue;
    }

    processOrderItem(productId, quantity);
}

The important part is that the iterator is consumed once.

If you need to process the same data again, create a new iterator or materialize the data into an array.

Be Careful With Iterator Consumption

Iterators are stateful.

Consider:

const result = Iterator.zip(
    [1, 2, 3],
    ["A", "B", "C"]
);

console.log([...result]);

console.log([...result]);

The first operation consumes the iterator.

The second operation does not recreate the original sequence.

This is different from a normal array:

const data = [
    [1, "A"],
    [2, "B"],
    [3, "C"]
];

console.log(data);
console.log(data);

For production code, remember this simple rule:

An iterator is a process of consuming values, not a permanent collection of values.

Handling Unequal Data Carefully

Suppose you receive:

const customerIds = [10, 20, 30, 40];
const emails = [
    "[email protected]",
    "[email protected]",
    "[email protected]"
];

Using the default mode:

const result = Iterator.zip(
    customerIds,
    emails
);

the last customer will not have a corresponding email.

That may be acceptable in one application but could be a data-integrity problem in another.

If equal lengths are required:

const result = Iterator.zip(
    customerIds,
    emails,
    {
        mode: "strict"
    }
);

This makes the requirement explicit.

For data validation and import pipelines, strict mode can prevent silent loss of unmatched records.

Iterator.zipKeyed()

Chrome 153 also adds Iterator.zipKeyed().

The difference is that Iterator.zip() produces arrays, while Iterator.zipKeyed() produces keyed objects. Chrome's release documentation lists both APIs as part of Joint Iteration.

For example, instead of:

[
    [101, "Laptop", 75000]
]

you can work with named fields.

Conceptually:

const products = Iterator.zipKeyed({
    id: [101, 102],
    name: ["Laptop", "Mouse"],
    price: [75000, 1500]
});

for (const product of products) {
    console.log(product);
}

The result is easier to understand because each value has a name.

This can be particularly useful when combining several related datasets.

Choosing Between zip() and zipKeyed()

Use Iterator.zip() when positional values are clear:

for (const [id, price] of Iterator.zip(ids, prices)) {
    // ...
}

Use Iterator.zipKeyed() when several values need meaningful field names:

for (const product of Iterator.zipKeyed({
    id: ids,
    name: names,
    price: prices
})) {
    // ...
}

The choice is mostly about how the resulting data should be represented and consumed.

Common Mistakes

Assuming It Returns an Array

This:

const result = Iterator.zip(
    [1, 2],
    ["A", "B"]
);

does not give you a normal array.

If you need one:

const result = [
    ...Iterator.zip(
        [1, 2],
        ["A", "B"]
    )
];

Forgetting About Unequal Lengths

Default behavior is "shortest".

That means extra values from longer inputs are not included.

If that is not what your application expects, choose "longest" or "strict" explicitly.

Reusing a Consumed Iterator

Do not assume an iterator can be traversed repeatedly.

Create a new iterator when necessary.

Converting Everything to Arrays

An iterator can be useful precisely because it does not require immediate materialization.

Use an array when you need random access or repeated traversal.

Use an iterator when sequential consumption is enough.

Ignoring Browser Support

Iterator.zip() is a modern JavaScript feature.

Chrome 153 supports Joint Iteration, but applications targeting multiple browsers should verify the browser support required by their users before replacing older approaches. Chrome 153's stable release date was September 8, 2026.

For applications with strict browser compatibility requirements, feature detection or an appropriate compatibility strategy may still be necessary.

Best Practices

When using Iterator.zip() in real applications:

  1. Use "shortest" when incomplete pairs are acceptable.

  2. Use "strict" when unequal input lengths indicate invalid data.

  3. Use "longest" when missing values must be preserved.

  4. Keep iterator processing lazy when you do not need the complete result.

  5. Convert to an array only when array operations or repeated access are actually required.

  6. Remember that iterators are consumed as they are read.

  7. Use Iterator.zipKeyed() when named fields make the resulting data easier to understand.

  8. Test browser compatibility before using the API in widely distributed web applications.

  9. Avoid using Iterator.zip() simply because it is new; use it where it makes the code clearer.

  10. For critical data-processing code, explicitly document what should happen when input sequences have different lengths.

Iterator.zip() vs Traditional Array Code

Approach

Works With Iterables

Lazy

Handles Multiple Inputs

Readability

map() + index

Mostly arrays

No

Yes

Good for simple arrays

Manual for loop

Yes

Depends

Yes

Depends on implementation

Iterator.zip()

Yes

Yes

Yes

Clear for paired iteration

Iterator.zipKeyed()

Yes

Yes

Yes

Clear for named fields

The main advantage of Iterator.zip() is not that every existing loop becomes faster.

Its real value is providing a standard JavaScript abstraction for synchronizing multiple iterators.

When Should You Use Iterator.zip()?

Iterator.zip() is a good fit when:

  • Multiple sequences must be processed together.

  • The data naturally belongs together by position.

  • You are already working with iterators or generators.

  • You want lazy processing.

  • You need explicit handling of unequal sequence lengths.

  • Manual index management is making the code harder to read.

It may not be necessary for a tiny array transformation where a simple map() is already perfectly clear.

For example:

const names = ["Alice", "Bob"];

const result = names.map((name, index) => ({
    name,
    score: scores[index]
}));

There is no need to rewrite straightforward code just to use a newer API.

Good production code should optimize for clarity, correctness, and maintainability rather than simply using the newest language feature.

Summary

Iterator.zip() gives JavaScript developers a standard way to process multiple iterables together.

Chrome 153 introduces support for Joint Iteration, including Iterator.zip() and Iterator.zipKeyed(). Iterator.zip() produces arrays containing values from matching positions, while Iterator.zipKeyed() produces keyed results.

The default "shortest" mode stops when the first iterable finishes. "longest" allows processing to continue with padding for missing values, while "strict" reports unequal input lengths as an error.

The biggest practical benefit is not simply shorter syntax. Iterator.zip() works naturally with iterators and generators and allows data to be processed lazily.

For production applications, choose the mode based on your data requirements, remember that iterators are consumed as they are read, and verify browser compatibility before adopting the API across a broad user base.