Understanding Pure vs. Impure Pipes in Angular: A Comprehensive Guide to Efficient Data Transformation

Angular is a robust framework for building dynamic web applications, and pipes are one of its most powerful features for transforming data in templates. Pipes allow developers to format, filter, or manipulate data declaratively, enhancing readability and reusability. However, not all pipes are created equal. Angular distinguishes between pure and impure pipes, each with distinct behaviors that impact performance and functionality. Choosing between pure and impure pipes is critical for optimizing your application’s performance and ensuring it scales effectively.

In this in-depth guide, we’ll explore the differences between pure and impure pipes, their use cases, and how they affect Angular’s change detection. With detailed explanations, practical examples, and performance considerations, this blog will equip you with the knowledge to use pipes effectively in your Angular applications. Whether you’re formatting dates or filtering lists, understanding pure and impure pipes will help you make informed decisions for better performance and maintainability. Let’s dive into the fundamentals of Angular pipes and the pure vs. impure distinction.


What are Pipes in Angular?

Pipes in Angular are a mechanism for transforming data in templates without altering the underlying data. They take an input value, apply a transformation, and return the result for display. Angular provides built-in pipes like date, uppercase, and json, and developers can create custom pipes for specific needs.

Syntax of a Pipe

Pipes are applied in templates using the | operator:

{ { value | pipeName }}

For example, to format a date:

{ { today | date: 'short' }}

This transforms the today value (e.g., a Date object) into a formatted string like 6/4/2025, 2:36 AM.

Pure vs. Impure Pipes: The Core Difference

Angular categorizes pipes as pure or impure based on how they handle change detection:

  • Pure Pipes: Execute only when their input value changes (based on reference comparison). They are stateless, predictable, and optimized for performance.
  • Impure Pipes: Execute on every change detection cycle, regardless of whether the input value has changed. They can handle complex or mutable data but may impact performance.

The distinction lies in Angular’s change detection mechanism, which determines when a pipe’s transformation logic runs. Understanding this is key to using pipes effectively.

To learn more about Angular pipes, see Angular Pipes.


Deep Dive into Pure Pipes

Pure pipes are the default in Angular and are designed for efficiency. They run only when Angular detects a change in the pipe’s input value or parameters, using reference equality (===) to compare the current and previous values.

Characteristics of Pure Pipes

  • Stateless: Pure pipes don’t maintain internal state between executions. They produce the same output for the same input, making them predictable.
  • Performance-Optimized: Because they run only on input changes, pure pipes minimize computational overhead during change detection.
  • Default Behavior: All built-in Angular pipes (e.g., date, uppercase, currency) and custom pipes are pure unless explicitly marked as impure.

Creating a Pure Pipe

Let’s create a custom pure pipe to format a user’s full name.

  1. Generate the Pipe:
ng generate pipe pipes/full-name
  1. Implement the Pipe:
import { Pipe, PipeTransform } from '@angular/core';

   @Pipe({
     name: 'fullName',
   })
   export class FullNamePipe implements PipeTransform {
     transform(user: { firstName: string; lastName: string }): string {
       if (!user) return '';
       return `${user.firstName} ${user.lastName}`;
     }
   }
  1. Use the Pipe in a Template:
{ { user | fullName }}

Component:

import { Component } from '@angular/core';

   @Component({
     selector: 'app-user',
     template: `{ { user | fullName }}`,
   })
   export class UserComponent {
     user = { firstName: 'John', lastName: 'Doe' };
   }
Explanation:
  • The fullName pipe concatenates firstName and lastName.
  • As a pure pipe (default), it runs only when the user reference changes (e.g., assigning a new object to user).

When Pure Pipes Run

Angular triggers a pure pipe when:

  • The input value’s reference changes (e.g., user = newObject).
  • The pipe’s parameters change (e.g., date: 'short' changes to date: 'full').

For example:

export class UserComponent {
  user = { firstName: 'John', lastName: 'Doe' };

  updateUser() {
    this.user.firstName = 'Jane'; // Pipe DOES NOT run (same object reference)
    this.user = { firstName: 'Jane', lastName: 'Doe' }; // Pipe runs (new reference)
  }
}

Because user.firstName = 'Jane' modifies the existing object, the pipe doesn’t rerun, as the reference remains the same. This is efficient but can be limiting for mutable data.

Benefits of Pure Pipes

  • Performance: Minimizes executions during change detection, ideal for large component trees.
  • Predictability: Same input always produces the same output, simplifying debugging.
  • Scalability: Reduces CPU usage in applications with frequent UI updates.

Limitations of Pure Pipes

Pure pipes rely on reference changes, so they’re unsuitable for:

  • Mutable Objects: Changes to object properties (e.g., user.firstName) don’t trigger the pipe.
  • Arrays with Internal Changes: Adding or removing items from an array without changing its reference.
  • Complex Logic: Scenarios requiring state or frequent updates regardless of input changes.

For custom pipe creation, see Build Custom Pipes.


Deep Dive into Impure Pipes

Impure pipes run on every change detection cycle, regardless of whether the input or parameters have changed. This makes them more flexible but less performant, as they can execute frequently in dynamic applications.

Characteristics of Impure Pipes

  • Stateful or Dynamic: Impure pipes can track internal state or respond to changes in mutable data (e.g., array modifications).
  • Performance Cost: Running on every change detection cycle increases CPU usage, especially in complex apps.
  • Explicit Configuration: Pipes are impure when marked with pure: false in the @Pipe decorator.

Creating an Impure Pipe

Let’s create an impure pipe to filter a list of users based on a search term, which needs to rerun when the array or search term changes internally.

  1. Generate the Pipe:
ng generate pipe pipes/filter-users
  1. Implement the Pipe:
import { Pipe, PipeTransform } from '@angular/core';

   @Pipe({
     name: 'filterUsers',
     pure: false, // Mark as impure
   })
   export class FilterUsersPipe implements PipeTransform {
     transform(users: string[], searchTerm: string): string[] {
       if (!users || !searchTerm) return users || [];
       return users.filter((user) =>
         user.toLowerCase().includes(searchTerm.toLowerCase())
       );
     }
   }
  1. Use the Pipe in a Template:
{ { user }}

Component:

import { Component } from '@angular/core';

   @Component({
     selector: 'app-user-list',
     template: `
       
       
         { { user }}
       
     `,
   })
   export class UserListComponent {
     users = ['John Doe', 'Jane Smith', 'Bob Johnson'];
     searchTerm = '';

     addUser() {
       this.users.push('Alice Brown'); // Impure pipe detects this change
     }
   }
Explanation:
  • The filterUsers pipe filters the users array based on searchTerm.
  • As an impure pipe (pure: false), it runs on every change detection cycle, detecting changes like users.push or updates to searchTerm.
  • Without pure: false, the pipe wouldn’t rerun when the array is modified in place, as the users reference remains unchanged.

When Impure Pipes Run

Impure pipes execute:

  • On every change detection cycle, triggered by any event in the Angular zone (e.g., clicks, HTTP responses, timers).
  • Even if the input reference or parameters haven’t changed, making them suitable for mutable data.

Benefits of Impure Pipes

  • Flexibility: Handle mutable objects, arrays, or stateful transformations (e.g., filtering, sorting).
  • Dynamic Updates: Respond to internal changes without requiring new references.
  • Complex Logic: Support scenarios where transformations depend on external state or frequent updates.

Limitations of Impure Pipes

  • Performance Overhead: Frequent executions can slow down applications, especially with large datasets or complex transformations.
  • Debugging Challenges: Unpredictable execution frequency can make it harder to trace performance issues.
  • Scalability Concerns: In large apps, multiple impure pipes can degrade performance significantly.

Pure vs. Impure Pipes: A Side-by-Side Comparison

AspectPure PipesImpure Pipes
Execution TriggerRuns only on input/parameter reference changeRuns on every change detection cycle
PerformanceHighly efficient, minimal overheadCan be resource-intensive
Use CaseStateless transformations (e.g., formatting)Mutable data or stateful logic (e.g., filtering)
DefaultYes (all pipes unless specified)No (requires pure: false)
Exampledate, uppercase, custom formattingFiltering arrays, sorting dynamic lists

Choosing Between Pure and Impure Pipes

  • Use Pure Pipes When:
    • The transformation is stateless (e.g., formatting strings, numbers, or dates).
    • Performance is a priority, especially in large applications.
    • Inputs are immutable or change via new references.
  • Use Impure Pipes When:
    • The transformation involves mutable data (e.g., arrays modified in place).
    • The pipe needs to respond to internal state changes or external triggers.
    • The dataset is small, and performance impact is negligible.

Performance Tip: Optimize Impure Pipes

If you must use an impure pipe, mitigate its performance impact by:

  • Limiting Scope: Apply the pipe to small datasets or specific components.
  • Using OnPush Change Detection: Reduces change detection cycles, as discussed in [Optimize Change Detection](/angular/advanced/optimize-change-detection).
  • Memoizing Results: Cache previous outputs to avoid redundant computations (see example below).

Example: Memoized Impure Pipe

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filterUsers',
  pure: false,
})
export class FilterUsersPipe implements PipeTransform {
  private cachedUsers: string[] = [];
  private cachedSearchTerm = '';
  private cachedResult: string[] = [];

  transform(users: string[], searchTerm: string): string[] {
    // Return cached result if inputs haven't changed
    if (
      users === this.cachedUsers &&
      searchTerm === this.cachedSearchTerm &&
      this.cachedResult
    ) {
      return this.cachedResult;
    }

    // Update cache
    this.cachedUsers = users;
    this.cachedSearchTerm = searchTerm;

    // Perform filtering
    this.cachedResult =
      users?.filter((user) =>
        user.toLowerCase().includes(searchTerm.toLowerCase())
      ) || [];

    return this.cachedResult;
  }
}

Explanation:

  • Caches the input users, searchTerm, and output cachedResult.
  • Returns the cached result if inputs haven’t changed, reducing computations.
  • Still impure to detect array mutations, but more efficient.

Practical Example: Combining Pure and Impure Pipes

Let’s combine a pure pipe (formatting) and an impure pipe (filtering) in a single application to demonstrate their interplay.

  1. Pure Pipe: FullName (from earlier):
@Pipe({
     name: 'fullName',
   })
   export class FullNamePipe implements PipeTransform {
     transform(user: { firstName: string; lastName: string }): string {
       if (!user) return '';
       return `${user.firstName} ${user.lastName}`;
     }
   }
  1. Impure Pipe: FilterUsers:
@Pipe({
     name: 'filterUsers',
     pure: false,
   })
   export class FilterUsersPipe implements PipeTransform {
     transform(users: { firstName: string; lastName: string }[], searchTerm: string): any[] {
       if (!users || !searchTerm) return users || [];
       return users.filter((user) =>
         `${user.firstName} ${user.lastName}`
           .toLowerCase()
           .includes(searchTerm.toLowerCase())
       );
     }
   }
  1. Component:
import { Component } from '@angular/core';

   @Component({
     selector: 'app-user-filter',
     template: `
       
       
         
           { { user | fullName }}
         
       
       Add User
     `,
   })
   export class UserFilterComponent {
     users = [
       { firstName: 'John', lastName: 'Doe' },
       { firstName: 'Jane', lastName: 'Smith' },
     ];
     searchTerm = '';

     addUser() {
       this.users.push({ firstName: 'Bob', lastName: 'Johnson' });
     }
   }
  1. Explanation:
    • Impure Pipe (filterUsers): Runs on every change detection cycle, detecting array mutations (e.g., addUser) and search term changes.
    • Pure Pipe (fullName): Runs only when a user object’s reference changes, efficiently formatting each user’s name.
    • Result: The impure pipe handles dynamic filtering, while the pure pipe optimizes static formatting, balancing functionality and performance.

Measuring Pipe Performance

To ensure your pipes are performant, profile their impact using Angular and browser tools.

Step 1: Use Angular DevTools

  1. Install Angular DevTools from the Chrome Web Store.
  2. Open the Profiler tab and record a session while interacting with components using pipes.
  3. Check for excessive change detection cycles, especially with impure pipes. Optimize by memoizing or switching to pure pipes where possible.

Step 2: Use Chrome DevTools

  1. Open Chrome DevTools (F12) and go to the Performance tab.
  2. Record a session while triggering events (e.g., typing in a search input).
  3. Look for long tasks related to pipe execution. Impure pipes may show frequent calls in the call stack.

Step 3: Compare Pure vs. Impure

Test the same functionality with a pure pipe (if feasible) and an impure pipe:

  • Pure Pipe: Fewer executions, lower CPU usage.
  • Impure Pipe: More executions, higher overhead, especially with large datasets.

For advanced profiling, see Profile App Performance.


FAQs

What is the difference between pure and impure pipes in Angular?

Pure pipes run only when their input or parameters change (based on reference comparison), making them performant. Impure pipes run on every change detection cycle, allowing them to handle mutable data but with higher performance costs.

When should I use an impure pipe?

Use an impure pipe when dealing with mutable data (e.g., arrays modified in place) or stateful transformations, such as filtering or sorting dynamic lists. Be cautious of performance impacts in large applications.

Can I optimize an impure pipe’s performance?

Yes, optimize impure pipes by memoizing results, limiting their scope to small datasets, or using OnPush change detection to reduce change detection cycles. Caching previous outputs can significantly improve efficiency.

Are Angular’s built-in pipes pure or impure?

All built-in Angular pipes (e.g., date, uppercase, currency) are pure, ensuring optimal performance for common transformations. Custom pipes are pure by default unless marked with pure: false.


Conclusion

Understanding the difference between pure and impure pipes in Angular is crucial for building efficient, scalable applications. Pure pipes offer performance and predictability for stateless transformations, while impure pipes provide flexibility for dynamic, mutable data at the cost of increased overhead. By carefully choosing between them, optimizing impure pipes with memoization, and combining them with OnPush change detection, you can create responsive and maintainable Angular applications.

For further exploration, dive into related topics like Build Custom Pipes or Optimize Change Detection to enhance your Angular skills. With a solid grasp of pure and impure pipes, you’ll be well-equipped to transform data efficiently, delivering exceptional user experiences in your applications.