Thursday, April 2, 2026

Top 50 JavaScript & Node.js Interview Questions and Answers – Beginner to Advanced

Top 50 JavaScript & Node.js Interview Questions and Answers – Beginner to Advanced

📅 Published: April 2026  |  ⏱ Reading Time: ~20 minutes  |  🏷️ JavaScriptNode.jsInterviewExpress.jsWeb Development

📌 TL;DR: This article covers the 50 most asked JavaScript and Node.js interview questions for 2026 — from core JS concepts like closures, the event loop, and prototypes, to Node.js specifics like streams, the module system, Express.js middleware, and REST API design. Every question includes a clear answer and a working code example. Perfect for developers preparing for frontend, backend, or full-stack JavaScript roles.

Introduction

JavaScript is the world's most widely used programming language, and Node.js has made it a first-class citizen on the server side. Whether you are applying for a frontend, backend, or full-stack role, JavaScript interview questions are unavoidable — and Node.js questions are increasingly common even for frontend positions.

This guide walks through 50 carefully selected questions grouped by topic, each with a thorough explanation and real code examples you can run yourself. Difficulty labels help you gauge what level each question targets, and the tips section at the end tells you exactly what interviewers are looking for beyond the textbook answer.

💡 How to use this guide: Don't just memorize answers. For each question, open your browser console or a Node.js REPL and run the code. Understanding why the output is what it is will serve you far better in an interview than reciting a memorized answer.

Section 1 – JavaScript Core Concepts

These questions test your understanding of the JavaScript language itself — the concepts that underpin everything else. They are asked in virtually every JavaScript interview regardless of the role or seniority level.
Q1. What are the differences between var, let, and const? Beginner
Featurevarletconst
ScopeFunction-scopedBlock-scopedBlock-scoped
HoistingHoisted & initialized to undefinedHoisted but NOT initialized (TDZ)Hoisted but NOT initialized (TDZ)
Re-declarationAllowedNot allowedNot allowed
Re-assignmentAllowedAllowedNot allowed
Global object propertyYes (window.x)NoNo
// var is function-scoped
function testVar() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10 — var leaks out of the if block
}

// let is block-scoped
function testLet() {
  if (true) {
    let y = 20;
  }
  console.log(y); // ReferenceError: y is not defined
}

// const cannot be reassigned
const PI = 3.14;
PI = 3; // TypeError: Assignment to constant variable

// BUT: const objects can be mutated
const user = { name: "Saiful" };
user.name = "John"; // This works — we mutated the object, not the binding
user = {};           // TypeError — this tries to reassign the binding
💡 Best Practice: Always use const by default. Switch to let only when you need to reassign. Never use var in modern JavaScript.
Q2. What is a Closure in JavaScript? Intermediate

A closure is a function that remembers the variables from its outer scope even after the outer function has finished executing. Closures are one of the most powerful and most tested JavaScript concepts.

function makeCounter() {
  let count = 0; // this variable is "enclosed"

  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// count is not accessible from outside
console.log(count); // ReferenceError

Real-world uses of closures:

  • Data encapsulation / private variables
  • Factory functions
  • Memoization / caching
  • Event handlers and callbacks
  • Partial application and currying
// Closure for private state
function createBankAccount(initialBalance) {
  let balance = initialBalance; // private

  return {
    deposit: (amount) => { balance += amount; },
    withdraw: (amount) => { balance -= amount; },
    getBalance: () => balance
  };
}

const account = createBankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.balance);      // undefined — truly private
Q3. What is Hoisting in JavaScript? Beginner

Hoisting is JavaScript's behavior of moving declarations to the top of their scope during the compilation phase, before code executes. Only the declaration is hoisted, not the initialization.

// Function declarations are fully hoisted
console.log(greet("Saiful")); // "Hello, Saiful" — works before declaration

function greet(name) {
  return `Hello, ${name}`;
}

// var declarations are hoisted but initialized to undefined
console.log(age); // undefined (not ReferenceError)
var age = 25;
console.log(age); // 25

// let and const are hoisted but NOT initialized — Temporal Dead Zone (TDZ)
console.log(city); // ReferenceError: Cannot access 'city' before initialization
let city = "Dhaka";
💡 The Temporal Dead Zone (TDZ) is the period between the start of a block and the point where a let or const variable is declared. Accessing a variable in its TDZ throws a ReferenceError.
Q4. What is the difference between == and ===? Beginner

== performs loose equality with type coercion — JavaScript tries to convert both values to the same type before comparing. === performs strict equality — no type conversion, both value and type must match.

console.log(0 == false);   // true  (false coerces to 0)
console.log(0 === false);  // false (different types)
console.log("" == false);  // true  (both coerce to 0)
console.log("" === false); // false
console.log(null == undefined);  // true  (special case)
console.log(null === undefined); // false
console.log(1 == "1");   // true  ("1" coerces to 1)
console.log(1 === "1");  // false
⚠️ Always use === in production code. The coercion rules of == are notoriously confusing and a common source of bugs.
Q5. What is the difference between null and undefined? Beginner
  • undefined — a variable has been declared but not assigned a value. JavaScript sets this automatically.
  • null — an intentional absence of value. A developer explicitly sets this to signal "no value here".
let a;
console.log(a);          // undefined (declared, not assigned)
console.log(typeof a);   // "undefined"

let b = null;
console.log(b);          // null (intentionally empty)
console.log(typeof b);   // "object" — famous JS quirk/bug

// Checking for both
function process(value) {
  if (value == null) { // true for both null and undefined
    return "no value";
  }
  return value;
}
Q6. What is the difference between call, apply, and bind? Intermediate

All three methods control the value of this inside a function, but they differ in how arguments are passed and when the function executes.

MethodExecutes Immediately?Arguments
callYesPassed individually: fn.call(ctx, a, b)
applyYesPassed as array: fn.apply(ctx, [a, b])
bindNo — returns new functionPassed individually, executed later
const person = { name: "Saiful" };

function introduce(role, company) {
  return `I'm ${this.name}, a ${role} at ${company}`;
}

// call — execute immediately, args individually
console.log(introduce.call(person, "Developer", "TriksBuddy"));

// apply — execute immediately, args as array
console.log(introduce.apply(person, ["Developer", "TriksBuddy"]));

// bind — returns a new function for later use
const boundIntroduce = introduce.bind(person, "Developer");
console.log(boundIntroduce("TriksBuddy")); // call later with remaining args
Q7. What is Prototypal Inheritance in JavaScript? Intermediate

JavaScript uses prototypal inheritance — every object has an internal link to another object called its prototype. When you access a property, JavaScript looks at the object first, then walks up the prototype chain until it finds the property or reaches null.

// Constructor function approach
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  return `${this.name} makes a sound`;
};

function Dog(name, breed) {
  Animal.call(this, name); // inherit properties
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype); // inherit methods
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
  return `${this.name} barks!`;
};

const dog = new Dog("Rex", "Labrador");
console.log(dog.speak()); // "Rex makes a sound" (inherited from Animal)
console.log(dog.bark());  // "Rex barks!" (own method)

// Modern class syntax (syntactic sugar over the above)
class Cat extends Animal {
  meow() {
    return `${this.name} meows!`;
  }
}

const cat = new Cat("Whiskers");
console.log(cat.speak()); // inherited
console.log(cat.meow());  // own
Q8. What is the difference between map, filter, and reduce? Beginner
MethodReturnsUse When
mapNew array of same lengthTransforming each element
filterNew array (possibly shorter)Selecting elements that match a condition
reduceSingle accumulated valueAggregating array into one value
const products = [
  { name: "Laptop", price: 1200, inStock: true },
  { name: "Mouse", price: 25, inStock: false },
  { name: "Keyboard", price: 80, inStock: true },
  { name: "Monitor", price: 400, inStock: true }
];

// map — transform to array of names
const names = products.map(p => p.name);
// ["Laptop", "Mouse", "Keyboard", "Monitor"]

// filter — only in-stock products
const inStock = products.filter(p => p.inStock);
// [{Laptop...}, {Keyboard...}, {Monitor...}]

// reduce — total price of in-stock items
const total = products
  .filter(p => p.inStock)
  .reduce((sum, p) => sum + p.price, 0);
// 1680
Q9. What is the Spread Operator and Rest Parameter? Beginner

Both use the ... syntax but serve opposite purposes:

  • Spreadexpands an iterable into individual elements
  • Restcollects remaining elements into an array
// Spread — expanding
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// Copy an object without mutation
const original = { name: "Saiful", role: "Developer" };
const updated = { ...original, role: "Senior Developer" };

// Rest — collecting remaining arguments
function sum(first, second, ...rest) {
  console.log(first);  // 1
  console.log(second); // 2
  console.log(rest);   // [3, 4, 5]
  return first + second + rest.reduce((a, b) => a + b, 0);
}

sum(1, 2, 3, 4, 5); // 15
Q10. What is Destructuring in JavaScript? Beginner

Destructuring allows you to unpack values from arrays or properties from objects into distinct variables in a single, clean expression.

// Object destructuring
const user = { name: "Saiful", age: 30, city: "Dhaka" };
const { name, age, city = "Unknown" } = user;
console.log(name, age, city); // "Saiful" 30 "Dhaka"

// Rename while destructuring
const { name: userName, age: userAge } = user;

// Array destructuring
const [first, second, , fourth] = [10, 20, 30, 40];
console.log(first, second, fourth); // 10 20 40

// Destructuring in function parameters
function displayUser({ name, role = "User", age }) {
  return `${name} (${role}), age ${age}`;
}

displayUser({ name: "Saiful", age: 30 }); // "Saiful (User), age 30"

// Swapping variables
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
Q11. What is the difference between a shallow copy and a deep copy? Intermediate

A shallow copy copies only the top-level properties — nested objects are still referenced, not duplicated. A deep copy recursively copies all levels, creating a completely independent object.

const original = {
  name: "Saiful",
  address: { city: "Dhaka", country: "Bangladesh" }
};

// Shallow copy — nested object is still shared
const shallow = { ...original };
shallow.address.city = "Chittagong";
console.log(original.address.city); // "Chittagong" — original was mutated!

// Deep copy — using structuredClone (modern, built-in)
const deep = structuredClone(original);
deep.address.city = "Sylhet";
console.log(original.address.city); // "Dhaka" — original is safe

// Alternative: JSON (has limitations — loses functions, Dates, undefined)
const jsonCopy = JSON.parse(JSON.stringify(original));
💡 structuredClone() was added to JavaScript in 2022 and is now the recommended way to deep copy objects in modern environments.
Q12. What are Higher-Order Functions? Intermediate

A Higher-Order Function (HOF) is a function that either takes one or more functions as arguments, or returns a function as its result. map, filter, and reduce are all higher-order functions.

// HOF that takes a function as argument
function applyTwice(fn, value) {
  return fn(fn(value));
}

const double = x => x * 2;
console.log(applyTwice(double, 5)); // 20 (5 → 10 → 20)

// HOF that returns a function (function factory)
function createMultiplier(multiplier) {
  return (number) => number * multiplier;
}

const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

console.log(triple(5));    // 15
console.log(quadruple(5)); // 20
Q13. What is Memoization and how do you implement it? Advanced

Memoization is an optimization technique that caches the results of expensive function calls and returns the cached result for the same inputs — avoiding redundant computations.

// Without memoization — recalculates every time
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
// fibonacci(40) is very slow — exponential time complexity

// With memoization
function memoize(fn) {
  const cache = new Map();

  return function(...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      console.log(`Cache hit for: ${key}`);
      return cache.get(key);
    }

    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const memoFib = memoize(function fib(n) {
  if (n <= 1) return n;
  return memoFib(n - 1) + memoFib(n - 2);
});

console.log(memoFib(40)); // Very fast — each value calculated only once
Q14. What is the difference between for...in and for...of? Beginner
  • for...in — iterates over the enumerable property keys of an object (including inherited ones)
  • for...of — iterates over the values of any iterable (arrays, strings, Maps, Sets, generators)
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  console.log(key); // "a", "b", "c" — keys, not values
}

const arr = [10, 20, 30];
for (const value of arr) {
  console.log(value); // 10, 20, 30 — values
}

// for...in on an array gives index keys — usually not what you want
for (const key in arr) {
  console.log(key); // "0", "1", "2" — index strings
}
💡 Use for...of for arrays and iterables. Use for...in only for plain objects when you need to iterate keys.
Q15. What are WeakMap and WeakSet, and when would you use them? Advanced

WeakMap and WeakSet hold weak references to their keys/values — meaning the garbage collector can collect them if no other references exist. They are not iterable and don't prevent memory leaks.

// WeakMap — private data storage per object instance
const privateData = new WeakMap();

class User {
  constructor(name, password) {
    privateData.set(this, { password }); // password stored privately
    this.name = name;
  }

  checkPassword(input) {
    return privateData.get(this).password === input;
  }
}

const user = new User("Saiful", "secret123");
console.log(user.checkPassword("secret123")); // true
console.log(user.password); // undefined — truly private

// When user is garbage collected, its WeakMap entry is automatically removed
// No memory leak!

Section 2 – Asynchronous JavaScript

Async JavaScript is the single most tested topic in Node.js interviews. The event loop, callbacks, Promises, and async/await are all must-know concepts. Expect at least 3-5 questions on this in any backend JavaScript interview.
Q16. How does the JavaScript Event Loop work? Advanced

JavaScript is single-threaded — it can only execute one thing at a time. The Event Loop is the mechanism that allows JavaScript to handle asynchronous operations without blocking the main thread.

The components:
  • Call Stack — where synchronous code executes, one frame at a time
  • Web APIs / Node.js APIs — where async operations (setTimeout, fetch, I/O) actually run
  • Callback Queue (Task Queue) — where callbacks from async operations wait
  • Microtask Queue — higher-priority queue for Promise callbacks and queueMicrotask()
  • Event Loop — continuously checks if the call stack is empty, then pulls tasks from queues
console.log("1 - Start");

setTimeout(() => console.log("2 - setTimeout"), 0);

Promise.resolve().then(() => console.log("3 - Promise"));

queueMicrotask(() => console.log("4 - Microtask"));

console.log("5 - End");

// Output order:
// 1 - Start
// 5 - End
// 3 - Promise      ← microtask queue runs before callback queue
// 4 - Microtask    ← microtask queue
// 2 - setTimeout   ← callback queue (runs last)
💡 Key Rule: The microtask queue (Promises) always empties completely before the callback queue (setTimeout, setInterval) is processed. This is why Promise callbacks run before setTimeout callbacks, even with setTimeout(fn, 0).
Q17. What is a Promise and what are its states? Beginner

A Promise is an object representing the eventual completion or failure of an asynchronous operation. It has three states:

  • Pending — initial state, neither fulfilled nor rejected
  • Fulfilled — the operation completed successfully
  • Rejected — the operation failed

Once a Promise settles (fulfilled or rejected), it cannot change state again.

// Creating a Promise
function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve({ id: userId, name: "Saiful" }); // success
      } else {
        reject(new Error("Invalid user ID"));    // failure
      }
    }, 1000);
  });
}

// Consuming a Promise
fetchUserData(1)
  .then(user => console.log("Got user:", user))
  .catch(error => console.error("Error:", error.message))
  .finally(() => console.log("Request complete")); // always runs
Q18. What is the difference between Promise.all, Promise.allSettled, Promise.race, and Promise.any? Intermediate
MethodResolves WhenRejects WhenUse Case
Promise.allALL promises fulfillANY promise rejectsAll tasks must succeed
Promise.allSettledALL promises settle (any outcome)Never rejectsNeed results of all, even failures
Promise.raceFIRST promise settlesFIRST promise rejectsTimeouts, first-wins scenarios
Promise.anyFIRST promise fulfillsALL promises rejectTry multiple sources, take first success
const p1 = fetch("/api/users");
const p2 = fetch("/api/products");
const p3 = fetch("/api/orders");

// Wait for all — fails if any one fails
const [users, products, orders] = await Promise.all([p1, p2, p3]);

// Wait for all regardless of failure
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(result => {
  if (result.status === "fulfilled") console.log(result.value);
  else console.error(result.reason);
});

// Timeout pattern with Promise.race
const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error("Timeout!")), 5000)
);
const data = await Promise.race([fetch("/api/data"), timeout]);
Q19. What is async/await and how does it work under the hood? Intermediate

async/await is syntactic sugar over Promises. An async function always returns a Promise. The await keyword pauses execution of the async function until the awaited Promise settles, without blocking the main thread.

// Promise chain — harder to read
function getUserOrders(userId) {
  return fetchUser(userId)
    .then(user => fetchOrders(user.id))
    .then(orders => processOrders(orders))
    .catch(error => handleError(error));
}

// async/await — same logic, much cleaner
async function getUserOrders(userId) {
  try {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(user.id);
    return await processOrders(orders);
  } catch (error) {
    handleError(error);
  }
}

// Running async calls in parallel (don't await each one sequentially!)
async function loadDashboard(userId) {
  // ❌ Sequential — slow (waits for each before starting next)
  const user = await fetchUser(userId);
  const posts = await fetchPosts(userId);
  const stats = await fetchStats(userId);

  // ✅ Parallel — fast (all start at the same time)
  const [user, posts, stats] = await Promise.all([
    fetchUser(userId),
    fetchPosts(userId),
    fetchStats(userId)
  ]);
}
Q20. What is Callback Hell and how do you avoid it? Intermediate

Callback hell (also called the "pyramid of doom") occurs when callbacks are nested multiple levels deep, making code hard to read, maintain, and debug.

// Callback hell — deeply nested, hard to follow
getUser(userId, function(user) {
  getOrders(user.id, function(orders) {
    getProducts(orders[0].id, function(products) {
      getReviews(products[0].id, function(reviews) {
        // ... more nesting
        console.log(reviews); // buried 4 levels deep
      });
    });
  });
});

// Solution 1: Promises
getUser(userId)
  .then(user => getOrders(user.id))
  .then(orders => getProducts(orders[0].id))
  .then(products => getReviews(products[0].id))
  .then(reviews => console.log(reviews))
  .catch(console.error);

// Solution 2: async/await (cleanest)
async function loadData(userId) {
  const user = await getUser(userId);
  const orders = await getOrders(user.id);
  const products = await getProducts(orders[0].id);
  const reviews = await getReviews(products[0].id);
  console.log(reviews);
}
Q21. What is a Generator function in JavaScript? Advanced

Generator functions (declared with function*) can pause and resume their execution using the yield keyword. They return a Generator object that implements the Iterator protocol.

function* numberGenerator() {
  console.log("Start");
  yield 1;
  console.log("After 1");
  yield 2;
  console.log("After 2");
  yield 3;
}

const gen = numberGenerator();

console.log(gen.next()); // "Start" → { value: 1, done: false }
console.log(gen.next()); // "After 1" → { value: 2, done: false }
console.log(gen.next()); // "After 2" → { value: 3, done: false }
console.log(gen.next()); // → { value: undefined, done: true }

// Practical use: infinite sequence without memory issues
function* infiniteIds() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const idGen = infiniteIds();
console.log(idGen.next().value); // 1
console.log(idGen.next().value); // 2
// ... generates on demand, no array in memory

Section 3 – Node.js Core Concepts

Node.js brings JavaScript to the server. These questions test your understanding of what makes Node.js unique — its non-blocking I/O model, module system, streams, and built-in modules.
Q22. What is Node.js and what makes it different from browser JavaScript? Beginner

Node.js is a server-side JavaScript runtime built on Chrome's V8 engine. It executes JavaScript outside the browser, enabling you to build web servers, CLI tools, APIs, and more.

FeatureBrowser JavaScriptNode.js
EnvironmentBrowserServer / system
DOM accessYes (window, document)No
File system accessNoYes (fs module)
Network accessLimited (CORS restricted)Full (HTTP, TCP, UDP)
Module systemES Modules (native)CommonJS + ES Modules
Global objectwindowglobal / globalThis
Q23. What is the difference between CommonJS and ES Modules in Node.js? Intermediate
FeatureCommonJS (CJS)ES Modules (ESM)
Syntaxrequire() / module.exportsimport / export
LoadingSynchronousAsynchronous
Tree-shakingNot supportedSupported
File extension.js or .cjs.mjs or .js with "type":"module"
Default in Node.jsYes (legacy default)Opt-in
// CommonJS
const express = require("express");
const { readFile } = require("fs");
module.exports = { myFunction };

// ES Modules
import express from "express";
import { readFile } from "fs/promises";
export { myFunction };
export default myClass;
Q24. What are Streams in Node.js and why are they important? Intermediate

Streams are objects that let you read data from a source or write data to a destination in a continuous fashion — chunk by chunk, rather than loading everything into memory at once. They are critical for handling large files, HTTP requests, and real-time data efficiently.

There are four types of streams: Readable, Writable, Duplex (both), and Transform (duplex that modifies data).

const fs = require("fs");
const zlib = require("zlib");

// Without streams — loads entire file into memory (bad for large files)
const data = fs.readFileSync("huge-file.csv"); // 2GB in RAM!

// With streams — processes chunk by chunk (memory efficient)
const readStream = fs.createReadStream("huge-file.csv");
const writeStream = fs.createWriteStream("output.csv.gz");
const gzip = zlib.createGzip();

// Pipe: read → compress → write
readStream
  .pipe(gzip)
  .pipe(writeStream)
  .on("finish", () => console.log("Done! Large file compressed efficiently"));

// HTTP response as a stream
const http = require("http");
http.createServer((req, res) => {
  const fileStream = fs.createReadStream("large-video.mp4");
  fileStream.pipe(res); // stream file directly to HTTP response
}).listen(3000);
Q25. What is the Node.js cluster module? Advanced

Node.js runs on a single thread, which means it cannot natively use multiple CPU cores. The cluster module allows you to spawn multiple worker processes (one per CPU core) that all share the same server port, enabling true parallelism.

const cluster = require("cluster");
const http = require("http");
const os = require("os");

if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  console.log(`Primary ${process.pid} running — spawning ${numCPUs} workers`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on("exit", (worker) => {
    console.log(`Worker ${worker.process.pid} died — restarting...`);
    cluster.fork(); // auto-restart dead workers
  });

} else {
  // Worker process — each runs an HTTP server on the same port
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Response from worker ${process.pid}`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started`);
}
💡 In production, tools like PM2 handle clustering automatically with pm2 start app.js -i max — no manual cluster code needed.
Q26. What is process.nextTick() vs setImmediate() in Node.js? Advanced
process.nextTick()setImmediate()
QueuenextTick queue (highest priority)Check phase of event loop
Runs whenAfter current operation, before I/OAfter I/O callbacks
PriorityHigher than PromisesLower than nextTick and Promises
console.log("1 - sync");

setImmediate(() => console.log("2 - setImmediate"));

process.nextTick(() => console.log("3 - nextTick"));

Promise.resolve().then(() => console.log("4 - Promise"));

console.log("5 - sync");

// Output:
// 1 - sync
// 5 - sync
// 3 - nextTick        ← nextTick queue (before microtasks in Node.js)
// 4 - Promise         ← microtask queue
// 2 - setImmediate    ← check phase

Section 4 – Express.js & REST APIs

Express.js is the most popular Node.js web framework. These questions cover the essential patterns you will use building real-world APIs.
Q27. What is Express.js and what are its core features? Beginner

Express.js is a minimal, unopinionated web framework for Node.js. It provides a thin layer of fundamental web application features without obscuring Node's features.

Core features: routing, middleware support, template engine integration, static file serving, error handling, and HTTP utility methods.

const express = require("express");
const app = express();

// Parse JSON request bodies
app.use(express.json());

// Route handlers
app.get("/api/products", async (req, res) => {
  const products = await Product.findAll();
  res.json(products);
});

app.post("/api/products", async (req, res) => {
  const { name, price } = req.body;
  const product = await Product.create({ name, price });
  res.status(201).json(product);
});

app.put("/api/products/:id", async (req, res) => {
  const product = await Product.findByIdAndUpdate(req.params.id, req.body);
  if (!product) return res.status(404).json({ message: "Not found" });
  res.json(product);
});

app.delete("/api/products/:id", async (req, res) => {
  await Product.findByIdAndDelete(req.params.id);
  res.status(204).send();
});

app.listen(3000, () => console.log("Server running on port 3000"));
Q28. What is Middleware in Express.js? Beginner

Middleware in Express is a function with access to the request object (req), response object (res), and the next function. Middleware can execute code, modify req/res, end the request-response cycle, or call the next middleware.

// Custom authentication middleware
function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];

  if (!token) {
    return res.status(401).json({ message: "No token provided" });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // attach user to request
    next(); // pass control to next middleware/route
  } catch (error) {
    res.status(401).json({ message: "Invalid token" });
  }
}

// Request logger middleware
function requestLogger(req, res, next) {
  console.log(`${new Date().toISOString()} ${req.method} ${req.path}`);
  next();
}

// Apply globally
app.use(requestLogger);

// Apply to specific routes
app.get("/api/profile", authenticate, (req, res) => {
  res.json(req.user);
});
Q29. How do you handle errors in Express.js? Intermediate

Express has a special error-handling middleware with four parameters (err, req, res, next). It must be registered after all other routes and middleware.

// Custom error class
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

// Async error wrapper — avoids try/catch in every route
const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

// Route using asyncHandler
app.get("/api/users/:id", asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) throw new AppError("User not found", 404);
  res.json(user);
}));

// Global error handler (must be last)
app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  const message = err.isOperational ? err.message : "Internal server error";

  console.error("Error:", err);

  res.status(statusCode).json({
    status: "error",
    message,
    ...(process.env.NODE_ENV === "development" && { stack: err.stack })
  });
});
Q30. What are best practices for structuring a Node.js/Express project? Intermediate

A well-structured Express project separates concerns and scales cleanly as the codebase grows:

project/
├── src/
│   ├── controllers/     # Handle HTTP requests/responses
│   │   └── userController.js
│   ├── services/        # Business logic (independent of HTTP)
│   │   └── userService.js
│   ├── repositories/    # Database access layer
│   │   └── userRepository.js
│   ├── middleware/      # Custom middleware
│   │   ├── auth.js
│   │   └── errorHandler.js
│   ├── routes/          # Route definitions
│   │   └── userRoutes.js
│   ├── models/          # Data models/schemas
│   │   └── User.js
│   ├── utils/           # Helpers and utilities
│   ├── config/          # Configuration files
│   └── app.js           # Express app setup
├── tests/
├── .env
└── server.js            # Entry point (starts the server)
💡 Keep your app.js for Express configuration and middleware, and your server.js only for starting the server. This makes testing easier — you can import app without actually starting a server.

Section 5 – Advanced & Performance Topics

Q31. What is the difference between SQL and NoSQL databases? When would you choose each with Node.js? Intermediate
FeatureSQL (PostgreSQL, MySQL)NoSQL (MongoDB, Redis)
SchemaFixed, predefinedFlexible, dynamic
Data modelTables and rowsDocuments, key-value, graphs
RelationshipsForeign keys, JOINsEmbedded documents or references
ScalingVertical (primarily)Horizontal
Best forComplex queries, transactionsUnstructured data, high write volume

Popular Node.js ORMs/ODMs: Prisma or Sequelize for SQL, Mongoose for MongoDB.

Q32. How do you secure a Node.js API? Intermediate

API security in Node.js covers multiple layers:

const helmet = require("helmet");
const rateLimit = require("express-rate-limit");
const mongoSanitize = require("express-mongo-sanitize");
const xss = require("xss-clean");

// 1. Security headers
app.use(helmet());

// 2. Rate limiting — prevent brute force
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,
  message: "Too many requests"
});
app.use("/api", limiter);

// 3. Data sanitization — prevent NoSQL injection
app.use(mongoSanitize());

// 4. XSS protection
app.use(xss());

// 5. CORS configuration
app.use(cors({
  origin: ["https://yourdomain.com"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  credentials: true
}));

// 6. Validate and sanitize all input
// 7. Use HTTPS only
// 8. Store secrets in environment variables, never hardcode
// 9. Hash passwords with bcrypt (never store plain text)
// 10. Use parameterized queries to prevent SQL injection
Q33. What is caching and how do you implement it in Node.js? Intermediate

Caching stores frequently accessed data in fast storage to reduce database load and improve response times. Redis is the standard caching layer for Node.js applications.

const redis = require("ioredis");
const client = new redis(process.env.REDIS_URL);

// Cache middleware
function cacheMiddleware(ttlSeconds = 60) {
  return async (req, res, next) => {
    const key = `cache:${req.originalUrl}`;

    const cached = await client.get(key);
    if (cached) {
      return res.json(JSON.parse(cached)); // serve from cache
    }

    // Override res.json to intercept and cache the response
    const originalJson = res.json.bind(res);
    res.json = async (data) => {
      await client.setex(key, ttlSeconds, JSON.stringify(data));
      return originalJson(data);
    };

    next();
  };
}

// Apply to routes
app.get("/api/products", cacheMiddleware(300), async (req, res) => {
  const products = await Product.findAll(); // only hits DB on cache miss
  res.json(products);
});
Q34. What is WebSocket and how does it differ from HTTP in Node.js? Intermediate
FeatureHTTPWebSocket
ConnectionShort-lived, request-responsePersistent, full-duplex
DirectionClient initiates every timeBoth client and server can send anytime
OverheadHeaders on every requestSmall framing headers after handshake
Use caseStandard web APIs, formsChat, live feeds, gaming, collaboration
const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ port: 8080 });

const clients = new Set();

wss.on("connection", (ws) => {
  clients.add(ws);
  console.log("Client connected");

  ws.on("message", (data) => {
    const message = JSON.parse(data);

    // Broadcast to all connected clients
    clients.forEach(client => {
      if (client.readyState === ws.OPEN) {
        client.send(JSON.stringify({
          user: message.user,
          text: message.text,
          time: new Date().toISOString()
        }));
      }
    });
  });

  ws.on("close", () => clients.delete(ws));
});
Q35. What are some Node.js performance optimization techniques? Advanced
  • Use async/await properly — never block the event loop with synchronous operations (readFileSync, JSON.parse on huge strings)
  • Use Streams for large file operations and HTTP responses
  • Implement caching with Redis for expensive database queries
  • Use connection pooling — don't create new DB connections per request
  • Enable gzip/Brotli compression with the compression middleware
  • Use clustering or worker threads for CPU-bound tasks
  • Implement pagination — never return unbounded datasets
  • Profile with Node.js built-in profiler or clinic.js to find bottlenecks
// Pagination example
app.get("/api/products", async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = Math.min(parseInt(req.query.limit) || 20, 100);
  const offset = (page - 1) * limit;

  const { count, rows } = await Product.findAndCountAll({
    limit,
    offset,
    order: [["createdAt", "DESC"]]
  });

  res.json({
    data: rows,
    pagination: {
      total: count,
      page,
      limit,
      totalPages: Math.ceil(count / limit)
    }
  });
});

💼 Interview Tips for JavaScript & Node.js Roles

  • Be ready to predict output. Interviewers frequently show you a code snippet and ask what it logs. Practice tracing async code, closures, and hoisting manually.
  • Know the event loop cold. Draw the call stack, microtask queue, and callback queue on a whiteboard if asked. Explaining why Promises resolve before setTimeout shows deep understanding.
  • Understand when NOT to use Node.js. It's not ideal for CPU-heavy computations — mention Worker Threads as the solution. This shows maturity.
  • Know at least one Node.js framework deeply — Express is minimum, bonus for NestJS or Fastify knowledge.
  • Security questions are common in senior roles — always mention helmet, rate limiting, input validation, and parameterized queries.
  • Demonstrate async best practices — parallel Promise.all vs sequential await, proper error handling, avoiding unhandled rejections.

❓ Frequently Asked Questions

Is Node.js good for CPU-intensive tasks?

No — Node.js is optimized for I/O-bound tasks (file reads, database queries, HTTP requests). For CPU-intensive work (image processing, machine learning, complex computations), the single-threaded event loop will be blocked. Use Worker Threads, child processes, or offload to a dedicated service written in a language better suited for CPU work like Python or Go.

What is the difference between Node.js and Deno?

Deno is a modern JavaScript/TypeScript runtime created by the original creator of Node.js, Ryan Dahl, to address Node's design mistakes. Key differences: Deno has TypeScript support built-in, uses ES Modules only, has a permission-based security model, and uses URLs for imports instead of npm. Node.js has a vastly larger ecosystem and is far more widely used in production.

Should I learn Express.js or NestJS?

Learn Express first to understand the fundamentals — middleware, routing, manual structure. Then learn NestJS if you are targeting enterprise roles or working in TypeScript-first teams. NestJS provides opinionated architecture (similar to ASP.NET Core with DI, decorators, and modules) that scales better in large teams.

What is the difference between require() and import in Node.js?

require() is CommonJS — synchronous, works everywhere in Node.js by default. import is ES Module syntax — asynchronous, requires either a .mjs extension or "type": "module" in package.json. ES Modules support tree-shaking and are the modern standard, but CommonJS still dominates the existing Node.js ecosystem.

How do you prevent unhandled Promise rejections in Node.js?

Always attach a .catch() handler to every Promise or use try/catch inside async functions. Globally, you can listen to the unhandledRejection process event to log and gracefully shut down: process.on('unhandledRejection', (reason) => { console.error(reason); process.exit(1); });

✅ Key Takeaways

  • Always use const by default, let when needed, and never var in modern JavaScript
  • Closures remember variables from their outer scope — they are the foundation of many JavaScript patterns
  • The Event Loop is what makes Node.js non-blocking — the microtask queue (Promises) always runs before the callback queue (setTimeout)
  • Promise.all for parallel success-or-fail, Promise.allSettled when you need all results regardless of failure
  • Use streams for large data processing — never load a 1GB file into memory when you can pipe it
  • Structure Express apps in layers: routes → controllers → services → repositories
  • Security fundamentals: helmet, rate limiting, input validation, bcrypt for passwords, parameterized queries
  • Node.js is ideal for I/O-bound work — use Worker Threads for CPU-intensive tasks

Found this helpful? Share it with someone preparing for a JavaScript interview. Have a question we didn't cover? Drop it in the comments — we read and respond to every one.

Wednesday, March 25, 2026

Top 35 ASP.NET Core Interview Questions and Answers (2026) – Beginner to Advanced

Top 35 ASP.NET Core Interview Questions and Answers (2026) – Beginner to Advanced

📅 Published: March 2026  |  ⏱ Reading Time: ~18 minutes  |  🏷️ ASP.NET CoreC#Interview.NET 8Web Development

📌 TL;DR: This article covers the 35 most asked ASP.NET Core interview questions for 2026, ranging from beginner concepts like middleware and routing to advanced topics like minimal APIs, gRPC, and performance optimization. Each answer includes code examples and practical explanations. Bookmark this page before your next interview.

Introduction

ASP.NET Core is one of the most in-demand backend frameworks in 2026, consistently ranking among the top technologies in Stack Overflow Developer Surveys. Whether you are preparing for your first .NET developer role or interviewing for a senior architect position, having a solid grasp of ASP.NET Core concepts is non-negotiable.

This guide covers 35 carefully selected interview questions with detailed answers, real code examples, and difficulty labels so you know exactly what level each question targets. Questions are grouped by topic so you can jump straight to the area you need to review most.

💡 Pro Tip: Interviewers don't just want definitions — they want to see that you understand why something works the way it does. For every answer here, make sure you understand the reasoning, not just the words.

Section 1 – ASP.NET Core Fundamentals

These questions are almost always asked in every .NET interview, regardless of seniority. Master these before anything else.

Q1. What is ASP.NET Core and how is it different from ASP.NET Framework? Beginner

ASP.NET Core is a cross-platform, high-performance, open-source framework for building modern web applications and APIs. It is a complete rewrite of the original ASP.NET Framework, designed from the ground up to run on Windows, Linux, and macOS.

FeatureASP.NET FrameworkASP.NET Core
PlatformWindows onlyCross-platform
PerformanceModerateVery high (one of the fastest frameworks)
HostingIIS onlyIIS, Kestrel, Docker, Nginx
Open SourcePartialFully open source
Dependency InjectionNot built-inBuilt-in from the start
Latest Version.NET Framework 4.8 (no new major versions).NET 8 / .NET 9 (active development)

Q2. What is the Program.cs file in ASP.NET Core and what is its role? Beginner

Program.cs is the entry point of an ASP.NET Core application. In .NET 6 and later, it uses a minimal hosting model that combines the old Startup.cs and Program.cs into a single file. It is responsible for:

  • Creating and configuring the WebApplication builder
  • Registering services into the dependency injection container
  • Configuring the middleware pipeline
  • Running the application
var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

var app = builder.Build();

// Configure middleware pipeline
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

Q3. What is Kestrel in ASP.NET Core? Beginner

Kestrel is the default, cross-platform web server built into ASP.NET Core. It is a lightweight, high-performance HTTP server based on libuv (and later on .NET's own async I/O). Kestrel can be used:

  • Alone — directly facing the internet in production for simple scenarios
  • Behind a reverse proxy — behind Nginx, Apache, or IIS (recommended for production)

Kestrel is what makes ASP.NET Core one of the fastest web frameworks in the world in TechEmpower benchmarks.

Q4. What is the difference between IApplicationBuilder and IServiceCollection? Beginner

These two interfaces serve fundamentally different purposes:

  • IServiceCollection — used to register services into the dependency injection container. This happens at application startup before the app runs. Example: builder.Services.AddControllers()
  • IApplicationBuilder — used to configure the HTTP request pipeline by adding middleware. Example: app.UseAuthentication()

A simple way to remember: IServiceCollection is about what your app needs, IApplicationBuilder is about how requests are handled.

Q5. What is the difference between AddSingleton, AddScoped, and AddTransient? Beginner

These three methods define the lifetime of a service registered in the DI container:

LifetimeCreatedShared?Best For
SingletonOnce per applicationAcross all requests and usersConfiguration, caching, logging
ScopedOnce per HTTP requestWithin the same requestDatabase contexts (EF Core DbContext)
TransientEvery time requestedNever sharedLightweight, stateless services
builder.Services.AddSingleton<IConfigService, ConfigService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddTransient<IEmailSender, EmailSender>();
⚠️ Common Mistake: Never inject a Scoped service into a Singleton. The Scoped service will behave like a Singleton and can cause data leaks between requests.

Q6. What is Routing in ASP.NET Core? Beginner

Routing is the mechanism that maps incoming HTTP requests to the correct controller action or endpoint. ASP.NET Core supports two main routing approaches:

1. Conventional Routing — defined globally using a URL pattern template:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

2. Attribute Routing — defined directly on controllers and actions using attributes:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetById(int id) { ... }

    [HttpPost]
    public IActionResult Create([FromBody] ProductDto dto) { ... }
}

Attribute routing is preferred for Web APIs because it gives you precise control over URL structure.

Q7. What is the difference between IActionResult and ActionResult<T>? Intermediate

IActionResult is a non-generic interface that can return any HTTP response. ActionResult<T> is a generic version introduced in ASP.NET Core 2.1 that additionally allows returning a strongly-typed object directly, which Swagger/OpenAPI can inspect for documentation.

// IActionResult - no type info for swagger
public IActionResult GetProduct(int id)
{
    var product = _repo.GetById(id);
    if (product == null) return NotFound();
    return Ok(product);
}

// ActionResult<T> - swagger knows the return type is Product
public ActionResult<Product> GetProduct(int id)
{
    var product = _repo.GetById(id);
    if (product == null) return NotFound();
    return product; // implicit conversion to Ok(product)
}

Use ActionResult<T> for API controllers whenever possible.

Q8. What are Model Binding and Model Validation in ASP.NET Core? Beginner

Model Binding automatically maps incoming request data (route values, query strings, form data, JSON body) to action method parameters or model properties.

Model Validation checks that the bound data meets the defined rules using Data Annotation attributes or Fluent Validation.

public class CreateUserDto
{
    [Required]
    [StringLength(100, MinimumLength = 2)]
    public string Name { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }

    [Range(18, 120)]
    public int Age { get; set; }
}

[HttpPost]
public IActionResult Create([FromBody] CreateUserDto dto)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    // proceed with valid data
}

When using [ApiController] attribute, model validation errors automatically return a 400 Bad Request — you don't need the ModelState.IsValid check manually.

Q9. What is the [ApiController] attribute and what does it do? Beginner

The [ApiController] attribute enables several API-specific behaviors automatically:

  • Automatic model validation — returns 400 if ModelState is invalid
  • Binding source inference — complex types are automatically bound from the request body ([FromBody] assumed)
  • Problem details responses — error responses follow RFC 7807 format
  • Attribute routing requirement — forces use of attribute routing

Q10. What is Configuration in ASP.NET Core and how does it work? Beginner

ASP.NET Core has a flexible configuration system that can read settings from multiple sources in a defined priority order:

  1. appsettings.json
  2. appsettings.{Environment}.json (e.g. appsettings.Development.json)
  3. Environment variables
  4. Command line arguments
  5. User Secrets (development only)
  6. Azure Key Vault (production)

Each source overrides the previous one, so environment variables override appsettings.json.

// appsettings.json
{
  "ConnectionStrings": {
    "Default": "Server=.;Database=MyDb;Trusted_Connection=True"
  },
  "AppSettings": {
    "PageSize": 20
  }
}

// Accessing configuration
var connStr = builder.Configuration.GetConnectionString("Default");
var pageSize = builder.Configuration.GetValue<int>("AppSettings:PageSize");

Q11. What is the Options Pattern in ASP.NET Core? Intermediate

The Options Pattern is a strongly-typed way to bind configuration sections to C# classes, making configuration easier to work with and testable.

// appsettings.json
{
  "EmailSettings": {
    "SmtpHost": "smtp.gmail.com",
    "Port": 587,
    "SenderEmail": "no-reply@triksbuddy.com"
  }
}

// Options class
public class EmailSettings
{
    public string SmtpHost { get; set; }
    public int Port { get; set; }
    public string SenderEmail { get; set; }
}

// Register in Program.cs
builder.Services.Configure<EmailSettings>(
    builder.Configuration.GetSection("EmailSettings"));

// Inject and use
public class EmailService
{
    private readonly EmailSettings _settings;

    public EmailService(IOptions<EmailSettings> options)
    {
        _settings = options.Value;
    }
}

Q12. What is Minimal API in ASP.NET Core? Intermediate

Minimal APIs, introduced in .NET 6, allow building HTTP APIs with minimal code and ceremony — no controllers, no Startup class. They are ideal for microservices and simple APIs.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();

app.MapGet("/products", async (AppDbContext db) =>
    await db.Products.ToListAsync());

app.MapGet("/products/{id}", async (int id, AppDbContext db) =>
    await db.Products.FindAsync(id) is Product p
        ? Results.Ok(p)
        : Results.NotFound());

app.MapPost("/products", async (Product product, AppDbContext db) =>
{
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Results.Created($"/products/{product.Id}", product);
});

app.Run();
 

Section 2 – Middleware & Request Pipeline

Middleware is one of the most important ASP.NET Core concepts. Almost every interview will include at least 2-3 questions on this topic.
 

Q13. What is Middleware in ASP.NET Core? Beginner

Middleware is software that is assembled into an application pipeline to handle HTTP requests and responses. Each middleware component can:

  • Choose whether to pass the request to the next component
  • Perform work before and after the next component in the pipeline

The pipeline is built as a chain of delegates — this is often called the "Russian dolls" model. Common built-in middleware includes: UseHttpsRedirection, UseAuthentication, UseAuthorization, UseStaticFiles, UseRouting.

Q14. How do you create custom middleware in ASP.NET Core? Intermediate

You can create middleware using a class with an InvokeAsync method and a constructor that takes RequestDelegate:

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next,
        ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation(
            "Request: {Method} {Path}",
            context.Request.Method,
            context.Request.Path);

        var stopwatch = Stopwatch.StartNew();
        await _next(context); // call next middleware
        stopwatch.Stop();

        _logger.LogInformation(
            "Response: {StatusCode} in {ElapsedMs}ms",
            context.Response.StatusCode,
            stopwatch.ElapsedMilliseconds);
    }
}

// Register in Program.cs
app.UseMiddleware<RequestLoggingMiddleware>();

Q15. What is the order of middleware execution and why does it matter? Intermediate

Middleware executes in the exact order it is registered in Program.cs. The order matters because each middleware wraps the next one. A request flows in through middleware top-to-bottom, and the response flows out bottom-to-top.

The recommended order for a typical ASP.NET Core app is:

app.UseExceptionHandler();     // 1. Catch all unhandled exceptions
app.UseHsts();                 // 2. HTTP Strict Transport Security
app.UseHttpsRedirection();     // 3. Redirect HTTP to HTTPS
app.UseStaticFiles();          // 4. Serve static files early
app.UseRouting();              // 5. Match routes
app.UseCors();                 // 6. CORS before auth
app.UseAuthentication();       // 7. Who are you?
app.UseAuthorization();        // 8. What can you do?
app.UseResponseCaching();      // 9. Cache after auth
app.MapControllers();          // 10. Execute the endpoint
⚠️ Common Mistake: Putting UseAuthorization() before UseAuthentication() means authorization runs without knowing who the user is. Always authenticate before authorizing.
 

Q16. What is the difference between Use, Run, and Map in middleware? Intermediate

  • Use — adds middleware that can call the next middleware in the pipeline
  • Run — adds terminal middleware (short-circuits the pipeline, nothing after it runs)
  • Map — branches the pipeline based on the request path
app.Use(async (context, next) =>
{
    // runs before next middleware
    await next(context);
    // runs after next middleware
});

app.Map("/health", healthApp =>
{
    healthApp.Run(async context =>
    {
        await context.Response.WriteAsync("Healthy");
    });
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Final middleware - nothing after this runs");
});

Q17. What is Exception Handling Middleware in ASP.NET Core? Intermediate

ASP.NET Core provides several ways to handle exceptions globally:

1. UseExceptionHandler — redirects to an error page or endpoint:

app.UseExceptionHandler("/error");
// or using a lambda:
app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        context.Response.StatusCode = 500;
        context.Response.ContentType = "application/json";
        var error = context.Features.Get<IExceptionHandlerFeature>();
        await context.Response.WriteAsJsonAsync(new {
            message = "An error occurred",
            detail = error?.Error.Message
        });
    });
});

2. Custom Global Exception Middleware — gives you full control over error responses across the entire API.

Q18. What is Response Caching in ASP.NET Core? Intermediate

Response Caching reduces server load by storing HTTP responses and serving them for subsequent identical requests without re-executing the action.

// Register
builder.Services.AddResponseCaching();

// Use in pipeline
app.UseResponseCaching();

// Apply to action
[HttpGet]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)]
public IActionResult GetProducts()
{
    return Ok(_productService.GetAll());
}

For distributed caching (Redis, SQL Server), use IDistributedCache or libraries like EasyCaching or FusionCache.

Q19. What is CORS and how do you configure it in ASP.NET Core? Beginner

CORS (Cross-Origin Resource Sharing) is a browser security feature that blocks web pages from making requests to a different domain than the one that served the page. ASP.NET Core has built-in CORS support:

// Define a named policy
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowMyApp", policy =>
    {
        policy.WithOrigins("https://triksbuddy.com", "https://localhost:3000")
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials();
    });
});

// Apply globally
app.UseCors("AllowMyApp");

// Or apply to specific controller/action
[EnableCors("AllowMyApp")]
public class ProductsController : ControllerBase { ... }

Q20. What is Rate Limiting in ASP.NET Core? Advanced

Rate limiting, built into ASP.NET Core 7+, restricts the number of requests a client can make in a given time window — protecting your API from abuse and DDoS attacks.

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("fixed", limiterOptions =>
    {
        limiterOptions.PermitLimit = 100;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        limiterOptions.QueueLimit = 10;
    });
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});

app.UseRateLimiter();

// Apply to endpoint
app.MapGet("/api/data", () => "data").RequireRateLimiting("fixed");

Section 3 – Dependency Injection

Q21. What is Dependency Injection and why is it important? Beginner

Dependency Injection (DI) is a design pattern where an object's dependencies are provided externally rather than created by the object itself. ASP.NET Core has DI built in from the ground up.

Benefits:

  • Loose coupling between components
  • Easier unit testing (you can inject mocks)
  • Better code organization and single responsibility
  • Centralized service lifetime management
// Without DI (tightly coupled - bad)
public class OrderService
{
    private readonly EmailService _emailService = new EmailService(); // hard dependency
}

// With DI (loosely coupled - good)
public class OrderService
{
    private readonly IEmailService _emailService;

    public OrderService(IEmailService emailService)
    {
        _emailService = emailService; // injected from outside
    }
}

Q22. What is the difference between constructor injection and property injection? Intermediate

Constructor Injection (preferred in ASP.NET Core) — dependencies are passed through the constructor. The object cannot be created without its dependencies, making them required and explicit.

Property Injection — dependencies are set through public properties after object creation. This makes dependencies optional, which can lead to null reference errors if not carefully managed. ASP.NET Core's built-in DI does not support property injection natively — you need a third-party container like Autofac.

Q23. What is IServiceProvider and when would you use it? Intermediate

IServiceProvider is the interface for the DI container itself. You can use it to resolve services manually (Service Locator pattern) — though this should be avoided in application code as it hides dependencies.

// Avoid this in application code (Service Locator anti-pattern)
public class MyClass
{
    private readonly IServiceProvider _provider;
    public MyClass(IServiceProvider provider) { _provider = provider; }

    public void DoWork()
    {
        var service = _provider.GetRequiredService<IMyService>();
    }
}

// Acceptable use: resolving scoped services from a singleton background service
public class MyBackgroundService : BackgroundService
{
    private readonly IServiceProvider _provider;
    public MyBackgroundService(IServiceProvider provider) { _provider = provider; }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        using var scope = _provider.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        // use dbContext safely within this scope
    }
}

Q24. What is a Keyed Service in ASP.NET Core 8? Advanced

Keyed Services, introduced in .NET 8, allow registering multiple implementations of the same interface with a unique key, and resolving a specific implementation by key.

// Register multiple implementations with keys
builder.Services.AddKeyedSingleton<IPaymentProcessor, StripeProcessor>("stripe");
builder.Services.AddKeyedSingleton<IPaymentProcessor, PayPalProcessor>("paypal");

// Resolve by key
public class CheckoutService
{
    private readonly IPaymentProcessor _processor;

    public CheckoutService([FromKeyedServices("stripe")] IPaymentProcessor processor)
    {
        _processor = processor;
    }
}
 

Section 4 – Authentication & Authorization

Q25. What is the difference between Authentication and Authorization? Beginner

  • Authentication — verifies who you are (identity). "Are you really John?"
  • Authorization — verifies what you can do (permissions). "Can John access this admin page?"

In ASP.NET Core, UseAuthentication() must always come before UseAuthorization() in the pipeline.

Q26. What is JWT and how is it used in ASP.NET Core? Intermediate

JWT (JSON Web Token) is a compact, self-contained token format used for stateless authentication. A JWT contains three Base64-encoded parts: Header, Payload (claims), and Signature.

// Configure JWT Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });

// Generate a token
var claims = new[]
{
    new Claim(ClaimTypes.Name, user.Username),
    new Claim(ClaimTypes.Role, user.Role)
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var token = new JwtSecurityToken(
    issuer: _config["Jwt:Issuer"],
    audience: _config["Jwt:Audience"],
    claims: claims,
    expires: DateTime.UtcNow.AddHours(1),
    signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);

Q27. What is Policy-Based Authorization in ASP.NET Core? Intermediate

Policy-based authorization provides more flexibility than simple role checks. You define named policies with requirements, then apply them to controllers or actions.

// Define policies
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"));

    options.AddPolicy("MinimumAge", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(18)));

    options.AddPolicy("PremiumUser", policy =>
        policy.RequireClaim("subscription", "premium"));
});

// Apply to controller
[Authorize(Policy = "AdminOnly")]
public class AdminController : ControllerBase { ... }

[Authorize(Policy = "MinimumAge")]
public IActionResult GetAdultContent() { ... }

 

Section 5 – Entity Framework Core

Q28. What is Entity Framework Core? Beginner

Entity Framework Core (EF Core) is the official ORM (Object-Relational Mapper) for .NET. It lets you work with a database using .NET objects, eliminating most of the data-access code you would otherwise write. EF Core supports SQL Server, MySQL, PostgreSQL, SQLite, and more.

EF Core supports three development approaches:

  • Code First — define your model in C# classes, EF generates the database
  • Database First — scaffold C# models from an existing database
  • Model First — less common, design through a visual designer

Q29. What is the difference between DbContext and DbSet<T>? Beginner

  • DbContext — represents a session with the database. It manages connections, change tracking, and saving data. You inherit from it to create your application context.
  • DbSet<T> — represents a table in the database. Each DbSet property on your DbContext corresponds to a database table.
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<Order> Orders { get; set; }
}

Q30. What is Lazy Loading vs Eager Loading vs Explicit Loading in EF Core? Intermediate

StrategyWhen Data is LoadedHow
Eager LoadingWith the main query.Include()
Lazy LoadingWhen navigation property is accessedProxy or UseLazyLoadingProxies()
Explicit LoadingManually triggered after initial load.Entry().Collection().LoadAsync()
// Eager Loading (recommended for most cases)
var orders = await db.Orders
    .Include(o => o.Customer)
    .Include(o => o.Items)
        .ThenInclude(i => i.Product)
    .ToListAsync();

// Explicit Loading
var order = await db.Orders.FindAsync(1);
await db.Entry(order).Collection(o => o.Items).LoadAsync();
⚠️ N+1 Problem: Lazy Loading can cause the N+1 query problem — 1 query for the list, then N queries for each related entity. Prefer Eager Loading in performance-sensitive code.

 

Section 6 – Advanced Topics

Q31. What is gRPC in ASP.NET Core and when would you use it? Advanced

gRPC is a high-performance, open-source RPC (Remote Procedure Call) framework that uses HTTP/2 and Protocol Buffers (protobuf) for serialization. It is significantly faster than REST for inter-service communication in microservices.

Use gRPC when:

  • Building microservices that communicate internally
  • You need real-time bidirectional streaming
  • Performance and bandwidth efficiency are critical

Use REST when:

  • Building public APIs consumed by browsers or third parties
  • You need broad compatibility without special client libraries

Q32. What is SignalR and what is it used for? Intermediate

SignalR is an ASP.NET Core library that enables real-time, bidirectional communication between server and clients. It automatically chooses the best transport (WebSockets, Server-Sent Events, or Long Polling) based on what the client supports.

Common use cases: chat applications, live notifications, real-time dashboards, collaborative editing, live sports scores.

// Hub definition
public class NotificationHub : Hub
{
    public async Task SendNotification(string userId, string message)
    {
        await Clients.User(userId).SendAsync("ReceiveNotification", message);
    }
}

// Register
builder.Services.AddSignalR();
app.MapHub<NotificationHub>("/notifications");

Q33. What are Background Services in ASP.NET Core? Intermediate

Background Services are long-running tasks that run in the background of your ASP.NET Core application. You implement IHostedService or extend BackgroundService.

public class EmailQueueProcessor : BackgroundService
{
    private readonly ILogger<EmailQueueProcessor> _logger;

    public EmailQueueProcessor(ILogger<EmailQueueProcessor> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Processing email queue...");
            // do work here
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }
}

// Register
builder.Services.AddHostedService<EmailQueueProcessor>();

Q34. What is Health Checks in ASP.NET Core? Intermediate

Health Checks provide an endpoint that reports the health status of your application and its dependencies (database, external APIs, etc.). They are essential for container orchestration systems like Kubernetes.

builder.Services.AddHealthChecks()
    .AddSqlServer(connectionString: builder.Configuration.GetConnectionString("Default"))
    .AddUrlGroup(new Uri("https://api.thirdparty.com/health"), name: "third-party-api")
    .AddCheck<CustomHealthCheck>("custom");

app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
 

Q35. How do you optimize performance in ASP.NET Core APIs? Advanced

Performance optimization in ASP.NET Core covers multiple layers:

  • Use async/await everywhere — never block threads with .Result or .Wait()
  • Response compression — enable Brotli/GZip compression
  • Output caching — use AddOutputCache() in .NET 7+
  • EF Core optimization — use AsNoTracking() for read-only queries, select only needed columns with Select()
  • Use IAsyncEnumerable — stream large result sets instead of loading all into memory
  • Connection pooling — EF Core and ADO.NET handle this automatically with properly configured connection strings
  • Avoid N+1 queries — use eager loading or projections
  • Use Span<T> and Memory<T> for high-performance string and buffer processing
// Read-only query optimization
var products = await db.Products
    .AsNoTracking()
    .Where(p => p.IsActive)
    .Select(p => new ProductDto { Id = p.Id, Name = p.Name })
    .ToListAsync();

💼 Interview Tips for ASP.NET Core Roles

  • Know the pipeline order cold. Drawing the middleware pipeline on a whiteboard is a very common interview exercise.
  • Understand DI lifetimes deeply. Scoped vs Singleton mistakes are a common source of bugs — interviewers love this topic.
  • Be ready for "how would you secure your API?" — cover JWT, HTTPS, rate limiting, input validation, and CORS.
  • Know at least one real performance optimization you've done or studied — AsNoTracking, caching, async queries.
  • Mention .NET 8 features if possible — Keyed Services, Native AOT, Frozen Collections — these signal you stay current.

❓ Frequently Asked Questions

What .NET version should I study for interviews in 2026?

Focus on .NET 8 (LTS) as your primary reference. Most companies that are actively hiring are on .NET 6, 7, or 8. Understanding the concepts matters more than version-specific syntax, but being aware of .NET 8 features signals that you stay current.

Is knowing Entity Framework Core enough for database questions?

EF Core covers most interview questions, but also be familiar with raw ADO.NET and Dapper (a lightweight ORM). Senior roles may ask when you'd choose Dapper over EF Core — the answer is performance-critical, high-volume read scenarios.

Do I need to know Blazor for ASP.NET Core interviews?

Only if the job description mentions it. For backend/API roles, Blazor knowledge is a bonus, not a requirement. For full-stack .NET roles, knowing Blazor Server vs Blazor WebAssembly is increasingly valuable.

What is the difference between REST and gRPC — when is each asked?

This question appears in senior and microservices-focused interviews. REST is for external APIs, gRPC is for internal service-to-service communication where performance is critical.

✅ Key Takeaways

  • ASP.NET Core is cross-platform, high-performance, and fully open source — fundamentally different from the old ASP.NET Framework
  • The middleware pipeline order matters — always authenticate before authorizing
  • DI service lifetimes (Singleton, Scoped, Transient) are one of the most tested topics — know them deeply
  • JWT is the standard for stateless API authentication — understand how to generate and validate tokens
  • EF Core's AsNoTracking() and eager loading are key performance tools
  • Minimal APIs, Keyed Services, and Rate Limiting are .NET 6-8 features worth knowing for modern interviews


Found this helpful? Share it with a friend preparing for their .NET interview. Drop your questions in the comments below — we read and reply to every one.