Conditional and Boolean Logic Operators

1. iif for Conditional Observable Selection

Feature Syntax Description Use Case
iif iif(condition, trueResult$, falseResult$?) Subscribes to one observable based on condition evaluated at subscription time Conditional stream selection
Lazy evaluation Condition checked when subscribed, not when created Deferred decision making Runtime branching
Default If falseResult$ omitted, emits EMPTY Optional false branch Conditional execution

Example: iif for conditional observable selection

import { iif, of, EMPTY, interval } from 'rxjs';
import { mergeMap, map } from 'rxjs/operators';

// Basic iif - simple conditional
const condition = Math.random() > 0.5;

iif(
  () => condition,
  of('Condition is true'),
  of('Condition is false')
).subscribe(console.log);

// Without false branch - emits EMPTY
iif(
  () => false,
  of('True branch')
  // No false branch - completes immediately
).subscribe({
  next: val => console.log(val),
  complete: () => console.log('Completed (empty)')
});

// Practical: Conditional data source
function getData(useCache: boolean) {
  return iif(
    () => useCache,
    of(cachedData),
    ajax.getJSON('/api/data')
  );
}

getData(hasCachedData).subscribe(data => displayData(data));

// Practical: Authentication check
const isAuthenticated$ = iif(
  () => hasAuthToken(),
  ajax.getJSON('/api/user/profile'),
  of(null)
);

isAuthenticated$.subscribe(profile => {
  if (profile) {
    displayProfile(profile);
  } else {
    redirectToLogin();
  }
});

// Practical: Environment-based configuration
const config$ = iif(
  () => process.env.NODE_ENV === 'production',
  ajax.getJSON('/api/config/production'),
  of(developmentConfig)
);

config$.subscribe(config => initializeApp(config));

// Practical: Feature flag
function getFeatureData(featureEnabled: boolean) {
  return iif(
    () => featureEnabled,
    ajax.getJSON('/api/feature/data'),
    of({ message: 'Feature disabled' })
  );
}

// Practical: Conditional retry
function fetchWithRetry(url: string, shouldRetry: boolean) {
  return ajax.getJSON(url).pipe(
    catchError(err => iif(
      () => shouldRetry,
      ajax.getJSON(url).pipe(delay(1000)), // Retry
      throwError(() => err) // Don't retry
    ))
  );
}

// Practical: User preference based loading
const theme$ = iif(
  () => userPreferences.theme === 'dark',
  of('dark-theme.css'),
  of('light-theme.css')
).pipe(
  mergeMap(themePath => loadStylesheet(themePath))
);

// Practical: A/B testing
const variant$ = iif(
  () => Math.random() < 0.5,
  of('variant-A'),
  of('variant-B')
);

variant$.subscribe(variant => {
  trackVariant(variant);
  showVariant(variant);
});

// Practical: Conditional polling
const pollInterval$ = iif(
  () => isHighPriority,
  interval(1000), // Poll every 1s
  interval(5000)  // Poll every 5s
);

pollInterval$.pipe(
  switchMap(() => ajax.getJSON('/api/status'))
).subscribe(status => updateStatus(status));

// Practical: Device-based content loading
const content$ = iif(
  () => isMobileDevice(),
  ajax.getJSON('/api/content/mobile'),
  ajax.getJSON('/api/content/desktop')
);

// Practical: Offline/online handling
const data$ = iif(
  () => navigator.onLine,
  ajax.getJSON('/api/data'),
  of(offlineData)
);

data$.subscribe(data => renderData(data));

// With mergeMap for dynamic conditions
clicks$.pipe(
  mergeMap(() => iif(
    () => currentUser.isPremium,
    getPremiumContent(),
    getFreeContent()
  ))
).subscribe(content => displayContent(content));
Note: iif evaluates condition at subscription time, not creation time. For in-stream conditionals, use filter or other operators.

2. every for Universal Condition Checking

Feature Syntax Description Result
every every(predicate, thisArg?) Returns true if all values satisfy predicate, false otherwise Single boolean emission
Short-circuit Completes with false on first failing value Doesn't wait for all values Early termination
Empty stream Returns true for empty observables Vacuous truth Logical consistency

Example: every for universal condition checking

import { of, from, range } from 'rxjs';
import { every, map } from 'rxjs/operators';

// Basic every - all values satisfy condition
of(2, 4, 6, 8).pipe(
  every(val => val % 2 === 0)
).subscribe(result => console.log('All even?', result)); // true

// Fails on first non-matching value
of(2, 4, 5, 8).pipe(
  every(val => val % 2 === 0)
).subscribe(result => console.log('All even?', result)); // false

// Empty stream returns true
of().pipe(
  every(val => val > 100)
).subscribe(result => console.log('Empty stream:', result)); // true

// Practical: Form validation - all fields valid
const formFields$ = from([
  { name: 'email', value: 'user@example.com', valid: true },
  { name: 'password', value: 'secret123', valid: true },
  { name: 'age', value: '25', valid: true }
]);

formFields$.pipe(
  every(field => field.valid)
).subscribe(allValid => {
  if (allValid) {
    enableSubmitButton();
  } else {
    showValidationErrors();
  }
});

// Practical: Permissions check
const requiredPermissions = ['read', 'write', 'delete'];
const userPermissions$ = from(requiredPermissions);

userPermissions$.pipe(
  every(permission => hasPermission(permission))
).subscribe(hasAllPermissions => {
  if (hasAllPermissions) {
    showAdminPanel();
  } else {
    showAccessDenied();
  }
});

// Practical: Data quality check
ajax.getJSON('/api/records').pipe(
  mergeMap(response => from(response.records)),
  every(record => {
    return record.id && 
           record.name && 
           record.timestamp &&
           typeof record.value === 'number';
  })
).subscribe(dataValid => {
  if (dataValid) {
    console.log('All records valid');
    processRecords();
  } else {
    console.error('Invalid data detected');
    rejectBatch();
  }
});

// Practical: Age verification
const users$ = ajax.getJSON('/api/users');

users$.pipe(
  mergeMap(response => from(response.users)),
  every(user => user.age >= 18)
).subscribe(allAdults => {
  if (allAdults) {
    proceedWithContent();
  } else {
    showAgeRestrictionWarning();
  }
});

// Practical: Configuration validation
const configEntries$ = from(Object.entries(config));

configEntries$.pipe(
  every(([key, value]) => value !== null && value !== undefined)
).subscribe(configComplete => {
  if (configComplete) {
    initializeApp();
  } else {
    showConfigurationError();
  }
});

// Practical: File upload validation
const files$ = from(selectedFiles);

files$.pipe(
  every(file => {
    const validType = ['image/jpeg', 'image/png'].includes(file.type);
    const validSize = file.size < 5 * 1024 * 1024; // 5MB
    return validType && validSize;
  })
).subscribe(allValid => {
  if (allValid) {
    uploadFiles(selectedFiles);
  } else {
    showFileValidationError();
  }
});

// Practical: Network connectivity check
const endpoints$ = from([
  '/api/health',
  '/api/status',
  '/api/ping'
]);

endpoints$.pipe(
  mergeMap(endpoint => ajax.getJSON(endpoint).pipe(
    map(() => true),
    catchError(() => of(false))
  )),
  every(success => success)
).subscribe(allReachable => {
  if (allReachable) {
    setOnlineStatus(true);
  } else {
    setOnlineStatus(false);
    showOfflineWarning();
  }
});

// Practical: Dependencies loaded check
const dependencies$ = from([
  loadScript('jquery.js'),
  loadScript('bootstrap.js'),
  loadScript('app.js')
]);

dependencies$.pipe(
  every(loaded => loaded === true)
).subscribe(allLoaded => {
  if (allLoaded) {
    initializeApplication();
  } else {
    showLoadingError();
  }
});

// Practical: Inventory check
const products$ = from(shoppingCart.items);

products$.pipe(
  mergeMap(item => checkStock(item.productId)),
  every(inStock => inStock)
).subscribe(allAvailable => {
  if (allAvailable) {
    proceedToCheckout();
  } else {
    showOutOfStockMessage();
  }
});
Note: every short-circuits on first false - efficient for large streams. Returns single boolean value then completes.

3. find and findIndex for First Match Operations

Operator Syntax Description Returns
find find(predicate, thisArg?) Emits first value that satisfies predicate, then completes First matching value or undefined
findIndex findIndex(predicate, thisArg?) Emits index of first value that satisfies predicate Index number or -1
Behavior Completes after first match or when source completes Short-circuits on match Early termination

Example: find and findIndex for searching

import { from, range } from 'rxjs';
import { find, findIndex, delay } from 'rxjs/operators';

// Basic find - first match
from([1, 2, 3, 4, 5]).pipe(
  find(val => val > 3)
).subscribe(result => console.log('Found:', result)); // 4

// No match returns undefined
from([1, 2, 3]).pipe(
  find(val => val > 10)
).subscribe(result => console.log('Found:', result)); // undefined

// findIndex - get position
from(['a', 'b', 'c', 'd']).pipe(
  findIndex(val => val === 'c')
).subscribe(index => console.log('Index:', index)); // 2

// No match returns -1
from(['a', 'b', 'c']).pipe(
  findIndex(val => val === 'z')
).subscribe(index => console.log('Index:', index)); // -1

// Practical: Find user by ID
ajax.getJSON('/api/users').pipe(
  mergeMap(response => from(response.users)),
  find(user => user.id === targetUserId)
).subscribe(user => {
  if (user) {
    displayUserProfile(user);
  } else {
    showUserNotFound();
  }
});

// Practical: Find first available slot
const timeSlots$ = from([
  { time: '09:00', available: false },
  { time: '10:00', available: false },
  { time: '11:00', available: true },
  { time: '12:00', available: true }
]);

timeSlots$.pipe(
  find(slot => slot.available)
).subscribe(slot => {
  if (slot) {
    console.log('First available:', slot.time);
    bookAppointment(slot);
  }
});

// Practical: Find error in logs
const logs$ = ajax.getJSON('/api/logs').pipe(
  mergeMap(response => from(response.logs))
);

logs$.pipe(
  find(log => log.level === 'ERROR')
).subscribe(errorLog => {
  if (errorLog) {
    highlightError(errorLog);
    console.error('First error:', errorLog);
  }
});

// Practical: Find product by name
const products$ = from(productCatalog);

products$.pipe(
  find(product => product.name.toLowerCase().includes(searchTerm))
).subscribe(product => {
  if (product) {
    navigateToProduct(product.id);
  } else {
    showNoResultsMessage();
  }
});

// Practical: Find first invalid field
const formFields$ = from([
  { name: 'email', valid: true },
  { name: 'password', valid: false },
  { name: 'age', valid: true }
]);

formFields$.pipe(
  find(field => !field.valid)
).subscribe(invalidField => {
  if (invalidField) {
    focusField(invalidField.name);
    showFieldError(invalidField.name);
  }
});

// findIndex for position-based logic
const items$ = from(['apple', 'banana', 'cherry', 'date']);

items$.pipe(
  findIndex(item => item.startsWith('c'))
).subscribe(index => {
  console.log('Found at position:', index); // 2
  highlightItemAtIndex(index);
});

// Practical: Find overdue task
const tasks$ = ajax.getJSON('/api/tasks').pipe(
  mergeMap(response => from(response.tasks))
);

tasks$.pipe(
  find(task => {
    const dueDate = new Date(task.dueDate);
    return dueDate < new Date() && task.status !== 'completed';
  })
).subscribe(overdueTask => {
  if (overdueTask) {
    showOverdueNotification(overdueTask);
  }
});

// Practical: Find matching route
const routes$ = from([
  { path: '/home', component: 'Home' },
  { path: '/about', component: 'About' },
  { path: '/contact', component: 'Contact' }
]);

routes$.pipe(
  find(route => route.path === currentPath)
).subscribe(route => {
  if (route) {
    loadComponent(route.component);
  } else {
    show404Page();
  }
});

// Practical: Find threshold breach
const sensorReadings$ = interval(1000).pipe(
  map(() => Math.random() * 100),
  take(100)
);

sensorReadings$.pipe(
  find(reading => reading > 95)
).subscribe(highReading => {
  if (highReading !== undefined) {
    console.warn('Threshold breached:', highReading);
    triggerAlert();
  }
});

// Practical: Find first cached item
const cacheKeys$ = from(['key1', 'key2', 'key3', 'key4']);

cacheKeys$.pipe(
  find(key => cache.has(key))
).subscribe(cachedKey => {
  if (cachedKey) {
    const data = cache.get(cachedKey);
    useCachedData(data);
  } else {
    fetchFreshData();
  }
});

// Combined with other operators
ajax.getJSON('/api/items').pipe(
  mergeMap(response => from(response.items)),
  find(item => item.featured === true)
).subscribe(featuredItem => {
  if (featuredItem) {
    displayFeaturedItem(featuredItem);
  }
});

4. isEmpty for Empty Stream Detection

Feature Syntax Description Result
isEmpty isEmpty() Emits true if source completes without emitting, false otherwise Single boolean emission
Timing Waits for source to complete before emitting Must complete to know if empty Deferred evaluation

Example: isEmpty for empty stream detection

import { EMPTY, of, from } from 'rxjs';
import { isEmpty, filter } from 'rxjs/operators';

// Basic isEmpty - empty stream
EMPTY.pipe(
  isEmpty()
).subscribe(result => console.log('Is empty?', result)); // true

// Non-empty stream
of(1, 2, 3).pipe(
  isEmpty()
).subscribe(result => console.log('Is empty?', result)); // false

// Practical: Check if search has results
ajax.getJSON(`/api/search?q=${query}`).pipe(
  mergeMap(response => from(response.results)),
  isEmpty()
).subscribe(noResults => {
  if (noResults) {
    showNoResultsMessage();
  } else {
    showResultsFound();
  }
});

// Practical: Validate shopping cart
const cartItems$ = from(shoppingCart.items);

cartItems$.pipe(
  isEmpty()
).subscribe(cartEmpty => {
  if (cartEmpty) {
    showEmptyCartMessage();
    disableCheckoutButton();
  } else {
    enableCheckoutButton();
  }
});

// Practical: Check for unread messages
ajax.getJSON('/api/messages/unread').pipe(
  mergeMap(response => from(response.messages)),
  isEmpty()
).subscribe(noUnread => {
  if (noUnread) {
    hideBadge();
  } else {
    showNotificationBadge();
  }
});

// Practical: Validate filtered results
const data$ = ajax.getJSON('/api/data');

data$.pipe(
  mergeMap(response => from(response.items)),
  filter(item => item.active && item.inStock),
  isEmpty()
).subscribe(noneAvailable => {
  if (noneAvailable) {
    showOutOfStockMessage();
  } else {
    displayAvailableItems();
  }
});

// Practical: Check pending tasks
ajax.getJSON('/api/tasks/pending').pipe(
  mergeMap(response => from(response.tasks)),
  isEmpty()
).subscribe(noPending => {
  if (noPending) {
    showAllCaughtUpMessage();
    hideTaskList();
  } else {
    showTaskList();
  }
});

// Practical: Validate form errors
const validationErrors$ = validateForm(formData);

validationErrors$.pipe(
  isEmpty()
).subscribe(noErrors => {
  if (noErrors) {
    submitForm();
  } else {
    showValidationSummary();
  }
});

// Practical: Check notification queue
const notifications$ = from(notificationQueue);

notifications$.pipe(
  filter(n => !n.dismissed),
  isEmpty()
).subscribe(allDismissed => {
  if (allDismissed) {
    hideNotificationPanel();
  }
});

// Practical: Detect empty file
const fileContent$ = readFileAsObservable(file);

fileContent$.pipe(
  isEmpty()
).subscribe(fileEmpty => {
  if (fileEmpty) {
    showError('File is empty');
  } else {
    processFile();
  }
});

// Practical: Check cached data
const cachedItems$ = from(Array.from(cache.values()));

cachedItems$.pipe(
  filter(item => !isExpired(item)),
  isEmpty()
).subscribe(cacheEmpty => {
  if (cacheEmpty) {
    fetchFreshData();
  } else {
    useCachedData();
  }
});

// Practical: Verify authentication tokens
const validTokens$ = from(authTokens).pipe(
  filter(token => !isExpired(token))
);

validTokens$.pipe(
  isEmpty()
).subscribe(noValidTokens => {
  if (noValidTokens) {
    redirectToLogin();
  } else {
    proceedToApp();
  }
});
Note: isEmpty must wait for source to complete. Use with finite streams or apply take/takeUntil to prevent infinite waiting.

5. defaultIfEmpty for Fallback Value Provision

Feature Syntax Description Behavior
defaultIfEmpty defaultIfEmpty(defaultValue) Emits default value if source completes without emitting Fallback for empty streams
Pass-through If source emits, default is ignored Only activates on empty completion Conditional fallback

Example: defaultIfEmpty for fallback values

import { EMPTY, of, from } from 'rxjs';
import { defaultIfEmpty, filter } from 'rxjs/operators';

// Basic defaultIfEmpty - provides fallback
EMPTY.pipe(
  defaultIfEmpty('No data')
).subscribe(val => console.log(val)); // 'No data'

// Non-empty stream - default ignored
of(1, 2, 3).pipe(
  defaultIfEmpty(999)
).subscribe(val => console.log(val)); // 1, 2, 3

// Practical: Search with fallback
ajax.getJSON(`/api/search?q=${query}`).pipe(
  mergeMap(response => from(response.results)),
  defaultIfEmpty({ message: 'No results found' })
).subscribe(result => displayResult(result));

// Practical: User preferences with defaults
ajax.getJSON('/api/user/preferences').pipe(
  mergeMap(response => from(response.preferences)),
  filter(pref => pref.enabled),
  defaultIfEmpty({
    theme: 'light',
    language: 'en',
    notifications: true
  })
).subscribe(preferences => applyPreferences(preferences));

// Practical: Shopping cart with default message
const cartItems$ = from(cart.items);

cartItems$.pipe(
  defaultIfEmpty({ type: 'empty-cart', message: 'Your cart is empty' })
).subscribe(item => {
  if (item.type === 'empty-cart') {
    showEmptyCartUI(item.message);
  } else {
    displayCartItem(item);
  }
});

// Practical: Configuration with fallbacks
ajax.getJSON('/api/config').pipe(
  map(config => config.feature),
  filter(feature => feature.enabled),
  defaultIfEmpty({
    enabled: false,
    settings: defaultSettings
  })
).subscribe(featureConfig => initializeFeature(featureConfig));

// Practical: Filtered list with placeholder
const activeUsers$ = ajax.getJSON('/api/users').pipe(
  mergeMap(response => from(response.users)),
  filter(user => user.active)
);

activeUsers$.pipe(
  defaultIfEmpty({ 
    id: null, 
    name: 'No active users',
    placeholder: true 
  })
).subscribe(user => {
  if (user.placeholder) {
    showPlaceholder(user.name);
  } else {
    displayUser(user);
  }
});

// Practical: Latest notification with default
const notifications$ = ajax.getJSON('/api/notifications').pipe(
  mergeMap(response => from(response.notifications)),
  filter(n => !n.read),
  take(1)
);

notifications$.pipe(
  defaultIfEmpty({
    title: 'All caught up!',
    message: 'No new notifications',
    type: 'placeholder'
  })
).subscribe(notification => displayNotification(notification));

// Practical: Validation errors with success message
const errors$ = validateForm(formData);

errors$.pipe(
  defaultIfEmpty({ 
    type: 'success', 
    message: 'Form is valid' 
  })
).subscribe(result => {
  if (result.type === 'success') {
    showSuccess(result.message);
    submitForm();
  } else {
    showError(result);
  }
});

// Practical: Product recommendations
ajax.getJSON('/api/recommendations').pipe(
  mergeMap(response => from(response.products)),
  filter(product => product.inStock),
  defaultIfEmpty({
    id: null,
    name: 'Check back later for recommendations',
    placeholder: true
  })
).subscribe(product => displayProduct(product));

// Practical: Chat messages with welcome
const messages$ = ajax.getJSON(`/api/chat/${roomId}`).pipe(
  mergeMap(response => from(response.messages))
);

messages$.pipe(
  defaultIfEmpty({
    author: 'System',
    text: 'No messages yet. Start the conversation!',
    timestamp: Date.now()
  })
).subscribe(message => displayMessage(message));

// Practical: Dashboard widgets with default
const widgets$ = from(userDashboard.widgets).pipe(
  filter(w => w.visible)
);

widgets$.pipe(
  defaultIfEmpty({
    type: 'welcome',
    content: 'Add widgets to customize your dashboard'
  })
).subscribe(widget => renderWidget(widget));

// Multiple defaults example
const data$ = ajax.getJSON('/api/data').pipe(
  mergeMap(response => from(response.items)),
  filter(item => item.priority === 'high'),
  defaultIfEmpty(null)
);

data$.subscribe(item => {
  if (item === null) {
    console.log('No high priority items');
  } else {
    processItem(item);
  }
});

6. sequenceEqual for Stream Comparison

Feature Syntax Description Comparison
sequenceEqual sequenceEqual(compareTo$, comparator?) Compares two observables emit same values in same order Element-wise equality
Custom comparator sequenceEqual(other$, (a, b) => a.id === b.id) Define custom equality logic Flexible comparison
Result Emits single boolean then completes true if sequences equal, false otherwise Complete comparison needed

Example: sequenceEqual for stream comparison

import { of, from } from 'rxjs';
import { sequenceEqual, delay } from 'rxjs/operators';

// Basic sequenceEqual - identical sequences
const a$ = of(1, 2, 3);
const b$ = of(1, 2, 3);

a$.pipe(
  sequenceEqual(b$)
).subscribe(equal => console.log('Equal?', equal)); // true

// Different sequences
const c$ = of(1, 2, 3);
const d$ = of(1, 2, 4);

c$.pipe(
  sequenceEqual(d$)
).subscribe(equal => console.log('Equal?', equal)); // false

// Different lengths
const e$ = of(1, 2, 3);
const f$ = of(1, 2);

e$.pipe(
  sequenceEqual(f$)
).subscribe(equal => console.log('Equal?', equal)); // false

// Timing doesn't matter
const slow$ = of(1, 2, 3).pipe(delay(1000));
const fast$ = of(1, 2, 3);

slow$.pipe(
  sequenceEqual(fast$)
).subscribe(equal => console.log('Equal?', equal)); // true

// Custom comparator
const users1$ = of(
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
);

const users2$ = of(
  { id: 1, name: 'Alice Updated' },
  { id: 2, name: 'Bob Updated' }
);

users1$.pipe(
  sequenceEqual(users2$, (a, b) => a.id === b.id)
).subscribe(equal => console.log('IDs equal?', equal)); // true

// Practical: Verify data integrity
const original$ = ajax.getJSON('/api/data/original');
const backup$ = ajax.getJSON('/api/data/backup');

original$.pipe(
  mergeMap(r => from(r.items)),
  sequenceEqual(
    backup$.pipe(mergeMap(r => from(r.items)))
  )
).subscribe(dataIntact => {
  if (dataIntact) {
    console.log('Backup verified');
  } else {
    console.error('Data mismatch detected');
    triggerBackupAlert();
  }
});

// Practical: Compare form states
const initialFormState$ = of(initialFormData);
const currentFormState$ = of(getCurrentFormData());

initialFormState$.pipe(
  sequenceEqual(currentFormState$, deepEqual)
).subscribe(unchanged => {
  if (unchanged) {
    disableSaveButton();
  } else {
    enableSaveButton();
  }
});

// Practical: Validate sequential processing
const expectedOrder$ = from(['step1', 'step2', 'step3']);
const actualOrder$ = processSteps();

expectedOrder$.pipe(
  sequenceEqual(actualOrder$)
).subscribe(correctOrder => {
  if (correctOrder) {
    console.log('Steps executed in correct order');
    markProcessComplete();
  } else {
    console.error('Step order violation');
    abortProcess();
  }
});

// Practical: Test API response consistency
const firstCall$ = ajax.getJSON('/api/data');
const secondCall$ = ajax.getJSON('/api/data');

firstCall$.pipe(
  mergeMap(r => from(r.items)),
  sequenceEqual(
    secondCall$.pipe(mergeMap(r => from(r.items)))
  )
).subscribe(consistent => {
  if (!consistent) {
    console.warn('API returned inconsistent data');
  }
});

// Practical: Verify replay accuracy
const live$ = dataStream$;
const replayed$ = replayStream$;

live$.pipe(
  sequenceEqual(replayed$)
).subscribe(accurate => {
  if (accurate) {
    console.log('Replay accurate');
  } else {
    console.error('Replay discrepancy');
  }
});

// Practical: Compare user action sequences
const expectedActions$ = from(['login', 'navigate', 'edit', 'save']);
const actualActions$ = from(recordedUserActions);

expectedActions$.pipe(
  sequenceEqual(actualActions$)
).subscribe(matchesPattern => {
  if (matchesPattern) {
    console.log('User followed expected workflow');
  } else {
    console.log('Workflow deviation detected');
  }
});

// Practical: Configuration comparison
const defaultConfig$ = of(defaultConfiguration);
const userConfig$ = of(userConfiguration);

defaultConfig$.pipe(
  sequenceEqual(userConfig$, (a, b) => JSON.stringify(a) === JSON.stringify(b))
).subscribe(isDefault => {
  if (isDefault) {
    hideResetButton();
  } else {
    showResetButton();
  }
});

// Practical: Cache validation
const serverData$ = ajax.getJSON('/api/data').pipe(
  mergeMap(r => from(r.items))
);
const cachedData$ = from(cache.getAll());

serverData$.pipe(
  sequenceEqual(cachedData$)
).subscribe(cacheValid => {
  if (cacheValid) {
    useCachedData();
  } else {
    invalidateCache();
    fetchFreshData();
  }
});

// Practical: A/B test variant verification
const variantA$ = from(variantAResults);
const variantB$ = from(variantBResults);

variantA$.pipe(
  sequenceEqual(variantB$)
).subscribe(identical => {
  if (identical) {
    console.log('Variants produce identical results');
  } else {
    console.log('Variants differ - analyze results');
  }
});
Note: sequenceEqual requires both observables to complete. Compares values in order - timing differences ignored.

Section 11 Summary

  • iif selects observable at subscription time based on condition - lazy conditional branching
  • every returns true if all values satisfy predicate, false on first failure - short-circuits
  • find/findIndex emit first matching value/index then complete - efficient searching
  • isEmpty emits true if source completes without values - requires completion
  • defaultIfEmpty provides fallback value for empty streams - graceful empty handling
  • sequenceEqual compares two observables element-wise - supports custom comparators