RSRobince Studio

Top 50 Project Architecture interview questions and answers

The 50 questions interviewers ask most about Project Architecture, with short, clear answers and code where it helps. The first 25 are the most common; the rest go deeper. Free, no sign-up.

All 50 questions

  1. Can you explain the architecture of your project?
  2. How would you explain your project architecture in an interview?
  3. What is layered architecture and why do you use it?
  4. What is the difference between MVC and MVVM?
  5. What is clean architecture and have you used it?
  6. How do you structure the folders in your project?
  7. What is separation of concerns and how do you apply it?
  8. How is the frontend structured in a typical REST-based project?
  9. How is the backend structured in your REST API?
  10. How does authentication work in your project?
  11. What is the difference between authentication and authorization, and where do you handle each?
  12. How do you handle errors in your application?
  13. How do you do logging in your project?
  14. How do you manage configuration and different environments?
  15. What is the repository pattern and why use it?
  16. How does your database layer work, and how do you handle schema changes?
  17. How do you manage state in your frontend application?
  18. How do you design your REST APIs?
  19. What is your testing strategy?
  20. How do you do CI/CD and deployment?
  21. How would you make your application scalable?
  22. How do you use caching in your project?
  23. What security measures have you built into your architecture?
  24. How do you monitor your application in production?
  25. Why did you choose this architecture, and what would you change if you started again?
  26. Why did you choose a monolith over microservices?
  27. When would you move from a monolith to microservices?
  28. How would you migrate a legacy monolith to a new architecture without stopping the business?
  29. How do services communicate in your architecture: synchronous or asynchronous?
  30. How do you handle a downstream service failure or timeout?
  31. What is idempotency and why does it matter in your API design?
  32. How do you keep data consistent across multiple services?
  33. How do you use message queues and background jobs?
  34. How do you handle database performance problems?
  35. Why did you choose SQL over NoSQL, or the other way around?
  36. How do you handle API versioning and backward compatibility?
  37. How do you protect your API from abuse and traffic spikes?
  38. How do you manage secrets and sensitive data in your architecture?
  39. How do you design for high availability and disaster recovery?
  40. What is the CAP theorem and how did it affect your design choices?
  41. How do you use an API gateway and what problems does it solve?
  42. How do you containerize and orchestrate your application?
  43. How do you deploy changes safely with feature flags or canary releases?
  44. How do you manage technical debt and refactor safely?
  45. How do you enforce code quality and architecture rules across a team?
  46. What are Architecture Decision Records and how do you use them?
  47. How do you handle concurrency problems such as two users updating the same record?
  48. How do you handle file uploads and large data in your architecture?
  49. How do you make your system observable and debug a production incident?
  50. How do you decide when to use a third-party library, service, or build it yourself?

1. Can you explain the architecture of your project?

My project is a three-tier application: a React single-page frontend, a Node.js REST API, and a PostgreSQL database, with Redis for caching. The frontend talks to the API over HTTPS using JSON, the API is split into routes, controllers, services, and repositories, and the database is accessed only through the repository layer. I usually start with the big picture, then walk through one request end to end, and finish with deployment and monitoring.

2. How would you explain your project architecture in an interview?

Start with the business problem, then draw the high-level components, then trace one real request through them. A good structure is: what the system does, the main parts (client, API, database, external services), how data flows, and why you chose that design. Mention trade-offs and one thing you would improve. This shows you understand the decisions, not just the code, and it keeps the answer under two minutes.

3. What is layered architecture and why do you use it?

Layered architecture splits the code into horizontal layers where each layer only depends on the one below it, typically presentation, business logic, and data access. I use it because it separates concerns, makes each layer testable in isolation, and lets me change one layer, such as swapping the database, without rewriting the rest. The main risk is leaking logic across layers, so I keep controllers thin and put rules in services.

src/
  routes/        HTTP endpoints
  controllers/   parse request, send response
  services/      business rules
  repositories/  database queries
  models/        entities and schemas

4. What is the difference between MVC and MVVM?

In MVC the controller receives input, updates the model, and picks a view, while in MVVM a view model exposes observable state and the view binds to it automatically. MVC suits server-rendered web apps such as Spring MVC or Rails. MVVM suits UI frameworks with data binding, such as Angular, Vue, WPF, or Android with ViewModel. MVVM reduces glue code and makes UI logic easier to unit test because the view model has no reference to the view.

5. What is clean architecture and have you used it?

Clean architecture organizes code in concentric circles where dependencies point inward: entities and use cases at the centre, and frameworks, databases, and UI on the outside. The business rules never import the web framework or ORM; they depend on interfaces that the outer layers implement. This makes the core easy to test and the infrastructure replaceable. I apply it in a lighter form: domain and use cases are plain classes, and repositories are injected as interfaces.

6. How do you structure the folders in your project?

I organize folders by feature or domain, with shared code in a common folder, so everything for one feature lives together. For example, users/ contains its routes, controller, service, repository, and tests. This scales better than grouping purely by technical type, because adding a feature touches one folder instead of five. Small projects can group by layer first, but I switch to feature folders once the codebase grows.

src/
  features/
    users/    routes, controller, service, repo, tests
    orders/
  shared/     middleware, utils, errors
  config/
  app.ts

7. What is separation of concerns and how do you apply it?

Separation of concerns means each module has one clear responsibility and does not know about unrelated details. I apply it by keeping HTTP handling in controllers, business rules in services, and SQL in repositories, and by keeping UI components free of API-calling logic through hooks or service modules. The payoff is that a change in one area, like a new validation rule, stays contained, and each part can be tested on its own.

8. How is the frontend structured in a typical REST-based project?

The frontend is a component-based app with pages, reusable UI components, an API client layer, and a state layer. Pages compose components, custom hooks or services call the REST API through one shared HTTP client, and that client handles base URL, auth headers, and error mapping. Keeping API calls out of components makes them easy to mock in tests and lets the backend contract change in only one place.

9. How is the backend structured in your REST API?

The backend follows a request pipeline: middleware handles logging, CORS, authentication, and validation, then the router sends the request to a controller, which calls a service, which uses repositories to reach the database. Errors flow to a central error handler that returns a consistent JSON shape. Configuration comes from environment variables, and the app is stateless so it can run as several identical instances behind a load balancer.

10. How does authentication work in your project?

Users log in with credentials, the server verifies the password hash, and it returns a short-lived access token, usually a JWT, plus a longer-lived refresh token. The client sends the access token in the Authorization: Bearer header, and middleware verifies the signature and expiry on every protected route. When the access token expires, the client uses the refresh token to get a new one. I store refresh tokens in HttpOnly secure cookies and hash passwords with bcrypt or Argon2.

Client -> POST /auth/login -> verify hash -> issue access + refresh
Client -> GET /orders (Bearer token) -> middleware verifies -> handler
Client -> POST /auth/refresh -> new access token

11. What is the difference between authentication and authorization, and where do you handle each?

Authentication proves who the user is, while authorization decides what that user is allowed to do. I handle authentication in middleware that validates the token and attaches the user to the request. Authorization is a separate check, using roles or permissions, done either in route-level middleware or inside services for resource-specific rules, such as "only the owner can edit this order". Never rely on the frontend alone for either.

12. How do you handle errors in your application?

I use a central error-handling strategy: services throw typed errors, such as NotFoundError or ValidationError, and one global middleware converts them into consistent HTTP responses with the right status code and a JSON body. Unexpected errors return a generic 500 message to the client while the full stack trace is logged server-side. On the frontend, I catch API errors in the HTTP client and show user-friendly messages, with an error boundary as a last resort.

13. How do you do logging in your project?

I use a structured logger such as pino or winston that writes JSON logs with a level, timestamp, and a correlation ID per request. Log levels separate debug noise from warnings and errors, and in production I log at info and above. Logs go to stdout and are collected by a platform like CloudWatch, ELK, or Datadog. I never log passwords, tokens, or personal data, and I include the request ID so one request can be traced across services.

14. How do you manage configuration and different environments?

I keep configuration out of the code and load it from environment variables, with separate values for development, staging, and production. A config module reads and validates them at startup so the app fails fast if something is missing. Secrets live in a secret manager such as AWS Secrets Manager or in the CI/CD platform, never in git. A .env.example file documents the required variables without real values, following the twelve-factor approach.

NODE_ENV=production
DATABASE_URL=postgres://user:pass@host:5432/app
JWT_SECRET=change-me
REDIS_URL=redis://cache:6379

15. What is the repository pattern and why use it?

The repository pattern wraps data access behind an interface with methods like findById and save, so the business layer never writes queries directly. It decouples services from the database or ORM, which makes switching technology easier and lets me mock repositories in unit tests. It also keeps queries in one place, avoiding duplicated SQL. The trade-off is extra code, so I skip it for very small apps where an ORM is already enough.

16. How does your database layer work, and how do you handle schema changes?

The application uses a connection pool and accesses the database through repositories, using an ORM or query builder such as Prisma, TypeORM, or Knex. Schema changes are made through versioned migration files that run in the CI/CD pipeline, so every environment stays in sync and changes are reviewable in git. I add indexes for common query filters, use transactions for multi-step writes, and make migrations backward compatible so deployments need no downtime.

17. How do you manage state in your frontend application?

I separate server state from client state. Server data, such as lists fetched from the API, is handled by a library like React Query or SWR that provides caching, refetching, and loading states. Local UI state stays in component state, and truly global client state, such as the current user or theme, goes in Context, Redux Toolkit, or Zustand. This avoids putting everything into one large global store.

18. How do you design your REST APIs?

I design resource-oriented endpoints with nouns and standard HTTP methods, such as GET /orders/42 and POST /orders, and return correct status codes like 201, 400, 401, 404, and 409. Lists support pagination, filtering, and sorting, and responses use a consistent JSON shape. I validate input at the edge, version the API with /v1 in the path, and document it with OpenAPI so frontend and backend teams share one contract.

GET    /v1/orders?page=2&limit=20
GET    /v1/orders/42
POST   /v1/orders
PATCH  /v1/orders/42
DELETE /v1/orders/42

19. What is your testing strategy?

I follow the testing pyramid: many fast unit tests for services and utilities, a smaller set of integration tests that exercise the API with a real test database, and a few end-to-end tests for critical user flows using Playwright or Cypress. Repositories and external services are mocked in unit tests. Tests run on every pull request in CI, and I track coverage as a signal, not a goal, focusing on business-critical paths.

20. How do you do CI/CD and deployment?

Every push triggers a pipeline that installs dependencies, runs linting, type checks, and tests, then builds a Docker image. Merges to the main branch deploy automatically to staging, and production is promoted after approval or automatically with checks. Deployments use rolling or blue-green strategies so there is no downtime, database migrations run as a pipeline step, and a failed health check triggers an automatic rollback.

push -> lint + test -> build image -> push to registry
     -> deploy staging -> smoke tests -> deploy production

21. How would you make your application scalable?

I keep the API stateless so I can scale horizontally by adding instances behind a load balancer, and I store sessions or tokens outside the server memory. Then I remove bottlenecks in order: add database indexes and read replicas, cache hot data, move slow work to background queues, and serve static assets through a CDN. I measure first with metrics, because scaling the wrong part wastes effort.

22. How do you use caching in your project?

I cache at several levels: a CDN for static assets, HTTP cache headers for cacheable responses, and Redis for frequently read data such as product lists or user profiles. I use a cache-aside pattern, where the app checks the cache first, falls back to the database, then stores the result with a TTL. The hardest part is invalidation, so I delete or update the key whenever the underlying data changes.

23. What security measures have you built into your architecture?

I apply security at each layer: HTTPS everywhere, password hashing with bcrypt or Argon2, input validation, and parameterized queries to prevent SQL injection. I escape output and set a Content Security Policy against XSS, configure CORS to allow only known origins, add rate limiting against brute force, and use security headers via helmet. Secrets stay out of the code, dependencies are scanned for vulnerabilities, and users get the minimum permissions they need.

24. How do you monitor your application in production?

I monitor three things: metrics, logs, and traces. Metrics such as request rate, error rate, latency percentiles, CPU, and memory are collected with Prometheus and Grafana or a service like Datadog. Centralized logs help debug failures, and tracing with OpenTelemetry shows where time is spent across services. Alerts fire on symptoms such as a rising 5xx rate, and a /health endpoint lets the load balancer remove unhealthy instances.

25. Why did you choose this architecture, and what would you change if you started again?

I chose a layered monolith because it was the fastest way to deliver with a small team, it was simple to deploy, and clear module boundaries leave room to extract services later. If I started again, I would add automated API contract tests and observability from day one, and I would define module boundaries more strictly so a future split into microservices would be easier. Admitting trade-offs shows good engineering judgment.

26. Why did you choose a monolith over microservices?

I chose a monolith because the team was small, the domain boundaries were still changing, and a single deployable unit is far cheaper to build, test, and operate. Microservices add network failures, distributed transactions, and heavy DevOps overhead that only pay off at larger scale or with many independent teams. I kept modules loosely coupled so I can extract a service later when a clear reason appears, such as different scaling needs.

27. When would you move from a monolith to microservices?

I would split only when a concrete pain appears: independent teams blocking each other on releases, one component needing very different scaling, or a part needing a different technology or fault isolation. Even then I extract one service at a time, starting with a well-bounded module such as notifications or payments. Splitting early, without those signals, usually creates a distributed monolith that is harder to run than the original.

28. How would you migrate a legacy monolith to a new architecture without stopping the business?

I would use the strangler fig pattern: put a proxy or API gateway in front of the legacy system and move one feature at a time to the new implementation, routing traffic gradually. Each step is small, releasable, and reversible. I add characterization tests around old behaviour first, run old and new side by side where possible, and retire legacy code only after the new path has proven stable in production.

Client -> Gateway -> /orders  -> new service
                  -> /reports -> legacy monolith

29. How do services communicate in your architecture: synchronous or asynchronous?

I use synchronous REST or gRPC when the caller needs an immediate answer, and asynchronous messaging when the work can happen later or several consumers care about the event. Synchronous calls are simple but couple availability, since one slow service slows its callers. Events through a broker such as Kafka, RabbitMQ, or SQS decouple services and absorb spikes, at the cost of eventual consistency and harder debugging.

30. How do you handle a downstream service failure or timeout?

I never call another service without a timeout, and I wrap calls with retries using exponential backoff and jitter, but only for idempotent operations. A circuit breaker stops calling a failing service so it can recover and so threads are not exhausted. Where possible I provide a fallback, such as cached data or a degraded response, and I alert on error rates so the failure is visible quickly.

const res = await fetch(url, { signal: AbortSignal.timeout(2000) });
// retry with backoff, then open the circuit breaker

31. What is idempotency and why does it matter in your API design?

An operation is idempotent when repeating it produces the same result as doing it once. It matters because networks fail and clients retry, so a payment or order request might arrive twice. GET, PUT, and DELETE are idempotent by design, while POST is not. For critical POST endpoints I accept an Idempotency-Key header, store the result under that key, and return the saved response on any repeat.

32. How do you keep data consistent across multiple services?

I avoid distributed transactions and use eventual consistency with the saga pattern: each service performs a local transaction and publishes an event, and if a later step fails, compensating actions undo earlier ones. To publish reliably I use the transactional outbox, writing the event in the same database transaction as the change. Consumers are made idempotent because messages can be delivered more than once.

33. How do you use message queues and background jobs?

I move slow or non-critical work out of the request path into a queue, such as emails, report generation, image processing, or webhooks. The API enqueues a job and responds quickly, and separate workers process it with retries, a dead-letter queue for repeatedly failing jobs, and idempotent handlers. This smooths traffic spikes and lets me scale workers independently of the API. Tools like BullMQ, SQS, or RabbitMQ fit well.

34. How do you handle database performance problems?

I start by measuring: find slow queries with the slow query log or monitoring, then inspect them with EXPLAIN ANALYZE. Typical fixes are adding or correcting indexes, removing N+1 queries with joins or batching, selecting only needed columns, and paginating large results. If reads dominate I add caching or read replicas. Only after those do I consider heavier options like partitioning or sharding.

EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC LIMIT 20;

35. Why did you choose SQL over NoSQL, or the other way around?

I choose based on the data and access patterns. I default to a relational database such as PostgreSQL when data is structured, relationships matter, and I need transactions and flexible queries. I choose NoSQL such as MongoDB or DynamoDB for very high write volume, simple key-based access, or highly variable document shapes. Many systems use both, for example SQL for core data and Redis or a search engine for specialized needs.

36. How do you handle API versioning and backward compatibility?

I try to make changes additive so existing clients keep working: add new fields rather than renaming or removing them, and never change the meaning of existing ones. When a breaking change is unavoidable I introduce a new version, usually /v2 in the path, run both versions during a deprecation period, communicate a sunset date, and watch usage metrics before removing the old one. Contract tests catch accidental breaks.

37. How do you protect your API from abuse and traffic spikes?

I apply rate limiting per user, API key, or IP, often with a token bucket stored in Redis so limits hold across instances, and return 429 with a Retry-After header. I also limit request body sizes, paginate results, set timeouts, and put a WAF or CDN in front to absorb bad traffic. Expensive endpoints get stricter limits, and autoscaling plus queues handle legitimate spikes.

38. How do you manage secrets and sensitive data in your architecture?

Secrets never go in git or images; they are stored in a secret manager such as AWS Secrets Manager, HashiCorp Vault, or the CI platform, and injected at runtime. I use separate secrets per environment, give each service least-privilege access, and rotate keys regularly. Sensitive data is encrypted in transit with TLS and at rest, passwords are hashed, and logs are scrubbed so tokens and personal data are never written.

39. How do you design for high availability and disaster recovery?

I remove single points of failure by running multiple stateless instances across availability zones behind a load balancer, with a database that has automatic failover or replicas. For disaster recovery I define the recovery time objective and recovery point objective with the business, take automated backups, and regularly test restoring them. Health checks, infrastructure as code, and runbooks let me rebuild an environment quickly in another region if needed.

40. What is the CAP theorem and how did it affect your design choices?

The CAP theorem says that during a network partition a distributed system must choose between consistency and availability. For money, inventory, or bookings I choose consistency and accept rejecting requests during a partition. For feeds, counters, or recommendations I choose availability and accept briefly stale data, using eventual consistency. Knowing which data needs which guarantee lets me pick suitable stores and replication settings per feature.

41. How do you use an API gateway and what problems does it solve?

An API gateway is a single entry point that routes requests to backend services and handles cross-cutting concerns in one place. I use it for authentication, rate limiting, TLS termination, request logging, and routing by path or version. This keeps those concerns out of every service and gives clients one stable address. The risks are that it becomes a bottleneck or single point of failure, so I run it redundantly and keep business logic out of it.

42. How do you containerize and orchestrate your application?

I package each service as a Docker image using a multi-stage build so the final image is small and contains no build tools, and I run it as a non-root user. Locally I use Docker Compose for the app plus its database and cache. In production I use an orchestrator such as Kubernetes or ECS to handle scaling, rolling updates, restarts, and service discovery, with readiness and liveness probes for safe deployments.

FROM node:20-alpine AS build
RUN npm ci && npm run build
FROM node:20-alpine
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]

43. How do you deploy changes safely with feature flags or canary releases?

I separate deploying code from releasing a feature. Feature flags let me ship code turned off, then enable it for internal users, a small percentage, and finally everyone, with an instant kill switch if metrics worsen. A canary release sends a small share of traffic to the new version and compares error rate and latency before continuing. Both reduce blast radius and make rollbacks quick, and I remove old flags to avoid clutter.

44. How do you manage technical debt and refactor safely?

I treat technical debt as a tracked backlog item, prioritised by how much it slows the team or risks production, and I allocate regular time for it instead of waiting for a big rewrite. Before refactoring I make sure tests cover the behaviour, then change in small steps that keep everything working, often by following the boy scout rule. Big rewrites usually fail, so I prefer incremental improvement shipped continuously.

45. How do you enforce code quality and architecture rules across a team?

I automate what can be automated: linters and formatters in pre-commit hooks, type checking, and CI checks that fail the build. For architecture, I use tools like dependency-cruiser or ArchUnit to forbid illegal dependencies, such as controllers importing repositories. Pull requests need review, with a checklist and a short template. Architecture decision records document why choices were made, so new members understand and follow them.

46. What are Architecture Decision Records and how do you use them?

An Architecture Decision Record, or ADR, is a short document that captures one significant technical decision, including the context, the options considered, the choice made, and its consequences. I store them as markdown in the repository next to the code, numbered and never deleted, only superseded by newer ones. They help new teammates understand why the system looks the way it does and prevent the same debates from repeating.

47. How do you handle concurrency problems such as two users updating the same record?

I use optimistic locking in most cases: each row has a version number or timestamp, and an update succeeds only if the version still matches, otherwise the client gets a 409 Conflict and can retry. For high-contention data I use pessimistic locking with SELECT ... FOR UPDATE inside a transaction. Atomic operations and unique constraints in the database are the final safeguard, since application checks alone can race.

UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 7 AND version = 3;

48. How do you handle file uploads and large data in your architecture?

I avoid streaming large files through the API server. Instead the API issues a short-lived pre-signed URL and the client uploads directly to object storage such as S3, then notifies the backend or triggers an event. I validate type and size, scan for malware where needed, store only metadata and the file key in the database, and serve downloads through a CDN. This keeps the API fast and cheap.

49. How do you make your system observable and debug a production incident?

I rely on structured logs, metrics, and distributed traces tied together by a correlation ID. During an incident I first check dashboards and alerts to find what changed, such as a recent deploy, then follow traces to the failing component and read its logs. I mitigate first, usually with a rollback or flag, and fix the root cause afterwards. A blameless post-mortem then turns the lessons into alerts, tests, or runbooks.

50. How do you decide when to use a third-party library, service, or build it yourself?

I build in-house only what differentiates the product and buy or reuse the rest, such as authentication, payments, email, or search. I evaluate maintenance activity, licence, security record, community size, and how hard it would be to replace. I wrap external dependencies behind my own interface so switching vendors does not touch business code, and I keep the dependency list small and regularly updated.

More Design & Architecture interview questions

OOP · Data Structures · Microservices · System Design · All interview questions