All topics

QUESTIONS AND ANSWERS

Software Architecture

What is software architecture, and which decisions does it include?

It is the set of structures and decisions that guide how a system is organized and how it can evolve. It includes component responsibilities and boundaries, interfaces, data ownership and flow, dependencies, and relevant operational concerns.

It is more than a diagram, framework, or list of technologies. A useful architecture explains how its decisions address the system's requirements and what consequences they have for the people who build and operate it.

How do you choose between a monolith, a modular monolith, and microservices?

Start with the requirements, domain boundaries, team experience, and concrete deployment and scaling needs. A monolith often simplifies internal calls, transactions, and initial operations. A modular monolith keeps explicit boundaries within one deployment. Microservices can enable independent deployment and scaling, but add network communication, distributed failures, data coordination, observability, and operational work.

Do not choose microservices just because they are popular. If there is no clear need for independence between teams, components, or release cycles, start with a modular structure and revisit the decision when evidence emerges. Microsoft's architecture styles guide describes the benefits and costs of several styles.

How do you turn quality requirements into architecture criteria?

Turn vague statements into scenarios that can be observed and measured. For example: under a defined load, the service must keep latency below an agreed percentile; after a failure, it must recover within a target time and limit data loss.

Prioritize these scenarios with stakeholders and record their limits. Decisions often involve trade-offs among security, reliability, performance, cost, operations, and sustainability; evaluate them in the product's real context. The AWS Well-Architected Framework presents these concerns as pillars for reviewing an architecture.

How do you define module or service boundaries?

Group responsibilities that change together and belong to the same business capability. Aim for high cohesion within a boundary, few unnecessary dependencies across boundaries, and explicit contracts. In complex domains, a bounded context helps define where a particular model and vocabulary apply.

Avoid splitting services only by technical layers, such as creating one service per table or data-access layer. Revisit boundaries as the domain, requirements, and teams evolve. Microsoft explains this process in its guide to domain analysis and bounded contexts.

When does an event-driven architecture make sense?

Use it when several consumers need to react to changes, work can run asynchronously, or producers and consumers should be decoupled in timing and availability. In synchronous communication, a request typically waits for its dependency's immediate response; with events, a producer does not need to know each consumer.

Decoupling has costs: a consumer might receive duplicates, observe events in an unexpected order, or update after the producer. Design for idempotency, contract evolution, traceability, and failure handling. Microsoft's event-driven architecture guide describes these uses and challenges.

What is CQRS, and when is it worthwhile?

CQRS separates the models or paths used to change data from those used to query it. This separation can make complex write rules easier to manage and optimize queries with different needs; it can first be applied while keeping a single database.

Separating databases also increases synchronization work and can make reads temporarily stale. CQRS is unnecessary for a simple CRUD interface and does not automatically mean using Event Sourcing. Apply it when there is a concrete, measurable need, such as very different read and write load profiles. Microsoft's CQRS documentation explains its variations and trade-offs.

How do you design for failures in external dependencies?

Set timeouts for network calls. Retry only transient failures, with a finite attempt count and suitable delays, such as exponential back-off with random jitter. Check that an operation can be repeated without duplicating effects before retrying it.

A circuit breaker temporarily stops calls to a dependency that is persistently failing and allows recovery to be tested later. Use it when it helps contain cascading failures; it does not replace timeouts, error handling, or a recovery strategy. See Microsoft's guidance on transient faults and the circuit breaker pattern.

What should an architecture decision record contain?

A short record should document the context and problem, the decision, and its consequences. It can list considered alternatives, a date, and a status so the team understands what was accepted and why.

Record decisions that constrain structure, dependencies, interfaces, or quality attributes; there is no need to create one for every implementation detail. If new evidence changes the decision, write a new record that supersedes the old one and preserves the history. AWS guidance on architectural decision records explains their purpose and lifecycle.

How do you validate an architecture before committing to it?

First identify the riskiest assumptions and build a vertical slice or prototype that exercises them. Measure the priority quality scenarios under realistic loads; also exercise failures, recovery, security, deployment, and observability.

Compare results with the agreed targets and change the design if it falls short. Diagrams and arguments help communicate, but they do not replace evidence from testing the main risks. The AWS Well-Architected Framework recommends measuring the impact of architectural trade-offs.

How would you approach a system design question in an interview?

First clarify who uses the system, its main functions, expected scale, constraints, and quality goals. Estimate load and size when the numbers will affect a choice, then draw the components and data flows and explain the key trade-offs.

Explore the most relevant risks, such as consistency, failures, security, latency, and operations. Summarize your decisions and what you would validate with a prototype. Do not start by choosing a cloud or framework before you understand the problem.

What does CAP explain, and what should you not conclude from it?

CAP describes a limit for distributed systems during a network partition: a system must decide whether to respond with strong consistency or remain available while accepting that some responses might be stale or conflicting.

It does not mean that a system permanently chooses any two properties or that consistency or availability stop mattering when there is no partition. It describes trade-offs during communication failures; define which reads and writes each operation accepts and how conflicts are resolved.

How would you choose between a relational database and a NoSQL store?

Start with the queries and integrity rules the product needs, its data access patterns, transactions, volume, and scaling needs. A relational database often fits explicit relationships, flexible queries, and transactions across records. A document or key-value store can simplify known read patterns and scale some workloads, with different trade-offs in querying, consistency, and modeling.

Do not choose based on labels such as “SQL does not scale” or “NoSQL is faster.” Define the model around real queries, consistency and recovery requirements, and operations, then measure with representative data.

When would you scale vertically or horizontally?

Vertical scaling gives one instance more resources and is often simple, but it has limits and can preserve a common point of failure. Horizontal scaling adds instances to share load and capacity, but requires handling state distribution, balancing, coordination, and partial failures.

A service with no local state is usually easier to replicate; store shared sessions and data in appropriate systems. Measure the bottleneck first: adding instances does not fix a saturated database, global contention, or a slow dependency.

How do you distinguish an SLI, an SLO, and an SLA?

An SLI is a measure of a service property, such as the proportion of requests that are correct and fast enough. An SLO sets an internal target for that measure over a time window. An SLA is an agreement with defined consequences if a commitment is missed.

Choose indicators that reflect user experience and define how they are measured, the window, and the threshold. An error budget expresses how much SLO miss is allowed; it can guide the balance between shipping changes and reducing operational risk.

What does observability add to an architecture?

Observability helps infer what is happening inside a system from its signals. Metrics reveal trends and support alerts; logs explain specific events; traces show a request's path across components. Correlation IDs and consistent context help connect those signals.

Define the operational questions that need answers, instrument component boundaries, and avoid collecting unnecessary sensitive data. More telemetry without context, retention, or actionable alerts adds cost and noise instead of improving diagnosis.

When would you add a cache, and how would you handle stale data?

A cache can reduce latency and repeated reads when data is reused often and has a defined freshness tolerance. With cache-aside, the application checks the cache, loads from the source on a cache miss, and stores the result; on update, it invalidates or refreshes the entry.

Define expiration, size, invalidation, and behavior when the cache fails. Anticipate stampedes when a popular key expires and reads of stale data. Microsoft's guide to the cache-aside pattern explains its benefits and limits; the cache should not be the only copy of data that cannot be rebuilt.

How would you protect the system when load exceeds capacity?

Use limits per user or service so one client cannot consume all capacity; apply backpressure so producers slow down or pause when work accumulates. A queue can smooth bursts if work can be processed later and its size and age are controlled.

Prioritize essential operations, degrade optional features when appropriate, and return a clear response so clients know when to retry. Microsoft's guide to throttling describes limits as a way to sustain service objectives under load.

What does it mean for an operation to be idempotent, and why does it matter for retries?

An idempotent operation produces the same observable effect when the same request runs once or multiple times. This matters because a response can be lost after a server completes work; the client cannot always know whether it is safe to repeat the request.

For a write operation, you can accept an idempotency key, store its associated result, and return that result on a retry. Limit retention and bind the key to the user and request content. Retries should be finite, delayed with jitter, and used only for transient failures and operations that are safe to repeat.

How would you manage a transaction that spans multiple services?

Do not assume that a local database transaction can span distributed services with the same guarantees. A saga breaks the workflow into local steps and defines compensating actions if a later step fails. It can coordinate through events among participants or with an orchestrator.

Design each step for duplicates, retries, and uncertain outcomes; record progress and provide reconciliation when a compensation cannot undo the original effect. Microsoft's guidance on compensating transactions explains this approach and its limits.

What is Event Sourcing, and how is it different from publishing events?

Event Sourcing keeps the sequence of events that produced state changes as the primary record, then reconstructs state by applying them. Publishing an event in an event-driven architecture does not imply that this stream is the primary record: many systems publish notifications and keep current state in a conventional database.

Event Sourcing can help with audit or historical reconstruction when those requirements justify it. It adds event versioning, projections, replays, privacy, and operational complexity; do not adopt it just to “have events.”

How would you evolve an API contract without breaking consumers?

Prefer compatible changes: adding an optional field is usually safer than removing or changing the meaning of an existing one. Measure which client versions remain active, document the contract, and agree on a policy for notices and retiring versions.

If a change is incompatible, publish a new version and support the previous one for a defined transition. Validate contracts between producers and consumers to find incompatibilities before deployment. Microsoft's guide to API design and versioning summarizes these practices.

How would you apply threat modeling to a design?

Identify assets, trust boundaries, inputs, identities, and dependencies; then enumerate how each flow might be abused and the impact. Prioritize threats by likelihood and harm, and add verifiable controls such as server-side authorization, input validation, least privilege, request limits, and auditing.

Repeat the analysis when boundaries change or significant integrations are added. The OWASP threat modeling guide provides a process for structuring this review; threat modeling does not replace security testing or incident response.

Which architecture decisions help protect data?

Classify data by sensitivity, collect only what is needed, and define who can access it, for how long, and how it is deleted. Use encryption in transit and at rest where appropriate, manage keys separately, and prevent secrets or personal data from appearing in logs, traces, screenshots, or test copies.

Design controls for the full lifecycle: creation, use, transfer, backup, restoration, and deletion. Decisions depend on real obligations and threats; verify controls and permissions with tests, not just diagrams.

How would you reduce risk when releasing a new version?

Use gradual deployments when the system allows them: a canary sends a fraction of traffic to the new version; blue-green keeps two environments and switches traffic after checking the second. Compare agreed indicators, stop the rollout if they worsen, and keep a tested way to roll back.

Rolling back code does not automatically roll back data changes. Design compatibility between versions and migrations so a rollback or staged release does not leave data unreadable. The strategy should fit recovery goals and the cost of running parallel environments.

Which tests can verify architectural properties?

Unit tests validate local logic; integration tests exercise real or controlled dependencies; contract tests catch incompatible service changes; load and resilience tests reveal performance and recovery limits. No single test proves that an entire architecture meets every quality attribute.

Automate rules that must remain true—for example, module dependency boundaries or a latency budget—and run them in the appropriate pipeline. Keep scenarios representative and distinguish code failures from an unstable environment.

How would you migrate a database without stopping every application version?

For a compatible change, use an expand-and-contract sequence: first add the new schema so the existing version can tolerate it; then deploy code that reads or writes compatibly; migrate and validate data; finally remove old columns or paths when no consumers remain.

Make the migration repeatable or resumable, limit batch sizes, observe errors, and define how to stop or continue it. For high-risk changes, test restoration and recovery before touching production.

How would you check whether architecture boundaries remain useful?

Combine dependency and change reviews with operational signals: modifications that always need to deploy together, changes that require coordination across many teams, circular dependencies, failures that cross boundaries, and delivery times.

Automate testable architecture rules, revisit decision records when requirements change, and redraw the system using recent evidence. A useful boundary makes change easier and contains responsibilities; it is not an end in itself if the domain no longer supports it.