Skip to content

MEAN Stack Interview Questions for 10 Years Experience

40 questions with answers for candidates with 10+ years of experience — 7 medium, 33 hard.

What interviewers expect: 10 years experience

At ten years and above, roles include principal engineer, architect, delivery head or engineering manager. Interviews cover enterprise-wide technology strategy, modernisation of legacy systems, platform choices, hiring and growing teams, managing multiple projects and major incidents. Technical depth remains important, but the focus is on business outcomes, risk management and long-term maintainability of products.

This set is weighted towards Node.js and Express APIs (8), Angular Forms and HTTP Client (6), MongoDB Data Modelling and Queries (6), RxJS, Signals and State Management (6).

Technology strategy Legacy modernisation Team leadership Business outcomes
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

A form must show fields based on earlier answers, such as company name only when employment type is salaried. How would you build it?

Save to ▾
Show answer
Subscribe to the employment type control's valueChanges, or use a signal, and enable or disable the dependent controls accordingly; disabled controls are excluded from value and validation. Show the fields in the template with @if. For forms driven by configuration, build controls dynamically from a JSON schema into a FormGroup. Remember to clear stale values when a field is hidden.
02

Write mongosh commands to inspect a slow query and the indexes on a collection.

Save to ▾
Show answer
Useful diagnostic commands: db.orders.getIndexes() db.orders.find({ status: 'pending', city: 'Pune' }) .sort({ createdAt: -1 }) .explain('executionStats') db.setProfilingLevel(1, { slowms: 100 }) db.system.profile.find().sort({ ts: -1 }).limit(5) db.orders.createIndex({ status: 1, city: 1, createdAt: -1 }) Compare totalDocsExamined with nReturned before and after creating the index.
03

Your API needs to send welcome emails and SMS after signup, but this makes signup slow. What would you change?

Save to ▾
Show answer
Do not make the user wait for third-party providers. After saving the user, publish an event or add jobs to a queue and return the response immediately. Workers send the email and SMS with retries and backoff, and failures are logged without affecting signup. An EventEmitter is enough for a quick in-process version, but a persistent queue survives restarts and scales across instances.
04

Write a Socket.IO server event and an Angular service that listens for it.

Save to ▾
Show answer
Server side: io.on('connection', (socket) => { socket.on('join', (orderId) => socket.join(`order:${orderId}`)); }); // when status changes io.to(`order:${order.id}`).emit('status', { id: order.id, status: order.status }); Angular service: @Injectable({ providedIn: 'root' }) export class OrderSocketService { private socket = io(environment.wsUrl); watch(orderId: string) { this.socket.emit('join', orderId); return new Observable<OrderStatus>((sub) => { this.socket.on('status', (s) => sub.next(s)); return () => this.socket.off('status'); }); } }
05

Write a GitHub Actions job that tests and builds the Angular app.

Save to ▾
Show answer
Typical CI for the frontend: name: web-ci on: [pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - run: npx ng lint - run: npx ng test --watch=false - run: npx ng build --configuration production - uses: actions/upload-artifact@v4 with: name: web-dist path: dist/ The artifact can be deployed by a later job after approval.
06

Write an async validator that checks whether an email is already registered.

Save to ▾
Show answer
Return an Observable and debounce the server call. export function emailTaken(api: UserService): AsyncValidatorFn { return (control: AbstractControl) => timer(400).pipe( switchMap(() => api.emailExists(control.value)), map((exists) => (exists ? { emailTaken: true } : null)), catchError(() => of(null)) ); } email = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email], asyncValidators: [emailTaken(inject(UserService))], updateOn: 'blur' }); The control is PENDING while the check runs.
07

Describe a time you had to optimise a slow backend under pressure.

Save to ▾
Show answer
Situation: during a sales campaign our order API latency jumped to five seconds. Task: fix it while traffic was high. Action: I checked metrics, found a report query running on every order, moved it to a cached summary updated every minute, and added a missing index. I communicated progress to the support team every thirty minutes. Result: latency returned below 300 ms within two hours and the campaign continued smoothly.
08

What are discriminated unions and why are they useful?

Save to ▾
Show answer
A discriminated union is a union of object types that share a literal property, the tag, such as type Result = { status: 'ok'; data: User } | { status: 'error'; message: string }. Checking result.status narrows the type, so TypeScript knows which fields exist in each branch. Combined with a never check in the default case, the compiler warns you when a new variant is added but not handled.
09

Admin routes are protected by a guard, but the admin feature code is still downloaded by every user. How do you fix this?

Save to ▾
Show answer
canActivate does not stop the route from matching, so its lazy chunk can still be downloaded, for example by a preloading strategy. Use a canMatch guard on the lazy admin route instead, so the route definition does not match for non-admin users and its code is never loaded; they fall through to another route or a not-authorised page. Keep the real security on the API, since frontend code is always visible.
10

Explain the any, unknown and never types in TypeScript.

Save to ▾
Show answer
any switches off type checking, so any operation is allowed and errors move to runtime. unknown also accepts any value but forces you to narrow it with typeof, instanceof or a type guard before using it, making it the safe choice for parsed JSON or caught errors. never represents values that never occur, such as a function that always throws, and is used in exhaustive switch checks.
11

Use a MongoDB transaction in Mongoose to place an order and reduce stock together.

Save to ▾
Show answer
withTransaction retries on transient errors. const session = await mongoose.startSession(); try { await session.withTransaction(async () => { const res = await Product.updateOne( { _id: productId, stock: { $gte: qty } }, { $inc: { stock: -qty } }, { session } ); if (res.modifiedCount === 0) throw new Error('Out of stock'); await Order.create([{ userId, productId, qty }], { session }); }); } finally { await session.endSession(); } Transactions require a replica set, which Atlas provides by default.
12

What is CSRF and how do Angular and Express handle it?

Save to ▾
Show answer
Cross-site request forgery abuses cookies the browser sends automatically to make unwanted requests. Angular's HttpClient supports the double-submit cookie pattern: it reads a token from a cookie named XSRF-TOKEN and sends it in the X-XSRF-TOKEN header on mutating requests, which the server verifies. Combined with SameSite cookies and not using GET for changes, this protects cookie-based auth. Header-based JWTs are not vulnerable in the same way.
13

The security team asks you to move JWTs out of localStorage in the Angular app. What design would you propose?

Save to ▾
Show answer
Issue a short-lived access token kept only in memory in an Angular auth service, and a refresh token in an httpOnly, Secure, SameSite cookie scoped to the refresh path. On page load, call the refresh endpoint to get a new access token. An interceptor attaches the access token and refreshes on 401. Rotate refresh tokens and store their hashes server-side so they can be revoked.
14

How do you decide where state should live in a large Angular application?

Save to ▾
Show answer
Situation: our team argued over putting everything in a global store. Task: as tech lead I had to set guidelines. Action: I proposed rules: local UI state in components with signals, feature state in feature-scoped services or SignalStore, truly global data like the user and settings in root services, and server data cached in services. I documented examples and reviewed the first PRs. Result: fewer bugs and simpler components.
15

What are change streams in MongoDB and how can a MEAN app use them?

Save to ▾
Show answer
Change streams let an application subscribe to real-time inserts, updates and deletes on a collection, database or deployment, using the oplog of a replica set. A Node.js service can watch the orders collection and push status changes to Angular clients over Socket.IO, or sync data to search indexes and caches. Streams are resumable using a resume token after a restart.
16

Use httpResource to load a product whenever the selected id signal changes.

Save to ▾
Show answer
The request reruns automatically when the id changes. import { Component, input } from '@angular/core'; import { httpResource } from '@angular/common/http'; @Component({ selector: 'app-product-detail', template: ` @if (product.isLoading()) { <p>Loading...</p> } @else if (product.error()) { <p>Could not load product</p> } @else if (product.value(); as p) { <h2>{{ p.name }}</h2> } ` }) export class ProductDetailComponent { id = input.required<string>(); product = httpResource<Product>(() => `/api/products/${this.id()}`); }
17

What are read concern and write concern in MongoDB?

Save to ▾
Show answer
Write concern controls how many replica set members must acknowledge a write before it is considered successful; w: 'majority' protects against losing data on failover, while w: 1 is faster but riskier. Read concern controls the consistency of data read, such as local for the latest on one node or majority for data acknowledged by most members. Choose stronger settings for payments and orders.
18

Every API request needs a JWT header, and expired tokens should trigger a refresh without logging the user out. How do you implement this?

Save to ▾
Show answer
Write an auth interceptor that clones each request with the Authorization header from the auth service. In the response path, catch 401 errors, call the refresh endpoint once, store the new access token, and retry the original request. Use a shared refresh Observable with shareReplay or a flag so parallel failed requests wait for one refresh. If refresh fails, clear the session and redirect to login.
19

You see ExpressionChangedAfterItHasBeenCheckedError in development. What does it mean and how do you fix it?

Save to ▾
Show answer
In development Angular runs an extra check to make sure a binding did not change after it was already checked in the same cycle. The error usually appears when a child or a lifecycle hook like ngAfterViewInit changes parent data during rendering. Fix it by moving the update earlier, such as into ngOnInit, deriving the value with a computed signal, or restructuring data flow, rather than hiding it with setTimeout.
20

What are async validators and when do you use them?

Save to ▾
Show answer
Async validators return an Observable or Promise of validation errors, used when validity needs a server check, such as whether a username or email is already taken. Angular runs them only after synchronous validators pass, and the control's status is PENDING meanwhile. Use updateOn: 'blur' or debounce inside the validator to avoid a request on every keystroke, and still enforce uniqueness on the backend.
21

A mobile team and the Angular team both consume your API, and you need to change a response field. How do you avoid breaking clients?

Save to ▾
Show answer
Make additive changes first: add the new field alongside the old one, mark the old field as deprecated in the OpenAPI docs and inform both teams. Track usage, and remove the old field only after all clients have migrated. For larger breaking changes, introduce a new version such as /api/v2 and run both versions in parallel. Contract tests in CI catch accidental breaking changes.
22

Write a BullMQ queue and worker to send emails in the background.

Save to ▾
Show answer
Producer in the API, worker in a separate process. import { Queue, Worker } from 'bullmq'; const connection = { host: process.env.REDIS_HOST, port: 6379 }; export const emailQueue = new Queue('emails', { connection }); // in the signup controller await emailQueue.add('welcome', { to: user.email }, { attempts: 3, backoff: { type: 'exponential', delay: 2000 } }); // worker.ts new Worker('emails', async (job) => { await mailer.send(job.data.to, 'Welcome!', templates.welcome()); }, { connection }); Failed jobs retry automatically with backoff.
23

Write a role-based canMatch guard that only matches admin users.

Save to ▾
Show answer
Guard factories keep it reusable. import { inject } from '@angular/core'; import { CanMatchFn, Router } from '@angular/router'; export const roleGuard = (role: string): CanMatchFn => () => { const auth = inject(AuthService); const router = inject(Router); return auth.user()?.role === role ? true : router.createUrlTree(['/forbidden']); }; export const routes = [ { path: 'admin', canMatch: [roleGuard('admin')], loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES) } ];
24

A shared service exposes state through a BehaviorSubject, and components mutate the emitted object directly, causing bugs. How do you redesign it?

Save to ▾
Show answer
Keep the writable state private and expose only a read-only view: asObservable() for a subject, or asReadonly() for a signal. Provide methods like addItem and removeItem that create new immutable objects with spread syntax and emit them. Immutable updates also work with OnPush change detection. Consider migrating the service to signals with computed selectors, or NgRx SignalStore for larger state.
25

Wallet balance updates sometimes produce wrong totals when two purchases happen at the same time. How do you fix this?

Save to ▾
Show answer
Reading the balance, changing it in code and saving it creates a lost-update race. Use an atomic update instead: updateOne({ _id: userId, balance: { $gte: amount } }, { $inc: { balance: -amount } }) and check modifiedCount to confirm funds were sufficient. If the operation also writes a transaction record, wrap both in a multi-document transaction. Optimistic concurrency with a version field is another option.
26

What is NgRx and when is it worth using in an Angular application?

Save to ▾
Show answer
NgRx is a state management library family for Angular. NgRx Store follows the Redux pattern with actions, reducers, selectors and effects for side effects, giving predictable state and powerful DevTools. NgRx SignalStore offers a lighter, signal-based way to build stores with state, computed values and methods. It is worth it for large apps with complex shared state and many developers; small apps do fine with services and signals.
27

Your MongoDB collection of sensor readings is growing by millions of documents per day. How would you design for it?

Save to ▾
Show answer
Use a time series collection, which stores readings efficiently in compressed buckets with a timeField and metaField such as deviceId. Set expireAfterSeconds to drop old raw data, and keep hourly or daily aggregates in a separate collection for dashboards. Index on metaField and time, write in batches with insertMany, and plan sharding by device or a hashed key if a single replica set cannot keep up.
28

What is hierarchical dependency injection in Angular?

Save to ▾
Show answer
Angular has a tree of injectors that mirrors the application. Environment injectors come from the root and lazy routes, and element injectors come from components and directives. When a dependency is requested, Angular searches upward from the requesting element to the root. Providing a service at component level creates an isolated instance for that subtree. Modifiers like optional, self, skipSelf and host control this lookup.
29

Build a dynamic FormArray for adding and removing phone numbers.

Save to ▾
Show answer
Expose the array and helper methods. export class ContactFormComponent { private fb = inject(NonNullableFormBuilder); form = this.fb.group({ name: ['', Validators.required], phones: this.fb.array([this.fb.control('', Validators.pattern(/^\d{10}$/))]) }); get phones() { return this.form.controls.phones; } addPhone() { this.phones.push(this.fb.control('', Validators.pattern(/^\d{10}$/))); } removePhone(i: number) { this.phones.removeAt(i); } } // <div formArrayName='phones'> // @for (c of phones.controls; track $index) { // <input [formControlName]='$index'> <button type='button' (click)='removePhone($index)'>x</button> // } // </div>
30

A dashboard needs data from three independent APIs before it can render. How would you load them?

Save to ▾
Show answer
Use forkJoin to run all three requests in parallel and emit once when all complete, giving an object or array of results. Handle errors per request with catchError returning a fallback value, so one failed widget does not block the whole dashboard. If any source emits repeatedly, use combineLatest instead. With signals, you could use three httpResource instances and a computed for the combined loading state.
31

How do you design an API endpoint that returns a dashboard summary quickly when the underlying data is large?

Save to ▾
Show answer
Avoid heavy aggregation on every request. Precompute summaries with scheduled jobs or on write events and store them in a summary collection, cache the response in Redis with a short TTL, and return only the fields the dashboard needs. Use indexes for any live queries, add pagination for detail lists, and expose a separate endpoint for drill-down data so the first load stays fast.
32

Your Angular unit tests take fifteen minutes in CI and developers stop running them. What would you do?

Save to ▾
Show answer
Measure which suites are slow. Migrate from Karma to Vitest for faster runs, avoid importing heavy shared modules in TestBed, mock HTTP and large child components, and replace real timers with fake ones. Run only affected tests on pull requests using Nx affected or similar, parallelise jobs in CI and cache dependencies. Move slow browser flows to a focused E2E suite run separately.
33

What are Signal Forms in Angular?

Save to ▾
Show answer
Signal Forms are a newer, experimental forms API introduced in recent Angular versions that builds the form model on signals. You create a form from a signal holding the data model, and field values, validity and errors are all exposed as signals, with schema-based validation rules. They aim to reduce boilerplate and fit zoneless, signal-based apps. Reactive forms remain the stable choice for production projects today.
34

What is the difference between a monolith and microservices for a Node.js backend?

Save to ▾
Show answer
A monolith is one deployable API containing all modules, which is simpler to build, test and deploy, and is right for most early-stage products. Microservices split domains like orders, payments and notifications into separate services communicating over HTTP or message queues, allowing independent scaling and deployments, but adding network failures, distributed data and operational complexity. Many teams start with a modular monolith and split later.
35

A third-party chart component inside an Angular page causes performance issues because it triggers too many change detection cycles. What can you do?

Save to ▾
Show answer
In Zone.js apps, run the chart's frequent events outside Angular using NgZone.runOutsideAngular, then re-enter only when the UI must update. Make the host component OnPush, pass immutable inputs and use @defer so the chart loads only when visible. Moving to zoneless change detection solves the root cause, because third-party timers no longer trigger global checks.
36

Frontend and backend models keep drifting apart, causing runtime bugs. How would you keep them in sync in a MEAN project?

Save to ▾
Show answer
Share types from a single source. In a monorepo, for example with Nx, put DTO interfaces in a shared library used by both the Express API and Angular. Alternatively generate an OpenAPI spec from the backend and generate Angular client types from it, or define schemas once with zod and infer types. Add contract tests in CI so a breaking change fails the build.
37

Your Angular app's initial bundle is very large and the first page loads slowly. What steps would you take?

Save to ▾
Show answer
Run a production build with source map analysis to find large dependencies. Lazy load feature routes with loadComponent or loadChildren, use @defer for heavy widgets, remove unused libraries and replace large ones, and avoid importing whole utility libraries. Check budgets in angular.json, enable server-side rendering with hydration for public pages, optimise images with NgOptimizedImage, and serve compressed assets through a CDN.
38

What are the resource and httpResource APIs in Angular?

Save to ▾
Show answer
resource and httpResource bring asynchronous data into the signal world. You give them reactive parameters, such as a signal holding a product id, and they fetch data whenever those parameters change, cancelling stale requests. They expose signals like value, isLoading, error and status, and provide reload. httpResource builds on HttpClient, so interceptors still apply. They reduce manual subscribe-and-set code in components.
39

You need to poll an order status API every five seconds until the order is delivered. How would you implement it?

Save to ▾
Show answer
Use timer(0, 5000) piped into switchMap to call the status API, then takeWhile(status => status !== 'delivered', true) to stop after the final status, including it. Add takeUntilDestroyed so polling stops if the user leaves, and retry with a delay for transient errors. For long waits, server-sent events or websockets are more efficient than polling, which wastes requests.
40

How do real-time features work in a MEAN application?

Save to ▾
Show answer
Use WebSockets, commonly through Socket.IO on the Express server and its client library in an Angular service that exposes events as Observables or signals. The server emits events, such as new chat messages or order status changes, to rooms of interested users. For scaling across instances, add a Redis adapter. Server-sent events are a simpler option for one-way updates like notifications or live dashboards.

Frequently asked questions

What is asked in MEAN Stack interviews for 10+ years of experience?

At ten years and above, roles include principal engineer, architect, delivery head or engineering manager. Interviews cover enterprise-wide technology strategy, modernisation of legacy systems, platform choices, hiring and growing teams, managing multiple projects and major incidents. Technical depth remains important, but the focus is on business outcomes, risk management and long-term maintainability of products.

How many questions should I practise before a MEAN Stack interview?

Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.

Which topics matter most for 10+ years of experience?

Focus first on Technology strategy, Legacy modernisation, Team leadership, Business outcomes.