7 Advanced Angular Interview Questions for 2026 (ANSWERED)

·13 min read
AngularInterview QuestionsAdvanced AngularChange DetectionZone.jsDependency InjectionAngular SignalsSenior Developer

Angular is the framework of choice for 17% of web developers and dominates enterprise applications—yet Zone.js, the library that makes Angular's "magic" work, is understood by fewer than 1 in 10 Angular developers. How does Angular know when your data changed? Why do some components update and others don't? What actually happens when you call markForCheck()?

The questions that separate senior Angular developers from experienced beginners aren't about syntax or APIs. They're about understanding the mechanisms that make Angular work. These seven questions consistently reveal that understanding.

Question 1: How Does Change Detection Work in Angular?

This is the foundational question that sets the tone for an Angular interview.

The 30-Second Answer

Change detection is Angular's process of comparing the current component state with the previous state and updating the DOM when differences are found. It runs whenever async operations complete—like HTTP responses, timers, or user events—because Angular uses Zone.js to intercept these operations and trigger checks automatically.

The 2-Minute Answer (If They Want More)

A common misconception is that Angular uses a Virtual DOM like React. It doesn't. Angular uses Zone.js to know when to check for changes and a component tree traversal to detect what changed.

Here's the mental model: Zone.js wraps browser APIs like setTimeout, addEventListener, and fetch. When any async operation completes inside the Angular zone, Zone.js notifies Angular, which then runs change detection.

During change detection, Angular walks down the component tree from root to leaves. For each component, it compares the current values of template expressions with their previous values. If something changed, Angular updates the DOM.

// This triggers change detection automatically
@Component({
    selector: 'app-counter',
    template: `<button (click)="increment()">{{ count }}</button>`
})
export class CounterComponent {
    count = 0;
 
    increment() {
        this.count++; // Zone.js detects the click, triggers CD
    }
}

The default strategy checks every component on every cycle—even if nothing changed. This is why OnPush exists, and why understanding change detection matters for performance.

What interviewers are looking for: Understanding that Zone.js triggers change detection, not "watching" or "observing." Knowledge that Angular traverses the component tree, not a virtual DOM diff.

Common follow-up: "How would you trigger change detection manually?" Strong candidates mention ChangeDetectorRef.detectChanges() for immediate detection and markForCheck() for scheduling in OnPush components.

Question 2: What Is Zone.js and Can You Run Angular Without It?

This question reveals whether a candidate understands Angular's internals or just uses it as a black box.

The 30-Second Answer

Zone.js is a library that patches async browser APIs to create an execution context where Angular can detect when async operations complete. Yes, you can run Angular without Zone.js using the zoneless change detection option introduced in Angular 18, but you'll need to manage change detection manually or use Signals.

The 2-Minute Answer (If They Want More)

Zone.js monkey-patches browser APIs—setTimeout, Promise, addEventListener, fetch, XMLHttpRequest, and others—to intercept when they're called and when they complete.

Think of Zone.js as a surveillance system for async operations. Every time you click a button, wait for an HTTP response, or use setTimeout, Zone.js knows about it and tells Angular "something async just happened—you might want to check for changes."

// Without Zone.js, this wouldn't trigger change detection
setTimeout(() => {
    this.message = 'Updated!'; // Zone.js intercepts setTimeout
}, 1000);

Here's where it gets interesting. Since Angular 18, you can opt out of Zone.js entirely:

// main.ts - Zoneless Angular
bootstrapApplication(AppComponent, {
    providers: [
        provideExperimentalZonelessChangeDetection()
    ]
});

Without Zone.js, Angular doesn't automatically know when to run change detection. You either manage it manually with ChangeDetectorRef, or you use Angular Signals, which have their own reactivity system.

The NgZone service lets you interact with zones programmatically:

export class HeavyComponent {
    constructor(private ngZone: NgZone) {}
 
    runHeavyTask() {
        // Run outside Angular zone - no change detection triggered
        this.ngZone.runOutsideAngular(() => {
            // Heavy computation or third-party library
            heavyLibrary.process();
        });
 
        // Re-enter Angular zone when you need updates
        this.ngZone.run(() => {
            this.result = 'Done!';
        });
    }
}

What interviewers are looking for: Understanding of what Zone.js patches and why. Awareness of zoneless Angular as the future direction. Practical knowledge of NgZone for performance optimization.

Question 3: OnPush vs Default - When Do You Use Each Strategy?

This question tests performance optimization knowledge and immutability understanding.

The 30-Second Answer

Default strategy checks the component on every change detection cycle. OnPush only checks when input references change, events originate from the component, or you explicitly trigger detection. Use OnPush with immutable data patterns for better performance; use Default when component data changes frequently from external sources.

The 2-Minute Answer (If They Want More)

The strategies differ in when Angular checks a component, and this affects how you need to structure your data flow.

With Default, Angular checks the component during every change detection cycle, regardless of whether its inputs changed. Safe but potentially wasteful.

With OnPush, Angular only checks the component when an @Input() reference changes (not just its contents), when an event originates from the component or its children, when you explicitly call markForCheck() or detectChanges(), or when an async pipe receives a new value.

@Component({
    selector: 'app-user-card',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: `<div>{{ user.name }}</div>`
})
export class UserCardComponent {
    @Input() user!: User;
}
 
// Parent component
@Component({
    template: `<app-user-card [user]="currentUser" />`
})
export class ParentComponent {
    currentUser = { name: 'Alice' };
 
    updateName() {
        // WRONG: OnPush won't detect this mutation
        this.currentUser.name = 'Bob';
 
        // CORRECT: Create new reference
        this.currentUser = { ...this.currentUser, name: 'Bob' };
    }
}

The key insight: OnPush forces you into immutable data patterns. Instead of mutating objects, you create new ones. This pairs perfectly with RxJS Observables and the async pipe:

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: `
        <div *ngIf="user$ | async as user">
            {{ user.name }}
        </div>
    `
})
export class UserCardComponent {
    user$ = this.userService.getUser();
}

The async pipe automatically calls markForCheck() when new values arrive, making OnPush work seamlessly with reactive patterns.

What interviewers are looking for: Understanding that OnPush requires immutable data patterns. Knowledge of what triggers OnPush detection. Practical experience combining OnPush with RxJS.

Question 4: How Does Hierarchical Dependency Injection Work?

This question tests understanding of Angular's DI system beyond basic usage.

The 30-Second Answer

Angular creates a tree of injectors that mirrors the component tree. When you request a dependency, Angular searches upward through parent injectors until it finds a provider. This enables scoped instances—different parts of your app can have different instances of the same service based on where the provider is registered.

The 2-Minute Answer (If They Want More)

Angular has multiple injector levels, and where you provide a service determines its scope and lifetime.

At the top is the root injector (created by providedIn: 'root'). Services here are true singletons—one instance for the entire application.

Below that are module injectors for eagerly-loaded modules. But here's a gotcha: lazy-loaded modules get their own injector, so services provided in a lazy module are scoped to that module.

Finally, each component can have its own element injector:

// Singleton for the entire app
@Injectable({ providedIn: 'root' })
export class GlobalService { }
 
// New instance for each UserProfileComponent AND its children
@Component({
    selector: 'app-user-profile',
    providers: [UserStateService], // Component-level provider
    template: `...`
})
export class UserProfileComponent { }

The resolution algorithm walks up the injector tree:

UserDetailsComponent (child)
    ↓ "Do I have UserStateService?" No, check parent
UserProfileComponent (parent)
    ↓ "Do I have UserStateService?" Yes! Return this instance

This enables powerful patterns. A pattern that's served me well is providing a state service at a "container" component level, so all child components share the same state instance:

@Component({
    selector: 'app-checkout',
    providers: [CheckoutStateService], // Scoped to checkout flow
    template: `
        <app-cart />      <!-- Gets same CheckoutStateService -->
        <app-payment />   <!-- Gets same CheckoutStateService -->
        <app-summary />   <!-- Gets same CheckoutStateService -->
    `
})
export class CheckoutComponent { }

What interviewers are looking for: Understanding of injector hierarchy beyond "root = singleton." Knowledge of component-level providers for scoped instances. Awareness of lazy module injector isolation.

Common follow-up: "What's the difference between providedIn: 'root' and putting the service in AppModule providers?" Tree shaking. With providedIn: 'root', unused services are removed during build. With AppModule providers, they're always included.

Question 5: What Are Angular Signals and How Do They Change Reactivity?

This question tests awareness of modern Angular and where the framework is heading.

The 30-Second Answer

Signals are reactive primitives introduced in Angular 16 that track value changes without Zone.js. When a signal's value changes, Angular knows exactly which components need updating—no tree traversal required. Signals enable fine-grained reactivity and are the foundation for zoneless Angular.

The 2-Minute Answer (If They Want More)

Signals represent a fundamental shift in how Angular handles reactivity. Unlike Zone.js-based change detection (which checks everything when anything might have changed), signals enable fine-grained reactivity where Angular knows exactly what changed.

A signal is a wrapper around a value that tracks reads and writes:

import { signal, computed, effect } from '@angular/core';
 
@Component({
    selector: 'app-counter',
    template: `
        <p>Count: {{ count() }}</p>
        <p>Double: {{ doubled() }}</p>
        <button (click)="increment()">+1</button>
    `
})
export class CounterComponent {
    // Writable signal
    count = signal(0);
 
    // Computed signal - automatically updates when count changes
    doubled = computed(() => this.count() * 2);
 
    increment() {
        // Update signal value
        this.count.update(c => c + 1);
    }
}

Notice the template uses count() not count—you call the signal like a function to read its value. This is how Angular tracks which templates depend on which signals.

The key insight: with signals, Angular doesn't need to traverse the component tree. When count changes, Angular knows exactly which template expressions use it and updates only those. This is why signals are essential for zoneless Angular.

Signals also work beautifully with RxJS via toSignal and toObservable:

@Component({...})
export class UserComponent {
    // Convert Observable to Signal
    user = toSignal(this.userService.getCurrentUser());
 
    // Convert Signal to Observable
    count = signal(0);
    count$ = toObservable(this.count);
}

What interviewers are looking for: Understanding that signals enable fine-grained reactivity (not just "another way to hold values"). Knowledge of computed signals for derived state. Awareness that signals are the path to zoneless Angular.

Question 6: When Do You Use markForCheck() vs detectChanges()?

This question catches developers who've used these methods without understanding the difference.

The 30-Second Answer

markForCheck() schedules the component for checking during the next change detection cycle—it's asynchronous. detectChanges() immediately runs change detection on the component and its children—it's synchronous. Use markForCheck() with OnPush to notify Angular that something changed; use detectChanges() when you need immediate DOM updates.

The 2-Minute Answer (If They Want More)

These methods serve different purposes and behave very differently.

markForCheck() marks the component and all its ancestors up to the root as "dirty"—needing to be checked. But it doesn't run change detection immediately. It waits for the next cycle:

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class NotificationComponent {
    message = '';
 
    constructor(
        private cdr: ChangeDetectorRef,
        private websocket: WebSocketService
    ) {
        // WebSocket messages happen outside Angular zone
        this.websocket.messages$.subscribe(msg => {
            this.message = msg;
            this.cdr.markForCheck(); // Schedule for next CD cycle
        });
    }
}

detectChanges() immediately runs change detection on this component and its children, synchronously:

export class ChartComponent implements AfterViewInit {
    constructor(private cdr: ChangeDetectorRef) {}
 
    ngAfterViewInit() {
        // Third-party chart library updates the DOM
        this.chartLibrary.render(this.chartElement);
 
        // Immediately sync Angular's view with the DOM
        this.cdr.detectChanges();
    }
}

The key difference is timing and scope:

MethodTimingScope
markForCheck()Next CD cycleMarks ancestors for checking
detectChanges()ImmediateChecks component + descendants

A common mistake is using detectChanges() when markForCheck() would suffice. detectChanges() can cause performance issues if called frequently, and it can lead to "Expression has changed after it was checked" errors if you're not careful about timing.

What interviewers are looking for: Understanding of async vs sync behavior. Knowledge of when each is appropriate. Awareness of potential pitfalls with detectChanges().

Question 7: What Are Standalone Components and Why Are They the Future?

This question tests whether candidates are keeping up with Angular's evolution.

The 30-Second Answer

Standalone components declare their dependencies directly in the component decorator instead of through NgModules. They simplify Angular architecture by removing the need for module declarations and making components more self-contained. They're the recommended approach since Angular 15 and are required for zoneless applications.

The 2-Minute Answer (If They Want More)

Standalone components represent a fundamental shift in Angular's architecture. Instead of the traditional NgModule → declares → Component pattern, standalone components are self-contained units that declare their own dependencies:

// Traditional approach with NgModule
@NgModule({
    declarations: [UserCardComponent],
    imports: [CommonModule, SharedModule],
    exports: [UserCardComponent]
})
export class UserModule { }
 
// Standalone approach
@Component({
    selector: 'app-user-card',
    standalone: true,
    imports: [CommonModule, DatePipe, RouterLink],
    template: `...`
})
export class UserCardComponent { }

The advantages go beyond just less boilerplate. Standalone components are more tree-shakable because dependencies are explicit at the component level. They're easier to lazy-load:

// Lazy-load a standalone component directly
const routes: Routes = [
    {
        path: 'admin',
        loadComponent: () => import('./admin/admin.component')
            .then(m => m.AdminComponent)
    }
];

For new applications, you can bootstrap without any NgModule:

// main.ts - No AppModule needed
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideRouter } from '@angular/router';
 
bootstrapApplication(AppComponent, {
    providers: [
        provideRouter(routes),
        provideHttpClient()
    ]
});

The candidates who impress me understand that standalone components aren't just about removing modules—they're about making Angular applications more modular, tree-shakable, and aligned with modern JavaScript patterns. Combined with signals and zoneless change detection, they represent Angular's modernized architecture.

What interviewers are looking for: Understanding of why standalone is preferred (not just how to use it). Knowledge of bootstrapApplication and provideX functions. Awareness that standalone is required for some modern features.

What These Questions Reveal

After walking through all seven questions, you'll notice they test three areas. First, understanding of Angular's internal mechanisms (change detection, Zone.js, injector hierarchy). Second, ability to optimize performance systematically (OnPush, signals, markForCheck vs detectChanges). Third, awareness of Angular's evolution (signals, standalone components, zoneless).

The candidates who impress me most aren't the ones who memorize answers—they're the ones who explain trade-offs. "OnPush requires immutable patterns, but the performance gain is worth it for data-heavy components." "Zone.js makes development easier, but signals give you finer control." That's the thinking senior developers bring to architecture decisions.

Wrapping Up

These seven questions represent the Angular knowledge that separates senior developers from those who've just followed tutorials. They're not about memorizing APIs—they're about understanding how Angular works and making informed decisions about patterns and performance.

If you want to practice more questions like these, our collection includes 800+ interview questions covering Angular, React, JavaScript, TypeScript, and more—each with detailed explanations that help you understand the why, not just the what.


Related Articles

If you found this helpful, check out these related guides:

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides