Skip to content

MEAN Stack Interview Questions for 5 Years Experience

40 questions with answers for candidates with 5 years of experience — 29 medium, 11 hard.

What interviewers expect: 5 years experience

At five years, interviews focus on architecture and leadership. You may design a large Angular application structure with lazy features, plan migrations to standalone components, signals or zoneless mode, add SSR, and design scalable Node.js APIs with caching and MongoDB scaling. Behavioural rounds cover mentoring, estimation, handling client escalations and balancing quality with delivery deadlines.

This set is weighted towards Angular Forms and HTTP Client (7), Node.js and Express APIs (7), MongoDB Data Modelling and Queries (6), Security, Testing and Deployment (6).

Frontend architecture Framework migrations Scalable APIs Mentoring and estimation
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

Your app must call different API base URLs for development, staging and production. How do you configure this in Angular?

Save to ▾
Show answer
Use environment files such as environment.ts and environment.prod.ts with fileReplacements in angular.json build configurations, or load a config.json at startup with provideAppInitializer so one build works in every environment. Expose the URL through an InjectionToken and inject it in services or an interceptor. Never put secrets in these files, because everything in the Angular bundle is public.
02

What are generics in TypeScript? Give an Angular-related example.

Save to ▾
Show answer
Generics let you write reusable code that works with many types while keeping type safety, using type parameters like T. For example, an ApiResponse<T> interface can wrap any payload, and a service method get<T>(url: string): Observable<T> returns correctly typed data. Angular's HttpClient.get<User[]>() uses generics so the component knows it receives an array of users, without casting.
03

Write a custom attribute directive that highlights an element on hover.

Save to ▾
Show answer
Host listeners handle the events. import { Directive, ElementRef, HostListener, inject, input } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { private el = inject(ElementRef); color = input('yellow', { alias: 'appHighlight' }); @HostListener('mouseenter') onEnter() { this.el.nativeElement.style.backgroundColor = this.color(); } @HostListener('mouseleave') onLeave() { this.el.nativeElement.style.backgroundColor = ''; } } // usage: <p appHighlight='lightblue'>Hover me</p>
04

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.
05

After a new release, a bug affects many users. How do you respond and prevent a repeat?

Save to ▾
Show answer
First limit the impact: roll back to the previous version or disable the feature with a flag, and communicate status to support. Then reproduce the bug, fix it with a test that covers the case, and redeploy through the normal pipeline. Afterwards, run a blameless post-mortem to find gaps, such as missing tests, review or monitoring, and add the actions to the backlog.
06

Write a generic TypeScript function that groups an array of items by a key.

Save to ▾
Show answer
Generics keep the result strongly typed. function groupBy<T, K extends keyof T>(items: T[], key: K): Record<string, T[]> { return items.reduce((acc, item) => { const group = String(item[key]); (acc[group] ||= []).push(item); return acc; }, {} as Record<string, T[]>); } interface Order { id: number; status: string } const byStatus = groupBy<Order, 'status'>(orders, 'status'); K extends keyof T ensures only real property names are accepted.
07

How would you convince your team to adopt signals in an existing RxJS-heavy codebase?

Save to ▾
Show answer
Situation: our team was hesitant to change working RxJS patterns. Task: I wanted to reduce subscription bugs and prepare for zoneless mode. Action: I built a proof of concept converting one complex component, measured fewer lines and change detection cycles, and proposed a gradual plan: signals for component state, RxJS kept for streams, toSignal at boundaries. Result: the team agreed to use signals in all new features.
08

Tell me about a time you improved test coverage or quality in a project.

Save to ▾
Show answer
Situation: our MEAN project had almost no tests and every release broke something. Task: I volunteered to improve this. Action: I set up Supertest with an in-memory MongoDB for critical APIs, added component tests for checkout, wrote a short testing guide and made tests required in CI. I started with the five most bug-prone areas. Result: production bugs per release dropped sharply within three months.
09

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()}`); }
10

Users complain that the app shows a blank screen whenever the backend is down. How do you improve this?

Save to ▾
Show answer
Add a global error interceptor that maps network and 5xx errors to a friendly toast or banner, with retry for transient failures. In each data component, handle loading, error and empty states explicitly, using httpResource status signals or catchError with fallback values. Show a retry button, keep cached data visible if available, and log errors to monitoring. A health check banner helps during outages.
11

How does $lookup work in an aggregation pipeline?

Save to ▾
Show answer
$lookup performs a left outer join with another collection in the same database. The basic form matches localField to foreignField and writes matching documents into an array field. The pipeline form lets you add conditions, filtering and projections on the joined collection using let variables. Often you follow it with $unwind to flatten the array. Index the foreign field so the join stays efficient.
12

What are HTTP interceptors and what are common use cases?

Save to ▾
Show answer
Interceptors sit between HttpClient and the server and can modify every request and response. Modern Angular uses functional interceptors of type HttpInterceptorFn registered through withInterceptors. Common uses are attaching an Authorization header, adding a base URL, showing a global loading indicator, logging, retrying failed requests, refreshing expired tokens and mapping errors to user-friendly messages. Interceptors run in the order they are listed.
13

Serve the built Angular app and the API from one Express server.

Save to ▾
Show answer
API first, static files next, fallback last. import express from 'express'; import path from 'node:path'; const app = express(); app.use(express.json()); app.use('/api', apiRouter); const dist = path.join(process.cwd(), 'dist/shop/browser'); app.use(express.static(dist, { maxAge: '1y', index: false })); app.get('/*splat', (req, res) => { res.sendFile(path.join(dist, 'index.html')); }); app.listen(process.env.PORT || 3000); The named wildcard follows Express 5 path syntax.
14

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.
15

Write a type-ahead search using a reactive FormControl and RxJS operators.

Save to ▾
Show answer
switchMap cancels stale requests. export class SearchComponent { private api = inject(ProductService); search = new FormControl('', { nonNullable: true }); results = toSignal( this.search.valueChanges.pipe( debounceTime(300), map((t) => t.trim()), distinctUntilChanged(), filter((t) => t.length >= 2), switchMap((t) => this.api.search(t).pipe(catchError(() => of([])))) ), { initialValue: [] } ); } // template: <input [formControl]='search'> @for (p of results(); track p._id) {...}
16

How do you serve an Angular build and an Express API from the same Node server?

Save to ▾
Show answer
Build Angular with ng build, which outputs static files to dist/project/browser. In Express, register API routes under /api first, then serve the static files with express.static, and finally add a catch-all GET route that returns index.html so Angular routing works on refresh. In larger deployments, Nginx or a CDN usually serves the Angular files while Express handles only the API.
17

What is Angular SSR and why would you enable it?

Save to ▾
Show answer
Angular server-side rendering renders pages to HTML on a Node.js server, so users and search engines see content immediately. The browser then hydrates the page, attaching Angular to the existing DOM; incremental hydration can hydrate parts only when needed using @defer triggers. Route-level render modes allow some pages to be prerendered at build time and others rendered per request. It improves SEO and first load for public pages.
18

Write an exhaustive switch over a union type using never.

Save to ▾
Show answer
The never check makes the compiler flag unhandled cases. type Shape = { kind: 'circle'; r: number } | { kind: 'square'; side: number }; function area(s: Shape): number { switch (s.kind) { case 'circle': return Math.PI * s.r ** 2; case 'square': return s.side ** 2; default: { const unreachable: never = s; throw new Error(`Unknown shape ${unreachable}`); } } } Adding a triangle variant now causes a compile error until it is handled.
19

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.
20

Write a zod validation middleware for an Express route in TypeScript.

Save to ▾
Show answer
Reusable middleware with typed output. import { z, ZodSchema } from 'zod'; import { RequestHandler } from 'express'; export const validateBody = (schema: ZodSchema): RequestHandler => (req, res, next) => { const parsed = schema.safeParse(req.body); if (!parsed.success) { return res.status(400).json({ errors: parsed.error.flatten().fieldErrors }); } req.body = parsed.data; next(); }; const createOrder = z.object({ items: z.array(z.object({ productId: z.string(), qty: z.number().int().min(1) })).min(1), address: z.string().min(10) }); router.post('/orders', validateBody(createOrder), ordersController.create);
21

Tell me about a complex form you built and the challenges you solved.

Save to ▾
Show answer
Situation: I built a loan application form with five steps and over sixty fields. Task: make it reliable and user friendly. Action: I used typed reactive forms split into step groups, cross-field validators for income rules, a FormArray for co-applicants, auto-saved drafts every thirty seconds and mapped server validation errors back to fields. Result: drop-offs reduced noticeably and QA found very few form bugs.
22

Write a multi-stage Dockerfile that builds Angular and serves it with Nginx.

Save to ▾
Show answer
Build in Node, ship only static files. FROM node:22-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npx ng build --configuration production FROM nginx:alpine COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist/shop/browser /usr/share/nginx/html EXPOSE 80 The final image contains no Node.js or source code. nginx.conf should include try_files $uri /index.html for Angular routes.
23

Define a typed Mongoose model in TypeScript with an instance method.

Save to ▾
Show answer
Interface, methods type and schema together. import { Schema, model, Model } from 'mongoose'; import bcrypt from 'bcrypt'; interface IUser { name: string; email: string; password: string; role: 'user' | 'admin' } interface IUserMethods { comparePassword(plain: string): Promise<boolean> } type UserModel = Model<IUser, {}, IUserMethods>; const userSchema = new Schema<IUser, UserModel, IUserMethods>({ name: { type: String, required: true }, email: { type: String, required: true, unique: true, lowercase: true }, password: { type: String, required: true, select: false }, role: { type: String, enum: ['user', 'admin'], default: 'user' } }, { timestamps: true }); userSchema.method('comparePassword', function (plain: string) { return bcrypt.compare(plain, this.password); }); export const User = model<IUser, UserModel>('User', userSchema);
24

Your Express API is used by thousands of users and occasionally crashes due to an unhandled promise rejection. How do you make it more resilient?

Save to ▾
Show answer
Find the root cause from logs and fix missing await or catch blocks. Use Express 5, which forwards async errors to error middleware, or an async wrapper in Express 4. Add a central error handler, log unhandledRejection and uncaughtException and then exit cleanly. Run under PM2 or a container orchestrator with multiple instances and health checks so a crash restarts one instance while others keep serving.
25

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>
26

An order form lets users add any number of items with product, quantity and price. How do you build it?

Save to ▾
Show answer
Use a FormArray of FormGroups, each with product, quantity and price controls and validators like min(1). Provide addItem and removeItem methods that push or removeAt on the array, and render rows with @for over items.controls. Calculate the total from valueChanges or a computed signal. On submit, send getRawValue() typed as the order DTO, and disable the submit button while invalid or pending.
27

What are Mongoose virtuals, instance methods and statics?

Save to ▾
Show answer
Virtuals are computed properties not stored in MongoDB, such as fullName from firstName and lastName, or a virtual populate that links related documents. Instance methods are functions available on each document, like user.comparePassword(). Statics are functions on the model itself, like User.findByEmail(email). Virtuals are excluded from JSON output unless you enable toJSON: { virtuals: true } in schema options.
28

Describe how you handled a situation where requirements changed late in a sprint.

Save to ▾
Show answer
Situation: two days before release, the client asked to add role-based access to a reporting page. Task: deliver without breaking the release. Action: I clarified the minimum needed with the product owner, implemented a canMatch guard and hid admin actions, and coordinated with the backend developer for API checks, while moving extra filters to the next sprint. Result: we released on time and the client approved the change.
29

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.
30

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.
31

A penetration test report says your API returns other users' invoices when the invoice id is changed. How do you fix it?

Save to ▾
Show answer
This is broken object-level authorization, the top API risk in the OWASP list. Fix every query to scope by the authenticated user, for example Invoice.findOne({ _id: id, userId: req.user.id }), and return 404 when not found. Centralise ownership checks in services or middleware, add tests that try cross-user access, and review similar endpoints. Avoid relying on unguessable ids as protection.
32

Load three APIs in parallel with forkJoin and handle a failure in one of them.

Save to ▾
Show answer
Each request gets its own fallback. import { forkJoin, of, catchError } from 'rxjs'; loadDashboard() { return forkJoin({ sales: this.api.getSales().pipe(catchError(() => of(null))), orders: this.api.getRecentOrders().pipe(catchError(() => of([]))), users: this.api.getUserStats().pipe(catchError(() => of(null))) }); } dashboard = toSignal(this.loadDashboard()); // template: @if (dashboard(); as d) { ... d.sales ... } forkJoin emits once, after every inner Observable completes.
33

What are injection tokens and when do you need them?

Save to ▾
Show answer
An InjectionToken is a unique key for providing values that are not classes, such as configuration objects, API base URLs or feature flags. You create it with new InjectionToken<AppConfig>('APP_CONFIG'), provide it with { provide: APP_CONFIG, useValue: config }, and read it with inject(APP_CONFIG). Other provider types include useClass to swap implementations, useFactory for computed values and useExisting for aliases.
34

An npm audit shows several high-severity vulnerabilities in the Express project. How do you handle it?

Save to ▾
Show answer
Read each advisory to see whether the vulnerable code path is actually used in production or only in dev tooling. Run npm audit fix for safe updates, upgrade direct dependencies that pull vulnerable transitive packages, and use overrides in package.json if a fix is not yet released upstream. Test thoroughly after upgrading, enable Dependabot or Renovate for continuous updates and track any accepted risks.
35

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.
36

What are typed reactive forms?

Save to ▾
Show answer
Since Angular 14, reactive forms are strongly typed. A FormControl<string> knows its value type, so form.value and getRawValue return typed objects and the compiler catches wrong field names. Controls are nullable by default because reset sets them to null; using nonNullable: true or NonNullableFormBuilder resets to the initial value instead. Typed forms make it safe to map form values directly to API request interfaces.
37

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.
38

Write a functional auth guard that redirects unauthenticated users to the login page.

Save to ▾
Show answer
Return a UrlTree to redirect. import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; import { AuthService } from './auth.service'; export const authGuard: CanActivateFn = (route, state) => { const auth = inject(AuthService); const router = inject(Router); return auth.isLoggedIn() ? true : router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } }); }; // { path: 'orders', canActivate: [authGuard], loadComponent: ... }
39

A list API that uses Mongoose populate on three references has become slow. How would you improve it?

Save to ▾
Show answer
Check how many queries populate triggers and how much data each returns. Select only needed fields in populate, use lean(), and add indexes on the referenced fields. If the list always needs a customer name, store it with the extended reference pattern to avoid the join entirely. For complex filtering on related data, switch to one aggregation with $lookup and $project, and paginate.
40

What are @defer blocks and how do they help performance?

Save to ▾
Show answer
@defer lets you lazy load part of a template and its dependencies only when a trigger occurs, such as on viewport, on interaction, on hover, on idle, on timer or a custom condition with when. Angular automatically splits that code into a separate chunk. You can show @placeholder, @loading and @error blocks. It is ideal for heavy below-the-fold widgets like charts, comments and maps.

Frequently asked questions

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

At five years, interviews focus on architecture and leadership. You may design a large Angular application structure with lazy features, plan migrations to standalone components, signals or zoneless mode, add SSR, and design scalable Node.js APIs with caching and MongoDB scaling. Behavioural rounds cover mentoring, estimation, handling client escalations and balancing quality with delivery deadlines.

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 5 years of experience?

Focus first on Frontend architecture, Framework migrations, Scalable APIs, Mentoring and estimation.