Master Reference: RxJS, Angular Architecture & Advanced JavaScript / TypeScript (6-7+ YOE) .architect-badge { display: inline-block; background: #701a75; color: #f5d0fe; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold; margin-bottom: 10px; } .kw { color: #f472b6; } .fn { color: #38bdf8; } .str { color: #a7f3d0; } .cm { color: #94a3b8; font-style: italic; }
Complete Technical Guide for Senior Engineers & Technical Interviews (6-7+ YOE)

1. Quick Comparison Tables (Interview Essentials)

Observables vs Promises (Top Interview Question)

Feature RxJS Observables JavaScript Promises
Value Emissions Emits multiple values over time (0, 1, or infinite stream) Emits only a single value (or error) then completes
Execution Lazy (Does nothing until .subscribe() is called) Eager (Executes code immediately upon creation)
Cancellability Cancellable (unsubscribe(), switchMap, takeUntil) Non-cancellable natively once initiated
Operators Support Powerful ecosystem of 100+ operators (map, switchMap, debounceTime) Basic chaining via .then(), .catch(), .finally()
Data Flow Type Push-based stream (Producer pushes data over time) One-time async resolution
Multicasting Cold by default (unicast), can be made Hot (shareReplay) Hot by default (multicast — same value shared)
Angular Integration Built-in framework core (HttpClient, Reactive Forms, Router) Converted via firstValueFrom() or lastValueFrom()
🍕 Pizza Analogy (Promise): You order a pizza once. You wait, receive 1 pizza (or an error if out of stock), and the transaction is done.
📺 Netflix Analogy (Observable): You subscribe once. Video frames, audio, and events stream continuously over time until you unsubscribe.

Higher-Order Mapping Operators

Operator Behavior Metaphor Best Used For
switchMap Cancels previous request, switches to latest Elevator door timer resetting when new person arrives Live Search / Typeahead Input
mergeMap Runs all requests concurrently in parallel Multiple supermarket checkout registers open at once Bulk File Uploads / Parallel Fetches
concatMap Queues requests, runs strictly 1-by-1 in order Single file ticket queue line Bank Transactions / Sequential Saves
exhaustMap Ignores incoming triggers until current finishes Locked door while bathroom is occupied Form Submit / Login Button Click

Subject Types Comparison

Subject Type Initial Value? Replays Past Values? Best Used For
Subject No No (Late subscribers miss past emissions) Event emitters, button clicks, toast notifications
BehaviorSubject Yes (Required) Yes (Replays current/latest value immediately) Shared app state (Current User, Theme)
ReplaySubject(N) No Yes (Replays last N values) Audit logs, recent action history
AsyncSubject No Yes (Only emission upon stream completion) Operations emitting once when finished

2. Event Loop Execution Order: setTimeout vs Promise vs Observable.subscribe()

🔥 Top Interview Snippet

console.log('1: Sync Start');

setTimeout(() => {
  console.log('2: setTimeout (Macrotask)');
}, 0);

Promise.resolve().then(() => {
  console.log('3: Promise (Microtask)');
});

new Observable((subscriber) => {
  console.log('4: Observable Executor (Sync)');
  subscriber.next('5: Observable Value (Sync)');
}).subscribe((val) => {
  console.log(val);
});

console.log('6: Sync End');

Console Output Produced:

1: Sync Start
4: Observable Executor (Sync)
5: Observable Value (Sync)
6: Sync End
3: Promise (Microtask)
2: setTimeout (Macrotask)

Step-by-Step Execution Sequence Explained:

Order Output Log Queue Type Why it executes at this exact moment
1st 1: Sync Start Call Stack (Sync) Main synchronous script execution starts.
2nd 4: Observable Executor (Sync) Call Stack (Sync) Crucial Interview Point: Subscribing to a standard Observable runs synchronously!
3rd 5: Observable Value (Sync) Call Stack (Sync) subscriber.next() fires the observer callback synchronously inside .subscribe().
4th 6: Sync End Call Stack (Sync) Main synchronous script finishes execution.
5th 3: Promise (Microtask) Microtask Queue Promise .then() callback is placed in Microtask Queue. Microtasks execute immediately after Call Stack empties, before Macrotasks!
6th 2: setTimeout (Macrotask) Macrotask Queue setTimeout(..., 0) callback sits in Macrotask (Task) Queue. Executes only after all Microtasks are cleared.

🧠 The Event Loop Golden Rules for Technical Interviews:

  1. Rule 1: Call Stack (Synchronous Code) Always Executes First
    The JS engine executes synchronous code line-by-line until Call Stack is completely empty. Gotcha: Subscribing to a standard RxJS Observable is synchronous!
  2. Rule 2: Microtask Queue Executes Second (Flushes Completely)
    Once Call Stack is empty, Event Loop clears the entire Microtask Queue before touching any Macrotask! (Includes Promise.then(), queueMicrotask(), MutationObserver).
  3. Rule 3: Macrotask Queue Executes Third (One per Tick)
    Event Loop picks one single macrotask, executes it, then checks Microtask Queue again! (Includes setTimeout, setInterval, DOM events).

3. Higher-Order Mapping & Transformation

3.1 switchMap (Cancel Previous, Switch to Latest)

Real-Life Scenario: Live Search / Typeahead Autocomplete

💡 Why use it: Cancels stale pending search HTTP calls when a new keystroke arrives.
this.searchControl.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(query => this.http.get(`/api/search?q=${query}`))
).subscribe(results => this.searchResults = results);

3.2 mergeMap / flatMap (Concurrent Execution)

Real-Life Scenario: Multi-file Upload Manager

💡 Why use it: Processes multiple streams concurrently in parallel without cancelling any item.
from(selectedFiles).pipe(
  mergeMap(file => this.uploadService.uploadFile(file))
).subscribe(res => console.log('Uploaded:', res));

3.3 concatMap (Sequential Order Guaranteed)

Real-Life Scenario: Financial Payments / Step-by-Step Saves

💡 Why use it: Guarantees strict sequential execution order.
from(pendingTransactions).pipe(
  concatMap(tx => this.paymentService.processPayment(tx))
).subscribe(result => console.log('Processed in order:', result));

3.4 exhaustMap (Ignore New Until Current Finishes)

Real-Life Scenario: Login / Form Submit Button (Prevent Spam)

💡 Why use it: Ignores incoming clicks until the active API call completes.
this.loginClick$.pipe(
  exhaustMap(() => this.authService.login(this.form.value))
).subscribe(user => this.router.navigate(['/dashboard']));

3.5 map (Transform Values)

Real-Life Scenario: Data Transformation / DTO Mapping

💡 Why use it: Takes raw emitted values and transforms them into a new object structure.
this.http.get<UserDto>('/api/user/1').pipe(
  map(user => ({
    fullName: `${user.firstName} ${user.lastName}`,
    isAdult: user.age >= 18
  }))
).subscribe(userProfile => console.log(userProfile));

3.6 scan (Accumulate Values Over Time)

Real-Life Scenario: Shopping Cart Total / Running Accumulator

💡 Why use it: Acts like Array.reduce(), but emits running totals after every single emission.
this.addToCart$.pipe(
  scan((totalCount, item) => totalCount + item.quantity, 0)
).subscribe(totalItems => this.cartBadge = totalItems);

4. Combination Operators

4.1 forkJoin (Promise.all equivalent)

Real-Life Scenario: Dashboard Page Load

💡 Why use it: Waits for ALL observables to complete, then emits a single object with all results.
forkJoin({
  profile: this.userService.getProfile(),
  roles: this.userService.getRoles(),
  settings: this.settingsService.getSettings()
}).subscribe(({ profile, roles, settings }) => {
  this.initDashboard(profile, roles, settings);
});

4.2 combineLatest (Re-evaluate when ANY source changes)

Real-Life Scenario: Multi-Filter Table (Search + Category + Date)

💡 Why use it: Whenever ANY filter changes, re-runs query with the latest value of ALL filters combined.
combineLatest([
  this.searchQuery$,
  this.selectedCategory$,
  this.selectedDate$
]).pipe(
  switchMap(([query, category, date]) => 
    this.productService.filterProducts(query, category, date)
  )
).subscribe(filteredProducts => this.products = filteredProducts);

4.3 withLatestFrom (Primary Trigger + Secondary Snapshot)

Real-Life Scenario: Save Action with User Profile Snapshot

💡 Why use it: Captures current state of secondary stream only when the primary trigger emits.
this.saveButtonClick$.pipe(
  withLatestFrom(this.userProfile$),
  switchMap(([_, user]) => this.documentService.save(this.docData, user.id))
).subscribe();

4.4 merge (Combine Multiple Event Streams)

Real-Life Scenario: Refresh Trigger (Manual Click + 30s Auto Timer)

💡 Why use it: Unifies multiple streams into one as events arrive.
merge(
  this.refreshButtonClick$,
  timer(0, 30000) // auto refresh every 30 seconds
).pipe(
  switchMap(() => this.dataService.fetchLatestData())
).subscribe(data => this.updateGrid(data));

4.5 startWith (Provide Initial Value)

Real-Life Scenario: Initial Loading Spinner / Default Value Emitting

💡 Why use it: Emits an initial value immediately before the source starts emitting.
this.searchQuery$.pipe(
  startWith(''), // Start with empty search on page load
  switchMap(query => this.fetchItems(query))
).subscribe(items => this.items = items);

5. Rate Limiting & Filtering Operators

5.1 debounceTime (Pause / Quiet Period)

Real-Life Scenario: Search Input Keypress Pause

💡 Why use it: Waits for 300ms quiet period after last keystroke before firing action.
this.searchInput.valueChanges.pipe(
  debounceTime(300)
).subscribe(text => this.search(text));

5.2 throttleTime (First Event Immediately, Silence for N ms)

Real-Life Scenario: Window Resize / Scroll Event / Click Spam Prevention

💡 Why use it: Emits first event immediately, then ignores subsequent events for 1000ms.
this.scrollEvent$.pipe(
  throttleTime(1000)
).subscribe(() => this.loadMoreItems());

5.3 distinctUntilChanged (Ignore Duplicate Emitted Values)

Real-Life Scenario: Avoiding Redundant API Calls

💡 Why use it: Suppresses emission if current value is identical to previous value.
this.searchTerm$.pipe(
  distinctUntilChanged()
).subscribe(query => this.fetchResults(query));

5.4 takeUntilDestroyed / takeUntil (Memory Leak Cleanup)

Real-Life Scenario: Unsubscribing when Component Unmounts

💡 Why use it: Automatically cleans up subscriptions upon component destruction.
private destroyRef = inject(DestroyRef);

ngOnInit() {
  this.dataStream$.pipe(
    takeUntilDestroyed(this.destroyRef)
  ).subscribe(data => this.processData(data));
}

6. Multicasting, Caching & Error Handling

6.1 shareReplay (Cache & Multicast HTTP Responses)

Real-Life Scenario: Caching Lookup Data (Categories, Brands, Employees)

💡 Why use it: Executes HTTP call once and replays cached response to all subscribers.
getCompanyList(): Observable<Company[]> {
  if (!this.companyList$) {
    this.companyList$ = this.http.get<Company[]>('/api/companies').pipe(
      shareReplay(1) // Cache response & share stream
    );
  }
  return this.companyList$;
}

6.2 catchError (Graceful Error Handling)

Real-Life Scenario: Intercepting HTTP Failures gracefully

💡 Why use it: Intercepts stream errors, shows user toast alert, returns safe fallback observable.
this.http.get('/api/products').pipe(
  catchError(error => {
    this.notificationService.showError('Failed to load products');
    return of([]); // Safe fallback array
  })
).subscribe(products => this.products = products);

6.3 retry / retryWhen (Automatic Network Retry)

Real-Life Scenario: Intermittent Network Glitches / Flaky Connections

💡 Why use it: Automatically re-attempts failed HTTP calls N times before erroring out.
this.http.get('/api/unstable-endpoint').pipe(
  retry(3), // Retry up to 3 times on failure
  catchError(err => of(null))
).subscribe();

6.4 finalize (Execute Cleanup Always)

Real-Life Scenario: Hiding Loading Spinner / Resetting Buttons

💡 Why use it: Runs callback when observable completes OR errors (similar to finally block).
this.isLoading = true;
this.http.get('/api/data').pipe(
  finalize(() => this.isLoading = false) // Always turns off spinner
).subscribe();

6.5 tap (Side Effects & Debugging)

Real-Life Scenario: Logging & State Side-effects without data mutation

💡 Why use it: Inspects values or triggers side effects without modifying data stream.
this.http.get('/api/offers').pipe(
  tap(() => this.showLoader = true),
  tap(data => console.log('Data:', data))
).subscribe();

7. Advanced JavaScript Array Concepts (6-7+ YOE Interview Questions)

Senior Interview Favorite

7.1 Custom Array Polyfills (Writing myMap & myReduce from Scratch)

Real-Life Scenario: Demonstrating deep understanding of JavaScript prototypes, callback execution, and this context binding.

// 1. Custom Array.prototype.myMap
Array.prototype.myMap = function (callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this) { // Handles sparse arrays correctly
      result.push(callback.call(thisArg, this[i], i, this));
    }
  }
  return result;
};

// 2. Custom Array.prototype.myReduce
Array.prototype.myReduce = function (callback, initialValue) {
  let accumulator = initialValue !== undefined ? initialValue : this[0];
  let startIndex = initialValue !== undefined ? 0 : 1;

  for (let i = startIndex; i < this.length; i++) {
    if (i in this) {
      accumulator = callback(accumulator, this[i], i, this);
    }
  }
  return accumulator;
};
6-7+ YOE Pattern

7.2 Grouping Flat Array Data into Lookup Objects (reduce & Object.groupBy)

Real-Life Scenario: Grouping an API response array of orders by status (e.g. { 'PENDING': [...], 'SHIPPED': [...] }).

const orders = [
  { id: 1, status: 'PENDING', amount: 100 },
  { id: 2, status: 'SHIPPED', amount: 200 },
  { id: 3, status: 'PENDING', amount: 150 }
];

// Traditional reduce grouping (Compatible across all browsers)
const groupedByStatus = orders.reduce((acc, order) => {
  acc[order.status] = acc[order.status] || [];
  acc[order.status].push(order);
  return acc;
}, {});

// Modern ES2024 Object.groupBy
const modernGrouped = Object.groupBy(orders, order => order.status);
ES2023 Non-Mutating Array Methods

7.3 Immutable Array Operations (toSorted, toSpliced, toReversed, with)

Real-Life Scenario: Updating component state in Angular Signals or Redux without mutating original array references.

const originalList = [3, 1, 4, 2];

const sortedList = originalList.toSorted(); // [1, 2, 3, 4]
const reversedList = originalList.toReversed(); // [2, 4, 1, 3]
const updatedItem = originalList.with(0, 99); // [99, 1, 4, 2]

console.log(originalList); // Still [3, 1, 4, 2] (Pure & Unchanged!)
Data Flattening

7.4 flat() & flatMap()

Real-Life Scenario: Extracting nested child arrays from API items in 1 line.

const customers = [
  { name: 'Alice', tags: ['VIP', 'Tech'] },
  { name: 'Bob', tags: ['Retail'] }
];

const allTags = customers.flatMap(c => c.tags); // ['VIP', 'Tech', 'Retail']

8. Advanced JavaScript Object & Memory Concepts (6-7+ YOE)

Reactivity Architecture

8.1 JavaScript Proxy & Reflect (How Framework Reactivity Works)

Real-Life Scenario: Building custom reactive stores or intercepting property reads/writes dynamically (Vue 3, MobX, Signals).

const userState = { name: 'Alice', age: 30 };

const reactiveUser = new Proxy(userState, {
  get(target, prop, receiver) {
    console.log(`READ: ${String(prop)}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`SET: ${String(prop)} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
});

reactiveUser.name = 'Bob'; // Triggers SET trap automatically!
Memory Management

8.2 Deep Copying Objects (structuredClone vs JSON.parse vs Spread)

Real-Life Scenario: Safely cloning nested state objects containing Dates, Maps, Sets, and Circular references.

const complexObject = {
  id: 101,
  date: new Date(),
  map: new Map([['key', 'value']])
};

// Native Deep Clone (Supports Date, Map, Set, Array, Objects):
const deepCopy = structuredClone(complexObject);
Memory Leak Prevention

8.3 WeakMap & WeakSet (Garbage Collection Safety)

Real-Life Scenario: Storing private metadata for DOM elements or component instances without causing memory leaks.

let domNode = document.createElement('div');
const nodeMetadata = new WeakMap();

nodeMetadata.set(domNode, { clickedTimes: 5 });

domNode = null; // domNode is Garbage Collected & WeakMap entry is automatically deleted!

9. Advanced JavaScript Function & Async Concepts (6-7+ YOE)

Live Coding Interview Question

9.1 Custom Debounce & Throttle Implementation from Scratch

Real-Life Scenario: Writing custom utility functions in interviews without third-party libraries (lodash).

// 1. Debounce implementation from scratch
function debounce(fn, delay) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 2. Throttle implementation from scratch
function throttle(fn, limit) {
  let inThrottle = false;
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}
Performance Optimization

9.2 Memoization Pattern (Function Result Caching)

Real-Life Scenario: Caching heavy computational results to prevent duplicate CPU calculations.

function memoize(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}
Functional Programming Deep Dive

9.3 Currying & Function Composition (In-Depth Masterclass)

PART A: Currying Masterclass

Currying is a functional programming technique where a function with multiple arguments f(a, b, c) is transformed into a chain of unary functions taking 1 argument at a time: f(a)(b)(c).

Coffee Machine Analogy:
1. selectCoffee('Espresso') -> returns a function waiting for size.
2. selectSize('Large') -> returns a function waiting for sugar level.
3. selectSugar('Medium') -> brews the customized coffee!

Why Currying is Useful in Real-Life Software Engineering: Enables Partial Application (pre-configuring reusable base logger modules, API request builders, or form validator functions).

// 1. ES6 Curried Logger Function
const curriedLog = level => moduleName => message => {
  console.log(`[${level}] [${moduleName}]: ${message}`);
};

const logError = curriedLog('ERROR');
const logOfferError = logError('OfferService');

logOfferError('Database timeout!'); // [ERROR] [OfferService]: Database timeout!
logOfferError('Invalid payload!');  // [ERROR] [OfferService]: Invalid payload!

// 2. Generic curry() Polyfill Function from Scratch (Senior Interview Live Coding Task)
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function (...nextArgs) {
      return curried.apply(this, args.concat(nextArgs));
    };
  };
}

function add(a, b, c) { return a + b + c; }
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6

PART B: Function Composition (pipe vs compose)

Function Composition is combining simple functions to create a complex pipeline where the output of one function becomes the input to the next: h(x) = f(g(x)).

🏭 Automobile Assembly Line Analogy:
Raw Frame -> Attach Engine -> Spray Paint -> Quality Check -> Finished Car!
Pattern Execution Direction How it Works
compose(f, g, h)(x) Right-to-Left ⬅️ Mathematical order: f(g(h(x)))
pipe(h, g, f)(x) Left-to-Right ➡️ Intuitive pipeline order: x -> h -> g -> f (Used in RxJS .pipe()!)
// Building pipe() and compose() utilities from scratch:
const pipe = (...fns) => (initialVal) =>
  fns.reduce((acc, fn) => fn(acc), initialVal);

const compose = (...fns) => (initialVal) =>
  fns.reduceRight((acc, fn) => fn(acc), initialVal);

const trimString = (str) => str.trim();
const toLowerCase = (str) => str.toLowerCase();
const wrapInSpan = (str) => `<span>${str}</span>`;

const formatText = pipe(
  trimString,
  toLowerCase,
  wrapInSpan
);

console.log(formatText('   Hello Senior Angular Engineer!   '));
// Outputs: "<span>hello senior angular engineer!</span>"

10. Angular Performance & Architecture (6-7+ YOE)

Angular Architecture Masterclass

10.1 Zone.js vs Zoneless Angular Architecture

Real-Life Scenario: Eliminating Zone.js dirty-checking bottlenecks for high-performance applications.

🐵 Zone.js (Traditional): Monkey-patches all browser async APIs (setTimeout, addEventListener, fetch). Every time any event fires anywhere, Zone.js marks the entire application tree for dirty-checking.
Zoneless Angular (Modern Angular 18+): Powered by Signals (provideExperimentalZonelessChangeDetection()). Components notify Angular directly when their signal state changes, eliminating global dirty checking!
// main.ts — Enabling Zoneless Angular (Angular 18+)
bootstrapApplication(AppComponent, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});
Change Detection Strategy

10.2 ChangeDetectionStrategy.OnPush & Manual Control

Real-Life Scenario: Optimizing large table lists or dashboard grids by skipping unchanged child component subtrees.

@Component({
  selector: 'app-user-row',
  template: `...`,
  changeDetection: ChangeDetectionStrategy.OnPush // 👈 Skips re-renders unless @Input reference changes or Signal updates!
})
export class UserRowComponent {
  private cdr = inject(ChangeDetectorRef);

  // markForCheck(): Marks component and parent ancestors for check during next change detection cycle.
  onExternalDataArrived() {
    this.cdr.markForCheck();
  }

  // detectChanges(): Forces synchronous change detection on THIS component and its children IMMEDIATELY.
  forceImmediateRender() {
    this.cdr.detectChanges();
  }
}
Modern Angular 17+

10.3 Control Flow @for & Deferrable Views (@defer)

Real-Life Scenario: Lazy-loading heavy charts/editor components only when they scroll into the user's viewport.

<!-- 1. Modern @for with mandatory DOM recycling track -->
@for (item of products(); track item.id) {
  <div>{{ item.name }}</div>
} @empty {
  <p>No products found</p>
}

<!-- 2. Deferrable Views: Heavy chart bundle is NOT downloaded until scrolled into view! -->
@defer (on viewport) {
  <app-heavy-chart [data]="salesData()" />
} @placeholder {
  <div class="skeleton">Scroll down to load sales chart...</div>
} @loading (minimum 500ms) {
  <mat-spinner />
}
Dependency Injection

10.4 DI Resolution Modifiers (@Optional, @Self, @SkipSelf, @Host)

Real-Life Scenario: Controlling how Angular navigates the ElementInjector tree when building reusable component libraries or dialogs.

Modifier Resolution Behavior Use Case
@Optional() Returns null instead of throwing error if dependency is missing. Optional logging or theme services
@Self() Looks ONLY at the current element's injector. Does not search parents. Enforcing element-local directive instances
@SkipSelf() Skips current element's injector and starts searching parent injectors. Avoiding self-referential tree dependencies
@Host() Searches up the injector tree until reaching the host component template border. Form control directives binding to host form container

11. Core JavaScript Engine, Memory & DOM Architecture (6-7+ YOE)

Memory Architecture

11.1 Garbage Collection & Mark-and-Sweep Algorithm

Real-Life Scenario: Diagnosing and preventing memory leaks in single-page Angular applications.

🧹 Mark-and-Sweep Algorithm:
1. Mark Phase: Garbage Collector starts at GC Roots (window object, active call stack, global variables) and marks all reachable objects.
2. Sweep Phase: Any object in memory that was NOT marked as reachable is unallocated (garbage collected).

Common Memory Leaks in Angular:
  • Unsubscribed RxJS Observables (e.g. interval(1000).subscribe() without takeUntilDestroyed).
  • Uncleared DOM Event Listeners (e.g. window.addEventListener('resize', ...) without removeEventListener).
  • Detached DOM Nodes (storing DOM element references in JS arrays after removing element from page).
DOM Event Phase

11.2 Event Bubbling, Capturing & Event Delegation

Real-Life Scenario: Efficiently handling clicks on dynamic lists with 10,000 table rows without attaching 10,000 separate event listeners.

// Event Propagation Order: Capturing Phase (Top -> Down) => Target Phase => Bubbling Phase (Bottom -> Up)

// Event Delegation Pattern: Attach 1 listener to parent container instead of 10,000 children!
const tableBody = document.querySelector('#table-body');

tableBody.addEventListener('click', (event) => {
  const targetCell = event.target.closest('.cell-action');
  if (targetCell) {
    const rowId = targetCell.dataset.id;
    console.log(`Clicked action for row ID: ${rowId}`);
  }
});
Deep Dive Masterclass

11.3 Generators & Iterators (function* & yield) Explained

Standard JavaScript functions run to completion once called. A Generator Function (function*) is a special function that can pause execution at any yield statement and resume later when .next() is invoked.

🎮 The Video Game Pause Button Analogy:
When JS executes a normal function, it plays all the way to the end. When JS encounters yield in a generator, it hits a Pause Button, saves its exact variable state and call stack, and yields a value out. When you call gen.next(), it hits Unpause and continues right where it left off!

1. Iterators & The Iteration Protocol

An Iterator is an object with a .next() method returning { value: any, done: boolean }. Any object implementing [Symbol.iterator]() is an Iterable (Arrays, Strings, Maps, Sets can be looped via for...of).

2. Basic Generator Code Example

function* numberGenerator() {
  console.log('Start');
  yield 10;
  console.log('Resumed #1');
  yield 20;
  console.log('Resumed #2');
  return 30;
}

const gen = numberGenerator();

console.log(gen.next()); // Logs 'Start' -> Returns { value: 10, done: false }
console.log(gen.next()); // Logs 'Resumed #1' -> Returns { value: 20, done: false }
console.log(gen.next()); // Logs 'Resumed #2' -> Returns { value: 30, done: true }

3. Two-Way Communication: Passing Values BACK into Generator via gen.next(val)

yield is a two-way street! You can yield a value OUT, and pass a value BACK IN when unpausing.

function* chatBot() {
  const name = yield 'What is your name?';
  console.log(`Hello, ${name}!`);
  const age = yield 'How old are you?';
  console.log(`You are ${age} years old.`);
}

const chat = chatBot();
console.log(chat.next().value);       // Yields: 'What is your name?'
console.log(chat.next('Alice').value); // Passes 'Alice' -> Yields: 'How old are you?'
chat.next(28);                         // Passes 28 -> Logs: 'You are 28 years old.'

4. Senior Real-Life Scenarios for Generators

Scenario How Generators Solve It
Infinite Sequence Generators Generates unique transaction IDs or Fibonacci numbers infinitely without storing millions of items in memory RAM.
Paginated Async Data Streams Using async function* and for await...of to fetch REST API pages on-demand chunk by chunk.
State Machine Control Flow Managing step-by-step wizard forms or redux-saga task orchestration.

5. Async Generator Example (Streaming REST API Pages)

async function* fetchPaginatedUsers() {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const res = await fetch(`/api/users?page=${page}`);
    const data = await res.json();
    yield data.items;

    hasMore = page < data.totalPages;
    page++;
  }
}

async function loadAllUsers() {
  for await (const userChunk of fetchPaginatedUsers()) {
    console.log('Loaded chunk of users:', userChunk);
  }
}

6. Creating Custom Iterables using Symbol.iterator

const team = {
  members: ['Alice', 'Bob', 'Charlie'],
  *[Symbol.iterator]() {
    for (const member of this.members) {
      yield member;
    }
  }
};

for (const person of team) {
  console.log(person);
}
console.log([...team]);

12. Custom RxJS Operators & Schedulers (6-7+ YOE)

Custom Operator Live Coding

12.1 Building a Custom Pipeable RxJS Operator from Scratch

Real-Life Scenario: Writing clean reusable custom RxJS operators (e.g., filtering out null/undefined values or auto-logging errors).

import { Observable } from 'rxjs';

export function filterNil<T>() {
  return (source: Observable<T | null | undefined>): Observable<T> => {
    return new Observable<T>((subscriber) => {
      return source.subscribe({
        next(value) {
          if (value !== null && value !== undefined) {
            subscriber.next(value);
          }
        },
        error(err) { subscriber.error(err); },
        complete() { subscriber.complete(); }
      });
    });
  };
}

this.user$.pipe(
  filterNil()
).subscribe(user => console.log(user.name));
RxJS Concurrency Control

12.2 RxJS Schedulers Architecture

Real-Life Scenario: Controlling thread execution queues (Microtask vs Macrotask vs Animation Frame) inside RxJS pipelines.

Scheduler Execution Queue Best Used For
queueScheduler Synchronous execution (Queue) Iterating over arrays synchronously without stack overflow
asapScheduler Microtask Queue (Promise resolution) Operations that should run immediately after current stack empties
asyncScheduler Macrotask Queue (setTimeout) Time-based operations like interval, delay, or debouncing
animationFrameScheduler requestAnimationFrame Smooth 60fps UI animations & canvas redraws

13. Advanced TypeScript Masterclass (6-7+ YOE Technical Interviews)

TypeScript Core Types

13.1 any vs unknown vs never vs void (Top Senior Interview Question)

Real-Life Scenario: Enforcing absolute type-safety across enterprise APIs and error handling handlers.

Type Type-Checking strictness Can be assigned to anything? Can call methods on it directly? Best Used For
any Disables type checking completely Yes Yes (Unsafe!) Legacy JS migration only (Avoid in production)
unknown Safe Top Type ✅ (Requires type narrowing before use) Yes No (Must check type first via if (typeof x === 'string')) API responses, dynamic JSON parsing, third-party input
never Bottom Type (Represents a value that NEVER occurs) No No Functions that throw errors, infinite loops, switch exhaustiveness checks
void Represents absence of return value No No Functions returning nothing
// 1. unknown requires narrowing:
function processApiInput(data: unknown) {
  if (typeof data === 'string') {
    console.log(data.toUpperCase()); // ✅ Safe! TypeScript knows it's a string!
  }
}

// 2. never for exhaustiveness checking in switch statements:
function assertNever(x: never): never {
  throw new Error(`Unexpected object: ${x}`);
}
Utility Types

13.2 Essential Built-in Utility Types Masterclass

Real-Life Scenario: Transforming existing interface DTOs into form models, partial updates, or read-only states without code duplication.

Utility Type Behavior Real-Life Code Example
Partial<T> Makes all properties optional (?) type UpdateUserDto = Partial<User>; (For PATCH requests)
Required<T> Makes all properties required type CompleteUser = Required<UserDraft>;
Readonly<T> Makes all properties immutable (readonly) type ConfigState = Readonly<AppConfig>;
Record<K, T> Constructs an object type with keys K and values T type UserMap = Record<number, User>; (Dictionary lookup)
Pick<T, K> Selects only specific keys K from T type UserSummary = Pick<User, 'id' | 'name'>;
Omit<T, K> Removes specific keys K from T type CreateUserPayload = Omit<User, 'id' | 'createdAt'>;
NonNullable<T> Excludes null and undefined from T type CleanString = NonNullable<string | null | undefined>;
ReturnType<T> Extracts return type of a function T type ApiResponse = ReturnType<typeof fetchUser>;
Generics & Constraints

13.3 Generics & keyof Constraints (generic <T extends Constraint>)

Real-Life Scenario: Writing strongly typed helper functions that enforce property key existence at compile time.

// Function guarantees 'key' MUST exist on object 'obj'!
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 101, username: 'Alice', role: 'Admin' };

const username = getProperty(user, 'username'); // ✅ Type inferred as string!
// const invalid = getProperty(user, 'email'); ❌ Compile Error: Argument of type '"email"' is not assignable to keyof user!
Type Guards

13.4 Custom User-Defined Type Guards (is Keyword)

Real-Life Scenario: Safely narrowing untyped API payloads into concrete TypeScript interfaces at runtime.

interface AdminUser {
  id: number;
  adminPermissions: string[];
}

// Custom Type Guard returning "user is AdminUser"
function isAdmin(user: any): user is AdminUser {
  return user && Array.isArray(user.adminPermissions);
}

function handleUser(user: any) {
  if (isAdmin(user)) {
    // TypeScript compiler automatically narrows 'user' to AdminUser inside this block!
    console.log(user.adminPermissions.join(', ')); // ✅ Autocomplete works!
  }
}
Discriminated Unions

13.5 Discriminated Unions & Exhaustiveness Checks

Real-Life Scenario: Managing complex UI state machines (Loading, Success, Error) with 100% type safety.

type ApiResponse<T> = 
  | { status: 'LOADING' }
  | { status: 'SUCCESS'; data: T }
  | { status: 'ERROR'; errorMessage: string };

function renderState<T>(state: ApiResponse<T>) {
  switch (state.status) {
    case 'LOADING':
      return 'Spinner...';
    case 'SUCCESS':
      return `Data: ${JSON.stringify(state.data)}`; // ✅ TypeScript knows state.data exists here!
    case 'ERROR':
      return `Error: ${state.errorMessage}`; // ✅ TypeScript knows state.errorMessage exists here!
  }
}
Senior / Architect Level

13.6 Conditional Types & infer Keyword

Real-Life Scenario: Unwrapping asynchronous Promises or Array types inside generic utility libraries.

// 1. Unwrapping Array element types using infer
type UnpackArray<T> = T extends (infer U)[] ? U : T;

type StringArray = string[];
type SingleString = UnpackArray<StringArray>; // Inferred as string

// 2. Unwrapping Promise return value using infer
type UnpackPromise<T> = T extends Promise<infer R> ? R : T;

type AsyncData = Promise<{ id: number; name: string }>;
type ResolvedData = UnpackPromise<AsyncData>; // Inferred as { id: number; name: string }
Template Literals

13.7 Mapped Types & Template Literal Types

Real-Life Scenario: Auto-generating getter method types or strongly-typed event strings dynamically.

// 1. Template Literal Types for Event Listeners
type EventType = 'click' | 'hover';
type ElementTarget = 'button' | 'link';

type EventName = `${EventType}_${ElementTarget}`; 
// Resulting Type: 'click_button' | 'click_link' | 'hover_button' | 'hover_link'

// 2. Mapped Type to auto-generate Getter signature types
type Person = { name: string; age: number };

type PersonGetters = {
  [K in keyof Person as `get${Capitalize<string & K>}`]: () => Person[K]
};
// Resulting Type: { getName: () => string; getAge: () => number }

Post a Comment

Previous Post Next Post