Skip to content

zipWith - pair corresponding values ​

The zipWith operator will group together corresponding ordered values issued by the original Observable and the other Observable(s) specified. It waits for values to arrive from all Observables, one at a time, and creates pairs when they are ready. This is the Pipeable Operator version of Creation Function's zip.

🔰 Basic Syntax and Usage ​

ts
import { of, interval } from 'rxjs';
import { zipWith, map, take } from 'rxjs';

const letters$ = of('A', 'B', 'C', 'D');
const numbers$ = interval(1000).pipe(
  map(val => val * 10),
  take(3)
);

letters$
  .pipe(zipWith(numbers$))
  .subscribe(([letter, number]) => {
    console.log(`${letter} - ${number}`);
  });

// Output:
// A - 0
// B - 10
// C - 20
// (Dis not output because there is no corresponding value)
  • A pair is output when one value from each Observable is complete.
  • When one Observable completes, the remaining values are discarded.

🌐 RxJS Official Documentation - zipWith

💡 Typical utilization pattern ​

  • Combine parallel processing results in guaranteed order: pair multiple API call results
  • Mapping IDs to data: Combine user IDs with corresponding profile data
  • Stream synchronization: Synchronize related data issued at different times

🧠 Practical code example (with UI) ​

This is an example of pairing a list of user IDs and corresponding user names in sequence.

ts
import { from, of } from 'rxjs';
import { zipWith, delay, concatMap } from 'rxjs';

// Output Area Creation
const output = document.createElement('div');
output.innerHTML = '<h3>zipWith Practical examples of:</h3>';
document.body.appendChild(output);

// UserIDStream (immediately issued)
const userIds$ = from([101, 102, 103, 104]);

// User name stream (issued every1(issued every second)
const userNames$ = from(['Alice', 'Bob', 'Carol']).pipe(
  concatMap(name => of(name).pipe(delay(1000)))
);

// zipand display
userIds$
  .pipe(zipWith(userNames$))
  .subscribe(([id, name]) => {
    const item = document.createElement('div');
    item.textContent = `👤 UserID ${id}: ${name}`;
    output.appendChild(item);
  });

// Output:
// 👤 UserID 101: Alice
// 👤 UserID 102: Bob
// 👤 UserID 103: Carol
// (104is not displayed because there is no corresponding name)
  • IDs and names are paired in one-to-one correspondence.
  • When one is complete, the remaining values are discarded.

🔄 Difference from Creation Function zip. ​

Basic differences ​

zip (Creation Function)zipWith (Pipeable Operator)
Where usedUsed as a standalone functionUsed in .pipe() chain
How to writezip(obs1$, obs2$, obs3$)obs1$.pipe(zipWith(obs2$, obs3$))
First stream.Treat all as equalTreat as main stream
Advantages.Simple and readableEasy to combine with other operators

Specific examples of usage ​

For simple pairing, Creation Function is recommended.

ts
import { zip, of } from 'rxjs';

const questions$ = of('The name is?', 'The age is?', 'Address is?');
const answers$ = of('Taro', '30', 'Tokyo');
const scores$ = of(10, 20, 30);

// Simple and easy to read
zip(questions$, answers$, scores$).subscribe(([q, a, s]) => {
  console.log(`Q: ${q}, A: ${a}, Score: ${s}Score`);
});
// Output:
// Q: The name is?, A: Taro, Score: 10Score
// Q: The age is?, A: 30, Score: 20Score
// Q: Address is?, A: Tokyo, Score: 30Score

If you want to add conversion to the main stream, Pipeable Operator is recommended.

ts
import { from, interval } from 'rxjs';
import { zipWith, map, take, filter } from 'rxjs';

// Task List
const tasks$ = from([
  { id: 1, name: 'Reporting', priority: 'high' },
  { id: 2, name: 'Email Reply', priority: 'low' },
  { id: 3, name: 'Meeting preparation', priority: 'high' },
  { id: 4, name: 'Organize documents', priority: 'medium' }
]);

// List of assignees (assigned every1(assigned every second)
const assignees$ = from(['Alice', 'Bob', 'Carol', 'Dave']).pipe(
  zipWith(interval(1000).pipe(take(4))),
  map(([name]) => name)
);

// ✅ Pipeable OperatorEdition - Complete in one pipeline
tasks$
  .pipe(
    filter(task => task.priority === 'high'),  // High priority only
    map(task => task.name),                     // Extract task names
    zipWith(assignees$),                        // Assign person in charge
    map(([taskName, assignee]) => ({
      task: taskName,
      assignee,
      assignedAt: new Date().toLocaleTimeString()
    }))
  )
  .subscribe(assignment => {
    console.log(`[${assignment.assignedAt}] ${assignment.task} → Assigned to: ${assignment.assignee}`);
  });
// Output:
// [Time] Reporting → Assigned to: Alice
// [Time] Meeting preparation → Assigned to: Bob

// ❌ Creation FunctionEdition - Redundant
import { zip } from 'rxjs';
zip(
  tasks$.pipe(
    filter(task => task.priority === 'high'),
    map(task => task.name)
  ),
  assignees$
).pipe(
  map(([taskName, assignee]) => ({
    task: taskName,
    assignee,
    assignedAt: new Date().toLocaleTimeString()
  }))
).subscribe(assignment => {
  console.log(`[${assignment.assignedAt}] ${assignment.task} → Assigned to: ${assignment.assignee}`);
});

Synchronization of order-critical data

ts
import { from } from 'rxjs';
import { zipWith, map, concatMap, delay } from 'rxjs';
import { of } from 'rxjs';

// UICreate
const output = document.createElement('div');
output.innerHTML = '<h3>Quiz game</h3>';
document.body.appendChild(output);

const questionArea = document.createElement('div');
questionArea.style.marginTop = '10px';
output.appendChild(questionArea);

// List of questions (prepared immediately)
const questions$ = from([
  'The capital of Japan is?',
  '1+1is?',
  'What is the number of the Earth's planets??'
]);

// Answer list (simulating user input:2(every second)
const answers$ = from(['Tokyo', '2', '3']).pipe(
  concatMap((answer, index) =>
    of(answer).pipe(delay((index + 1) * 2000))
  )
);

// Correct answer list
const correctAnswers$ = from(['Tokyo', '2', '3']);

// ✅ Pipeable OperatorEdition - Process questions as mainstream
questions$
  .pipe(
    zipWith(answers$, correctAnswers$),
    map(([question, answer, correct], index) => ({
      no: index + 1,
      question,
      answer,
      correct,
      isCorrect: answer === correct
    }))
  )
  .subscribe(result => {
    const div = document.createElement('div');
    div.style.marginTop = '10px';
    div.style.padding = '10px';
    div.style.border = '1px solid #ccc';
    div.style.backgroundColor = result.isCorrect ? '#e8f5e9' : '#ffebee';
    div.innerHTML = `
      <strong>Question${result.no}:</strong> ${result.question}<br>
      <strong>Answers:</strong> ${result.answer}<br>
      <strong>Result:</strong> ${result.isCorrect ? '✅ Correct Answer!' : '❌ Incorrect'}
    `;
    questionArea.appendChild(div);
  });

Summary ​

  • zip: best if you just want to map multiple streams in order
  • zipWith: best if you want to merge the main stream with other streams in guaranteed order while transforming and processing the main stream

⚠️ Notes. ​

For different lengths. ​

When the shorter Observable completes, the remaining values of the longer one are discarded.

ts
import { of } from 'rxjs';
import { zipWith } from 'rxjs';

const short$ = of(1, 2, 3);
const long$ = of('A', 'B', 'C', 'D', 'E');

short$.pipe(zipWith(long$)).subscribe(console.log);
// Output: [1, 'A'], [2, 'B'], [3, 'C']
// 'D'and'E'are discarded

Memory Accumulation ​

If one Observable continues to issue values, the values will accumulate in memory until the other catches up.

ts
import { interval} from 'rxjs';
import { zipWith, take } from 'rxjs';

// Fast Stream (100msper stream)
const fast$ = interval(100).pipe(take(10));

// Low-speed stream (1(every second)
const slow$ = interval(1000).pipe(take(3));

fast$.pipe(zipWith(slow$)).subscribe(console.log);
// Output: [0, 0] (1seconds later), [1, 1] (2seconds later), [2, 2] (3seconds later)
// fast$values are stored in memory and wait

Difference from combineLatestWith ​

zipWith pairs the values in the corresponding order, while combineLatestWith combines the latest values.

ts
import { interval } from 'rxjs';
import { zipWith, combineLatestWith, take } from 'rxjs';

const source1$ = interval(1000).pipe(take(3));
const source2$ = interval(1500).pipe(take(2));

// zipWith: Paired in corresponding order
source1$.pipe(zipWith(source2$)).subscribe(console.log);
// Output: [0, 0], [1, 1]

// combineLatestWith: Combine the latest values
source1$.pipe(combineLatestWith(source2$)).subscribe(console.log);
// Output: [0, 0], [1, 0], [2, 0], [2, 1]

Released under the CC-BY-4.0 license.