Frontend interview answer

Angular pipes: built-in, async pipe, and pure vs impure (performance + pitfalls)

HighIntermediateAngular

Interview quick answer

Angular pipes transform values in templates using the | operator (formatting, slicing, mapping) without mutating the source. Most pipes are pure (run only when input reference/primitive changes). The async pipe is special: it subscribes/unsubscribes automatically and triggers view updates.

Interview focus

This Angular interview question tests whether you can explain pipes in Angular and when should you use them, connect it to production trade-offs, and handle common follow-up questions.

  • pipes in Angular and when should you use them explanation without falling back to memorized definitions
  • Pipes and Templates reasoning, edge cases, and production failure modes
  • How you would answer the most likely Angular interview follow-up
Practice more Angular interview questions
Interview answer drill

Use this Angular interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.

Full interview answer

Core idea

A pipe is a reusable template-level transformer: it takes an input value and returns a derived value for display. It should be side-effect free and should not mutate the input.

Concept

What it means in practice

Syntax

{{ value | pipeName:arg1:arg2 }} (you can pass parameters)

Chaining

{{ value | pipeA | pipeB }} (output of A becomes input of B)

Where it runs

In Angular’s change detection update phase (template evaluation).

What it’s for

Formatting + small/cheap view transforms; not heavy computation or side effects.

How pipes show up in real templates
HTML
<p>{{ today | date:'fullDate' }}</p>
<p>{{ username | uppercase }}</p>
<p>{{ price | currency:'EUR':'symbol':'1.2-2' }}</p>
<p>{{ completion | percent:'1.0-0' }}</p>
<pre>{{ user | json }}</pre>
                  

Built-in pipe

Typical use

date

Date/time formatting (uses locale + optional timezone)

currency

Money formatting (currency code, display, digitsInfo)

number / decimal

Numeric formatting with digitsInfo

percent

Percent formatting

json

Debug display of objects (not for production UI)

slice

Substrings / subarrays in templates (keep usage cheap)

keyvalue

Iterate object maps in templates

async

Subscribe to Observable/Promise and render latest value safely

Common built-ins interviewers expect you to mention

The async pipe is the one that matters most in real apps

async subscribes/unsubscribes automatically, avoids manual subscription leaks, and marks the view for update when a new value arrives (works great with OnPush).

TYPESCRIPT
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AsyncPipe, NgIf } from '@angular/common';
import { Observable, timer, map } from 'rxjs';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [NgIf, AsyncPipe],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <p *ngIf="count$ | async as count">Count: {{ count }}</p>
  `
})
export class DemoComponent {
  readonly count$: Observable<number> = timer(0, 1000).pipe(map(t => t));
}
                  

Pure vs impure pipes (performance hotspot)

Type

When it runs

Pros

Cons / risk

Pure (default)

Only when input changes by value (primitives) or by reference (objects/arrays/functions).

Fast; avoids re-running on every CD pass.

If you mutate arrays/objects in place, the pipe may not re-run (because reference didn’t change).

Impure (pure: false)

Every change detection cycle.

Can react to in-place mutation or time-varying global state.

Can become very expensive and cause jank; use sparingly.

Most Angular pipe interview questions are really about change detection cost

Classic pure-pipe gotcha

If you do items.push(...), the array reference stays the same, so a pure pipe may not run again. Prefer immutable updates (items = [...items, newItem]) or move the logic out of the template.

Custom pipes: keep them pure + small

Custom pipes are great for reusable formatting/transforms. Keep them deterministic, no side effects, no DOM, no HTTP. In modern Angular you can make them standalone and import them directly.

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

@Pipe({
  name: 'titleCaseWords',
  standalone: true
})
export class TitleCaseWordsPipe implements PipeTransform {
  transform(value: string | null | undefined): string {
    if (!value) return '';
    return value
      .trim()
      .split(/\s+/)
      .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
      .join(' ');
  }
}
                  
TYPESCRIPT
import { Component } from '@angular/core';
import { TitleCaseWordsPipe } from './title-case-words.pipe';

@Component({
  selector: 'app-user',
  standalone: true,
  imports: [TitleCaseWordsPipe],
  template: `{{ name | titleCaseWords }}`
})
export class UserComponent {
  name = 'ada lovelace';
}
                  

Pitfall

What goes wrong

Better approach

Heavy work inside a pipe used in big lists

Even pure pipes can run often enough to hurt if the transform is expensive.

Precompute in TS, memoize by key, or transform upstream in RxJS/store selectors.

Using an impure pipe to “fix” mutation

Runs every CD cycle; can tank performance.

Prefer immutable state updates or move logic out of template.

Doing side effects in pipes

Unpredictable behavior; multiple calls; hard to test.

Keep pipes pure; side effects belong in services/effects/components.

Manual subscribe in component just to render

Leaks/unsubscribe complexity; extra state variables.

Use async pipe in template.

Calling methods in templates instead of pipes

Method is executed every CD pass; can be worse than a pure pipe.

Use pure pipes for cheap transforms, or cache results explicitly.

What senior candidates mention quickly
TYPESCRIPT
// template
// <li *ngFor="let item of items | visibleItems">{{ item.name }}</li>
// <p *ngIf="user$ | async as user">{{ user.name }}</p>

addWrong(newItem: Item): void {
  this.items.push(newItem); // same array reference => pure pipe may not rerun
}

addRight(newItem: Item): void {
  this.items = [...this.items, newItem]; // new reference => pure pipe reruns
}

readonly user$ = this.api.loadUser();

// Manual subscribe just to copy data into a field is usually worse than
// letting the async pipe manage subscription and teardown in the template.
                  

Rule of thumb

Use a pure pipe for cheap, deterministic display transforms. Use the async pipe when the template should render an Observable or Promise without manual subscription bookkeeping. If the transform is heavy, list-sized, or needs memoization keys, move it out of the template into a selector, computed signal, facade, or RxJS pipeline instead of reaching for an impure pipe.

Summary

Pipes are template transformers used via |. Default is pure: runs when inputs change by value/reference (fast, but mutation-in-place can fool it). Impure pipes run every CD cycle (use rarely). The async pipe is key: it manages subscriptions and keeps templates clean—especially with OnPush.

Similar questions
Guides
Preparing for interviews?