Skip to content

RxJS Debugging Techniques ​

Due to the nature of asynchronous streams, debugging RxJS requires a different approach than traditional synchronous debugging techniques.

This page provides basic strategies for debugging RxJS applications and navigation to detailed debugging techniques.

Debugging Techniques Overview ​

Debugging RxJS can be categorized into the following four approaches

ApproachContentDetail Page
Basic Strategytap operator, developer tools, RxJS DevToolsExplained on this page
Common ScenariosSix typical problems: no values flow, memory leaks, missing errors, etc.→ Details
Custom ToolsNamed streams, debug operators, performance measurement→ Details
PerformanceSubscription monitoring, reevaluation detection, memory usage checking, best practices→ Details

Basic debugging strategies ​

1. log output with tap operator ​

The tap operator is the most basic debugging technique, allowing you to observe stream values without side-effects.

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

interval(1000)
  .pipe(
    tap(value => console.log('🔵 Original value:', value)),
    map(x => x * 2),
    tap(value => console.log('🟢 After map:', value)),
    filter(x => x > 5),
    tap(value => console.log('🟡 After filter:', value))
  )
  .subscribe(value => console.log('✅ Final value:', value));

// Output:
// 🔵 Original value: 0
// 🟢 After map: 0
// 🔵 Original value: 1
// 🟢 After map: 2
// 🔵 Original value: 2
// 🟢 After map: 4
// 🔵 Original value: 3
// 🟢 After map: 6
// 🟡 After filter: 6
// ✅ Final value: 6

Key Points ​

  • Insert a tap at each step of the pipeline to track the flow of data
  • Use pictograms and labels to improve the visibility of the log
  • Debug logs can be safely inserted because tap does not change values

2. Output detailed log information ​

To obtain more detailed debugging information, use the Observer object.

ts
import { of, throwError, concat } from 'rxjs';
import { tap } from 'rxjs';

const debug = (tag: string) =>
  tap({
    next: value => console.log(`[${tag}] next:`, value),
    error: error => console.error(`[${tag}] error:`, error),
    complete: () => console.log(`[${tag}] complete`)
  });

// Normal stream
of(1, 2, 3)
  .pipe(debug('Normal'))
  .subscribe();

// Output:
// [Normal] next: 1
// [Normal] next: 2
// [Normal] next: 3
// [Normal] complete

// Stream with error
concat(
  of(1, 2),
  throwError(() => new Error('Error occurred'))
)
  .pipe(debug('Error'))
  .subscribe({
    error: () => {} // Error handling
  });

// Output:
// [Error] next: 1
// [Error] next: 2
// [Error] error: Error: Error occurred

3. check with developer tools ​

This is a debugging technique that utilizes the browser's developer tools.

ts
import { fromEvent, timer } from 'rxjs';
import { map, tap, debounceTime } from 'rxjs';

// Helper function for debugging
function tapDebugger<T>(label: string) {
  return tap<T>({
    next: value => {
      console.group(`🔍 ${label}`);
      console.log('Value:', value);
      console.log('Type:', typeof value);
      console.log('Timestamp:', new Date().toISOString());
      console.trace('Stack trace');
      console.groupEnd();
    }
  });
}

// Debugging button click events
const button = document.querySelector('button');
if (button) {
  fromEvent(button, 'click')
    .pipe(
      tapDebugger('Click Event'),
      debounceTime(300),
      tapDebugger('After Debounce'),
      map(() => ({ timestamp: Date.now() }))
    )
    .subscribe(data => console.log('📤 Send:', data));
}

Utilizing Developer Tools ​

  • Group logs with console.group()
  • Display stack traces with console.trace()
  • Display arrays and objects in an easy-to-read format with console.table()
  • Place breakpoints in tap

4. Utilizing RxJS DevTools ​

RxJS DevTools is a debugging tool provided as a browser extension.

Installation ​

Main features ​

  • Visualization of Observable subscription status
  • Timeline display of stream values
  • Memory leak detection
  • Performance analysis

Usage Example ​

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

// Enable debugging only in development environment
// Different build tools use different environment variable checks
const isDevelopment =
  // Vite: import.meta.env.DEV
  // webpack: process.env.NODE_ENV === 'development'
  // Manual setup: use global variables
  typeof window !== 'undefined' && (window as any).__DEV__ === true;

const stream$ = interval(1000).pipe(
  take(5),
  map(x => x * 2)
);

if (isDevelopment) {
  // Make observable in DevTools
  stream$.subscribe({
    next: value => console.log('DevTools:', value)
  });
}

Detailed debugging techniques ​

Once you understand the basic strategy, learn specific debugging techniques on the detailed pages below.

Common Debugging Scenarios ​

Six typical problems encountered in real-world development and how to solve them

  • Scenario 1: No values flow
  • Scenario 2: Different value than expected is output
  • Scenario 3: Subscription never completes (infinite stream)
  • Scenario 4: Memory leak (forgot to unsubscribe)
  • Scenario 5: Error occurs and is not noticed
  • Scenario 6: I want to track retry attempts

→ View Common Debugging Scenarios

Custom Debug Tools ​

How to create your own debugging tools to meet your project requirements

  • Debugging Named Streams (tagStream)
  • Creating custom debug operators
  • Operator for performance measurement (measure)

→ View Custom Debug Tools

Performance Debugging ​

Application Optimization and Best Practices

  • Check and track subscriptions
  • Detect unnecessary reevaluations (shareReplay)
  • Monitor memory usage
  • Creating a Debugging Environment
  • Type-safe debugging
  • Set error bounds

→ View Performance Debugging

Summary ​

Debugging RxJS can be done efficiently by following these points.

Basic Strategy ​

  • ✅ Observe each stage of the stream with the tap operator
  • ✅ Utilize developer tools for detailed log output
  • ✅ Visualize the stream with RxJS DevTools

Common Scenarios ​

  • ✅ Values do not flow → Forgot subscription, check filtering conditions
  • ✅ Different value than expected → Operator order, note reference sharing
  • ✅ Subscription not completed → use take or takeUntil for infinite streams
  • ✅ Memory leaks → auto unsubscribe with takeUntil pattern
  • ✅ Missing errors → implement proper error handling

Debugging Tools ​

  • ✅ Flexible debugging with custom debug operators
  • ✅ Track multiple streams with named streams
  • ✅ Identify bottlenecks with performance measurement

Performance ​

  • ✅ Prevent memory leaks by monitoring subscriptions
  • ✅ Avoid unnecessary recalculations with shareReplay
  • ✅ Check memory usage periodically

Combined, these techniques allow for efficient debugging of RxJS applications.

Released under the CC-BY-4.0 license.