Node.jsBackendFull-Stack

Node.js Full-Stack Final Knowledge Test

TT
TopicTrick Team
Node.js Full-Stack Final Knowledge Test

Node.js Full-Stack Final Knowledge Test

You have covered 32 modules — from the V8 engine to Docker containers, from callbacks to Socket.IO rooms, from bcrypt to PM2 cluster mode. This final test checks that the knowledge has stuck.

Work through all 40 questions before checking your answers. Be honest with yourself — a wrong answer now is far better than a production bug later.

This is Module 33 of the Node.js Full‑Stack Developer course.


Section 1: Node.js Runtime & Core (Modules 1–8)

Q1. What are the three main components of the Node.js runtime?

Answer

V8 (Google's JavaScript engine — compiles and executes JS), libuv (C library — provides the event loop, thread pool, and async I/O), and the Node.js standard library (fs, http, path, crypto, etc.). → Module 1


Q2. List the six phases of the Node.js event loop in order.

Answer
  1. Timers — executes setTimeout and setInterval callbacks
  2. Pending callbacks — I/O callbacks deferred from previous cycle
  3. Idle / Prepare — internal use
  4. Poll — retrieves new I/O events; blocks if queue is empty
  5. Check — executes setImmediate callbacks
  6. Close callbacks — socket close events, etc.

→ Module 5


Q3. What is the difference between process.nextTick() and setImmediate()?

Answer

process.nextTick() fires before the event loop moves to the next phase — it is part of the microtask queue and runs immediately after the current operation completes. setImmediate() fires in the Check phase of the event loop — after I/O events have been processed. nextTick always runs before setImmediate. → Module 5


Q4. What does module.exports vs exports mean in CommonJS? When do they diverge?

Answer

exports is a reference to module.exports. They diverge when you reassign exports directly: exports = { fn } — this breaks the reference, and module.exports remains unchanged. Always use module.exports = { fn } or exports.fn = fn. Never reassign exports itself. → Module 4


Q5. What does "type": "module" in package.json change?

Answer

It switches the project to ES Modules — all .js files are treated as ESM (import/export syntax). Without it, .js files use CommonJS (require/module.exports). You can use .mjs for ESM files and .cjs for CJS files regardless of the type setting. → Module 4


Q6. What is the difference between npm install and npm ci?

Answer

npm install reads package.json, resolves versions, and updates package-lock.json. npm ci reads package-lock.json exactly and fails if it does not match package.json. npm ci is faster, reproducible, and used in CI/CD pipelines. Never use npm install in CI. → Module 8


Q7. What is the libuv thread pool used for, and how many threads does it have by default?

Answer

The libuv thread pool handles operations that cannot be done asynchronously at the OS level: DNS lookups (dns.lookup), file system operations (fs), crypto operations (crypto.pbkdf2, crypto.randomBytes). The default pool size is 4 threads. Set UV_THREADPOOL_SIZE to increase it (max 128). → Module 5


Q8. What is Promise.allSettled() and when should you use it over Promise.all()?

Answer

Promise.allSettled() waits for all promises to either resolve or reject, and returns an array of result objects ({ status: 'fulfilled', value } or { status: 'rejected', reason }). Use it when you want all operations to complete regardless of failures — e.g., sending notifications to multiple users where one failure should not prevent the others. Promise.all() rejects immediately if any promise rejects. → Module 6


Section 2: Express & API Design (Modules 9–13)

Q9. What is the order of Express middleware execution, and when does a request reach the error handler?

Answer

Middleware executes in the order it is registered with app.use(). A request reaches the error handler (a function with 4 parameters: err, req, res, next) when any middleware or route handler calls next(error) or throws in an async function (with async error wrapping). The error handler must be registered after all routes and other middleware. → Module 11


Q10. What is the difference between app.use('/path', router) and router.use(middleware) in Express?

Answer

app.use('/path', router) mounts the router at a path prefix — all routes in the router are prefixed with /path. router.use(middleware) applies middleware only to routes defined in that router. Router-level middleware does not affect other routers mounted on the main app. → Module 13


Q11. What HTTP status code should you return for a validation error (e.g., missing required field)?

Answer

422 Unprocessable Entity — the request was well-formed (valid JSON, correct Content-Type) but the content failed validation. Some APIs use 400 Bad Request for this, but 422 is more semantically precise. Always include a errors array in the body with field-level details. → Module 10


Q12. What is cursor-based pagination and when does it outperform offset pagination?

Answer

Cursor-based pagination uses the last item's ID or timestamp as a reference point: WHERE id > :cursor LIMIT 20. Offset pagination uses SKIP (page-1)*limit LIMIT limit. Cursor pagination does not degrade with large offsets — skipping 1 million records in MongoDB or PostgreSQL is slow, but querying from a cursor is O(log n). Use cursor pagination for large datasets, real-time feeds, and infinite scroll. → Module 10


Q13. In the Controller-Service-Repository pattern, what is each layer responsible for?

Answer
  • Controller: HTTP layer — parses req, calls a service, writes res. No business logic.
  • Service: Business logic — orchestrates data, applies rules, throws domain errors. No knowledge of req/res.
  • Repository (or Model): Data access — queries the database. No business logic.

→ Module 13


Section 3: Databases (Modules 14–16)

Q14. What does select: false do on a Mongoose schema field, and when do you need to override it?

Answer

select: false excludes the field from all query results by default. To include it explicitly in a query, use .select('+fieldName'). Use it for sensitive fields like password — this prevents accidentally returning them in API responses. → Module 14


Q15. What is the difference between findByIdAndUpdate() and save() in Mongoose regarding validators and middleware?

Answer

save() triggers all Mongoose validators and pre/post save middleware. findByIdAndUpdate() bypasses them by default — pass { runValidators: true } to enable validators, but pre-save hooks still do not run. Use save() when middleware must fire (e.g., password hashing). → Module 14


Q16. What does prisma migrate deploy do differently from prisma migrate dev?

Answer

prisma migrate dev creates new migration files from schema changes and applies them (for development). prisma migrate deploy applies existing migration files without creating new ones and without prompting — designed for production and CI/CD. Never run prisma migrate dev in production. → Module 15


Q17. What is mergeParams: true in Express Router and when is it required?

Answer

By default, a nested router does not have access to parent route parameters — Express resets req.params at each router boundary. express.Router({ mergeParams: true }) preserves parent params in nested routers. Required when a child router needs a parent's req.params (e.g., /workspaces/:workspaceId/tasks — the tasks router needs workspaceId). → Module 13 / → Module 16


Q18. What is the N+1 query problem? Give an example and the fix.

Answer

N+1 occurs when you fetch N records then make one DB query per record for related data: const posts = await Post.find() then for (post of posts) post.author = await User.findById(post.author) — N+1 total queries. Fix: await Post.find().populate('author', 'name email') — Mongoose fetches all authors in one additional query. In Prisma: prisma.post.findMany({ include: { author: true } }). → Module 16


Section 4: Caching & Auth (Modules 17–21)

Q19. Describe the cache-aside (lazy loading) pattern step by step.

Answer
  1. Receive request
  2. Check cache for the key → cache hit: return cached value immediately
  3. Cache miss: query the database
  4. Store the result in cache with a TTL
  5. Return the result

The application manages the cache explicitly. Data is only cached when first requested. → Module 17


Q20. Why should you never use KEYS * in production Redis, and what should you use instead?

Answer

KEYS * is O(N) and blocks Redis while it scans the entire keyspace — on a large dataset this can freeze Redis for seconds, affecting all clients. Use SCAN instead: it iterates in batches without blocking: redis.scanStream({ match: 'posts:*', count: 100 }). → Module 17


Q21. What are the three parts of a JWT and what does each contain?

Answer
  • Header: algorithm (alg: "HS256") and token type (typ: "JWT") — base64url encoded
  • Payload: claims — sub (subject/userId), role, exp (expiry), iat (issued at) — base64url encoded, not encrypted
  • Signature: HMAC of header + payload using the secret key — verifies integrity

→ Module 18


Q22. Why is sameSite: 'lax' used for the refresh token cookie in OAuth flows instead of 'strict'?

Answer

OAuth involves a cross-site redirect — the browser redirects from Google back to your domain. sameSite: 'strict' blocks cookies on cross-site navigations entirely, so the refresh token cookie would not be sent after the OAuth redirect. sameSite: 'lax' allows cookies on top-level navigations (following a link, form submission to the site) but blocks on cross-origin fetch/XHR, providing CSRF protection while supporting OAuth. → Module 21


Q23. What bcrypt cost factor should you use in tests and why?

Answer

4 (the minimum). In production you use 12 (300ms per hash). In tests you might run thousands of hash operations — at cost 12 that's minutes. Cost 4 produces a valid bcrypt hash in ~1ms, keeping tests fast without compromising correctness. Set BCRYPT_ROUNDS=4 in .env.test. → Module 19


Q24. What HTTP status codes should a 401 vs 403 response use, and what does each mean?

Answer
  • 401 Unauthorized: the request lacks valid authentication — no token, invalid token, or expired token. The client should authenticate and retry.
  • 403 Forbidden: the client is authenticated but does not have permission to perform the action. Authenticating again will not help.

→ Module 20


Q25. What is { session: false } in passport.authenticate() and why is it used in JWT APIs?

Answer

By default, Passport serialises the authenticated user into an HTTP session (cookie-based). { session: false } disables this — no session is created. In a JWT API you issue a token instead of a session, so sessions are unnecessary overhead. Always pass { session: false } when building a stateless JWT API with Passport. → Module 21


Section 5: Real-Time & Frontend (Modules 22–26)

Q26. Why must the Express catch-all route app.get('*', sendIndex) come after API routes?

Answer

Express matches routes in registration order. If the catch-all is registered first, it intercepts all requests — including API calls — and returns index.html. API routes must be registered before the catch-all so /api/* requests reach their handlers. → Module 22


Q27. What Vite configuration enables the development proxy so React can call the Express API without CORS errors?

Answer
js
// vite.config.js
server: {
  proxy: {
    '/api': { target: 'http://localhost:3000', changeOrigin: true }
  }
}

In production, React is served from the same Express server (same origin), so CORS is never needed. → Module 22


Q28. What is the difference between React Server Components and Client Components in Next.js?

Answer

Server Components (default) render on the server, can directly access databases and secrets, and ship zero JavaScript to the browser. Client Components ('use client' directive) render in the browser, handle interactivity (event handlers, hooks), and their JavaScript is included in the bundle. Server Components can render Client Components but not vice versa. → Module 23


Q29. What is mergeParams: true needed for in WebSocket rooms in Socket.IO?

Answer

This is an Express Router concept, not Socket.IO. In Socket.IO, rooms are managed with socket.join(roomId) and socket.to(roomId).emit(event, data) — no mergeParams needed. Socket.IO handles room routing internally. → Module 26


Q30. What does io.to(roomId).emit(event, data) vs socket.to(roomId).emit(event, data) send to?

Answer
  • io.to(roomId).emit() — sends to everyone in the room, including the current socket
  • socket.to(roomId).emit() — sends to everyone in the room except the current socket

Use socket.to() when broadcasting a user's own action (they already see it client-side). Use io.to() when you need to notify everyone including the sender. → Module 26


Section 6: Testing (Modules 27–28)

Q31. What is the correct way to assert that an async function throws a specific error in Jest?

Answer
js
await expect(asyncFn()).rejects.toThrow('message');
// or
await expect(asyncFn()).rejects.toMatchObject({ statusCode: 404 });

Do NOT await the function outside expect — the rejection must be inside expect() to be caught. → Module 27


Q32. What does jest.spyOn() do differently from jest.fn()?

Answer

jest.fn() creates a standalone mock function from scratch — no original implementation. jest.spyOn(object, 'method') wraps an existing method on an object, recording calls but still calling the original by default. You can then optionally override the implementation with .mockImplementation(). Always call spy.mockRestore() in afterEach to restore the original. → Module 28


Q33. Why do TDD practitioners write the test before the implementation?

Answer

Writing the test first forces you to design the interface before the implementation — you think about how the function will be called rather than how it will work. It ensures every line of code is covered by a test (because the test existed first), produces tests that describe intent rather than implementation details, and creates a fast feedback loop (Red → Green → Refactor). → Module 28


Section 7: Infrastructure (Modules 29–32)

Q34. What happens if you access process.env.MONGODB_URI in a route handler and it is undefined?

Answer

The application will throw an error the first time a request hits that route — potentially hours or days after startup. The fix is to validate all required env vars at startup (before the server starts listening) using Zod or Joi. If validation fails, the process exits immediately with a clear error listing which variables are missing. → Module 29


Q35. What is the purpose of the .dockerignore file and what must it always include?

Answer

.dockerignore prevents files from being sent to the Docker build context (and from being copied into the image). Always include: node_modules/ (Docker installs fresh), .env (secrets must not be in images), .git/, coverage/, and test files. A lean build context speeds up docker build and reduces the image attack surface. → Module 30


Q36. Why should Node.js in Docker run as a non-root user?

Answer

Running as root means that if an attacker exploits the application, they have root access inside the container — and potentially on the host if container isolation fails. Use USER node (the built-in non-root user in official Node images) or create a dedicated user: RUN adduser -S appuser && USER appuser. → Module 30


Q37. What does pm2 reload myapp do differently from pm2 restart myapp?

Answer

pm2 restart kills all processes and starts them again — causes downtime. pm2 reload performs a zero-downtime reload in cluster mode: it starts new workers, waits for them to be ready (listening on the port), then sends SIGINT to old workers one at a time. New requests go to the new workers while old workers drain their in-flight requests. → Module 32


Q38. What does instances: 'max' mean in PM2 ecosystem config?

Answer

PM2 spawns one worker process per available CPU core (os.availableParallelism() or os.cpus().length). On a 4-core server, instances: 'max' starts 4 Node.js processes all sharing port 3000. The OS load-balances incoming connections across them, fully utilising all CPU cores. → Module 32


Q39. What is the purpose of SIGTERM handling in a Node.js server?

Answer

SIGTERM is sent by the OS, PM2, or Kubernetes when requesting a graceful shutdown (e.g., during pm2 reload or a container stop). The handler should: stop accepting new connections (server.close()), wait for in-flight requests to complete, close database connections, and then call process.exit(0). Without a SIGTERM handler, Node.js exits immediately — leaving in-flight requests broken and potentially corrupting database operations. → Module 32


Q40. What is the difference between worker threads and the cluster module?

Answer

Cluster module: spawns multiple Node.js processes, each with its own event loop, V8 instance, and memory. Used for HTTP scaling across CPU cores. Workers communicate via IPC.

Worker threads: run within the same process, sharing memory (via SharedArrayBuffer). Used for CPU-intensive tasks that would block the event loop (image processing, crypto, data compression). Workers communicate via postMessage.

→ Module 32


Your Score

ScoreAssessment
38–40Exceptional — you are production-ready
33–37Strong — revisit 1–2 weak modules
28–32Good — review the sections you struggled with
Below 28Revisit the course before starting production projects

What's Next

You have completed the Node.js Full‑Stack Developer course. Here are the strongest next steps:

Build and ship something. Clone the Task Manager project (Module 24) from scratch without reference. Add a feature the course did not cover — notifications, file export, team analytics. Deploy it.

Go deeper on what interests you. Explore GraphQL with Apollo Server, gRPC for service-to-service communication, Kafka for event streaming, or Kubernetes for container orchestration.

Contribute to open source. Pick a Node.js project on GitHub and fix a bug or improve documentation. Reading real production codebases accelerates your growth faster than any course.


Node.js Full‑Stack Developer Course — Complete

Congratulations on completing all 33 modules. You now have the skills to build, secure, test, and deploy production-grade Node.js applications.