How do @Input() and @Output() work in Angular, and what pitfalls do seniors mention in interviews?

LowIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

@Input() defines an API for data flowing from parent → child via property binding. @Output() defines an API for events flowing from child → parent via EventEmitter (or newer output helpers). Interviewers expect you to explain timing (updates over time), typing, OnPush/immutability implications, and when NOT to use EventEmitter (cross-component event bus).

Answer

Core idea

@Input() is the child component’s public data API. The parent binds values into it: parent ➜ child.

@Output() is the child component’s public event API. The child emits typed events and the parent listens: child ➜ parent.

Decorator

Direction

Template syntax

What it really is

@Input()

Parent ➜ Child

[prop]="expr"

A bindable property on the child (Angular sets it during change detection).

@Output()

Child ➜ Parent

(event)="handler($event)"

An EventEmitter the parent listens to (payload is whatever the child emits).

Mental model: Inputs are values, Outputs are events

Typical pattern: pass data down, emit intent up

Child receives the current value via @Input and emits user intent via @Output (don’t mutate parent state directly).

TYPESCRIPT
import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core';

type User = { id: string; name: string; email: string };

@Component({
  selector: 'app-user-card',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <article>
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
      <button type="button" (click)="select()">Select</button>
    </article>
  `
})
export class UserCardComponent {
  @Input({ required: true }) user!: User;

  @Output() readonly selected = new EventEmitter<string>();

  select(): void {
    this.selected.emit(this.user.id);
  }
}
                  
HTML
<app-user-card
  [user]="selectedUser"
  (selected)="onUserSelected($event)"
></app-user-card>
                  
TYPESCRIPT
onUserSelected(userId: string): void {
  // parent owns the state update
  console.log('Selected:', userId);
}
                  

Inputs change over time

Interviewers often ask how you react when an input changes after the first render. The correct answers are: ngOnChanges, an @Input setter, or (in newer Angular) signals-based inputs.

TYPESCRIPT
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

type User = { id: string; name: string };

@Component({
  selector: 'app-user-badge',
  template: `{{ display }}`
})
export class UserBadgeComponent implements OnChanges {
  @Input() user?: User;
  display = '—';

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['user']) {
      this.display = this.user ? `${this.user.name} (${this.user.id})` : '—';
    }
  }
}
                  

Custom two-way binding (banana-in-a-box)

If you name an output xChange next to an input x, Angular enables [(x)] syntax. This is a common interview topic (and a common place to make naming mistakes).

TYPESCRIPT
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <button type="button" (click)="dec()">-</button>
    <span>{{ value }}</span>
    <button type="button" (click)="inc()">+</button>
  `
})
export class CounterComponent {
  @Input() value = 0;
  @Output() readonly valueChange = new EventEmitter<number>();

  inc(): void { this.valueChange.emit(this.value + 1); }
  dec(): void { this.valueChange.emit(this.value - 1); }
}

// parent template:
// <app-counter [(value)]="count"></app-counter>
                  

Pitfall (interview-grade)

What goes wrong

Fix

Mutating an @Input() object in the child

With OnPush and immutability, UI updates become unpredictable and state ownership is violated

Treat inputs as read-only; emit intent and let parent update state (new reference)

Using EventEmitter as an app-wide event bus

Tight coupling + unclear ownership + lifecycle surprises

Use a service with RxJS (Subject/Observable) or a store for cross-component events

Heavy logic in template bindings/handlers

Hard to test, noisy templates, can run often

Keep template thin; call a method and do logic in TS

Unstable identity with OnPush (mutating arrays/objects in place)

Child doesn’t re-render because reference didn’t change

Prefer immutable updates (new array/object) or explicit markForCheck() when needed

Wrong naming for two-way (valueChanged instead of valueChange)

[(value)] doesn’t work; parent needs manual wiring

Use the exact x + xChange naming convention

What senior candidates usually mention quickly
Summary

@Input defines the child’s data API (parent ➜ child). @Output defines the child’s event API (child ➜ parent). In interviews, emphasize: typed payloads, reacting to input changes correctly, OnPush + immutability behavior, and using EventEmitter only for component outputs (not as a global event bus).

Similar questions
Guides
23 / 43