Skip to content
Angular interview 5 min read

Signals & Reactivity Questions

Signals are the headline feature of modern Angular, and interviewers increasingly expect you to explain not just the API but the mental model behind reactive state. A signal is a wrapper around a value that notifies consumers when it changes, enabling fine-grained, glitch-free updates without zone.js. This page covers the questions you are most likely to face about signals, computed, effect, and how this model compares with RxJS.

What is a signal, and how does it differ from a plain property?

A signal is a reactive primitive that holds a value and tracks who reads it. You create one with signal(initialValue), read it by calling it as a function (count()), and write to it with .set() or .update(). The key difference from a plain class property is dependency tracking: when a signal is read inside a template, a computed, or an effect, Angular records that consumer and re-runs only it when the signal changes.

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

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `<button (click)="inc()">Count: {{ count() }}</button>`,
})
export class CounterComponent {
  count = signal(0);

  inc(): void {
    this.count.update((n) => n + 1);
  }
}

Read a signal by calling it (count()), not by referencing it (count). Forgetting the parentheses in a template silently renders the function object instead of the value — a classic interview trap.

What is set versus update?

set(value) replaces the value outright. update(fn) derives the next value from the current one, which is safer when the new state depends on the old. There is no mutate in current Angular — to change object or array contents you create a new reference so consumers can detect the change.

this.items.set([]);                          // replace
this.items.update((arr) => [...arr, item]);  // derive from previous

What is computed and how is it different from a method?

computed() creates a derived signal whose value is recalculated from other signals. It is memoized: the function only re-runs when a signal it read actually changes, and the result is cached otherwise. A normal template method re-runs on every change-detection pass, so computed is both more efficient and more predictable.

import { Component, signal, computed } from '@angular/core';

@Component({
  selector: 'app-cart',
  standalone: true,
  template: `Total: {{ total() }}`,
})
export class CartComponent {
  price = signal(20);
  qty = signal(3);
  total = computed(() => this.price() * this.qty());
}

Output:

Total: 60

Computed signals are also lazy — they do not evaluate until something reads them — and they track dependencies dynamically, so a branch that is never taken does not create a dependency.

What is an effect and when should you use one?

An effect() runs a side effect whenever any signal it reads changes. It runs once on creation to establish its dependencies, then re-runs reactively. Use effects to synchronize signals with the outside world — logging, localStorage, third-party libraries, or imperative DOM work. Do not use them to compute derived state; that is what computed is for.

import { Component, signal, effect } from '@angular/core';

@Component({ selector: 'app-theme', standalone: true, template: '' })
export class ThemeComponent {
  theme = signal<'light' | 'dark'>('light');

  constructor() {
    effect(() => {
      localStorage.setItem('theme', this.theme());
    });
  }
}

By default you cannot write to a signal inside an effect (Angular throws to prevent loops); pass { allowSignalWrites: true } only when you truly need it.

How do signals interact with change detection?

When a signal used in a template changes, Angular marks that specific component for check. In a fully signal-based, zoneless app this means updates are far more targeted than the traditional “check everything from the root” pass. Components using signals work today even under the default OnPush strategy without manual markForCheck() calls, because reading a signal in the template registers the template as a consumer.

Signals versus RxJS — when do you use each?

This is the most common comparison question. Both are reactive, but they solve different problems.

AspectSignalsRxJS Observables
Holds a current valueYes, always synchronousNo, values are emitted over time
Best forUI state, derived valuesAsync streams, events, HTTP, debouncing
OperatorsMinimal (computed)Rich (map, switchMap, debounceTime)
Backpressure / cancellationNoYes
Glitch-free derivationsYesRequires care

The pragmatic answer: use signals for state, use RxJS for streams and asynchronous orchestration, and bridge between them. toSignal() converts an observable into a signal, and toObservable() does the reverse.

import { toSignal } from '@angular/core/rxjs-interop';
import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

const http = inject(HttpClient);
const user = toSignal(http.get<User>('/api/me'), { initialValue: null });

What are input() and model() signals?

Modern Angular replaces @Input() with the signal-based input(), giving you a read-only signal for the bound value, and model() for two-way binding that exposes a writable signal. These integrate cleanly with computed and templates.

import { input, model } from '@angular/core';

readonly label = input.required<string>();
readonly checked = model(false); // supports [(checked)] two-way binding

Best Practices

  • Treat signals as the source of truth for UI state and derive everything else with computed, never by manually recomputing.
  • Keep effect() for side effects only; if you are assigning to another signal inside one, you probably want computed.
  • Always create new references for arrays and objects so consumers detect the change.
  • Use toSignal() to consume async data and toObservable() when you need RxJS operators on signal state.
  • Prefer input()/model()/output() over the decorator equivalents in new standalone components.
  • Avoid expensive work inside templates — wrap it in a memoized computed instead of calling a method.
Last updated June 14, 2026
Was this helpful?