What is the difference between a unit test and an integration test?
A unit test isolates one small piece of code (a class or function) and verifies its behaviour without touching external systems, using fakes or mocks for collaborators. An integration test exercises several components together, often with real infrastructure such as a database or message broker, to confirm they collaborate correctly. Unit tests are fast and numerous and form the base of the testing pyramid; integration tests are fewer, slower, and catch wiring, SQL, and serialization defects that unit tests with mocks cannot.
When should I choose a monolith over microservices?
Start with a monolith unless you have a concrete, present reason not to. A modular monolith is simpler to build, test, deploy, and debug, and it avoids the distributed-systems tax of network failures, eventual consistency, and operational overhead. Microservices pay off when you need independent deployment and scaling of parts, have teams large enough to own services autonomously, and have the platform maturity (observability, CI/CD, on-call) to operate many services. Splitting prematurely tends to convert simple in-process calls into hard distributed problems.
How do I avoid the N+1 query problem with an ORM?
The N+1 problem happens when loading N parent rows triggers one extra query per parent to fetch a related field, for a total of N+1 queries. Fix it by fetching the association in the same round trip: in JPA use a JOIN FETCH in the JPQL, an entity graph, or batch fetching, and verify by inspecting the generated SQL with logging enabled. Avoid relying on the default lazy loading inside a view or serializer, which is a common trigger; load exactly the data you need at the boundary instead.
What does SOLID actually buy me in practice?
SOLID is a set of heuristics that, applied judiciously, keep code easy to change. The most load-bearing in practice are the Single Responsibility Principle, which keeps unrelated reasons-to-change apart, and the Dependency Inversion Principle, which lets you depend on abstractions so implementations can be swapped and tested. Treat SOLID as guidance, not dogma: over-applying it produces needless indirection and interfaces with a single implementation, which violates simplicity principles like KISS and YAGNI.
Should I use checked or unchecked exceptions in Java?
Modern Java practice strongly favours unchecked (runtime) exceptions for most error conditions, because checked exceptions force callers to handle or declare them and tend to leak implementation detail up the call stack, polluting signatures. Reserve exceptions for genuinely exceptional, non-recoverable conditions, and return a result type or Optional for expected absence. Whatever you choose, never swallow an exception silently: log it with context or rethrow it wrapped in a domain-meaningful exception.
What is the difference between authentication and authorization?
Authentication answers "who are you?" by verifying an identity, for example via a password, token, or certificate. Authorization answers "what are you allowed to do?" by checking that the authenticated principal has permission for the requested action or resource. They are distinct concerns: a request can be authenticated yet unauthorized. A common mistake is enforcing only authentication and assuming any logged-in user may perform any action.
How should I version a REST API?
Pick a strategy and apply it consistently: URI versioning (for example /api/v1/...) is the most visible and cache-friendly, while header or media-type versioning keeps URIs clean at the cost of discoverability. Whatever you choose, treat the version as a contract governed by Semantic Versioning thinking: add fields and endpoints in a backward-compatible way within a major version, and only break compatibility in a new major version. Document deprecations and give clients a migration window before removing anything.
What makes an operation safe to retry?
An operation is safe to retry when it is idempotent - performing it more than once has the same effect as performing it once. In HTTP, GET, PUT, and DELETE are defined as idempotent while POST generally is not, so creating resources safely usually requires an idempotency key the server deduplicates on. Combine retries with exponential backoff and jitter to avoid retry storms, and protect downstreams with a circuit breaker so you fail fast when a dependency is clearly unhealthy.
When is it worth adding a cache?
Add a cache only after you have a measured latency or load problem and the data is read far more often than it changes. A cache introduces a second source of truth and the hard problem of invalidation, so weigh that cost against the benefit. Choose an invalidation strategy that matches your tolerance for staleness (TTL for "good enough" freshness, write-through or explicit eviction for stronger guarantees), and always design for a cache miss so correctness never depends on the cache being warm.
How much test coverage is enough?
Coverage percentage is a weak proxy: 100% line coverage can still miss behaviours, and a high number can hide tests that assert nothing meaningful. Aim for coverage of the behaviours that matter - business rules, edge cases, and error paths - rather than a single global threshold. A common pragmatic target is around 80% as a floor that flags untested areas, paired with code review that judges whether the tests actually verify behaviour rather than merely executing lines.
What is technical debt and how should I manage it?
Technical debt is the future cost incurred by choosing an expedient solution now instead of a better but slower one. Like financial debt, it charges interest: the suboptimal design slows every future change in that area. Manage it by making it visible (track it, label shortcuts deliberately), paying it down opportunistically when you next touch the code, and distinguishing deliberate, tracked debt from reckless, accidental debt. The danger is the silent, unmanaged debt that accumulates until the system resists change.
Why prefer immutability?
Immutable objects cannot change after construction, which eliminates an entire class of bugs caused by shared mutable state and aliasing, and makes objects inherently thread-safe. They are easier to reason about because a reference's value never changes underneath you, and they can be freely cached and shared. The cost is allocation of new instances on change, which is usually negligible and well worth the reduction in defects; Java records and copy-on-write updates make the pattern ergonomic.
What is the dependency rule in Clean Architecture?
The dependency rule states that source-code dependencies may only point inward, toward higher-level policy, never outward toward details. The domain layer (business rules) knows nothing about frameworks, databases, or the web; outer layers depend on inner ones through interfaces the inner layer defines. This keeps business logic testable in isolation and lets you swap a database or web framework without touching the core. Violations usually show up as domain classes importing JPA or Spring types.
How do I prevent SQL injection?
Always use parameterised queries or prepared statements so user input is sent as data, never concatenated into the SQL string. ORMs and query builders do this for you when you pass values as parameters rather than interpolating them. Validate and constrain input at the boundary as defence in depth, apply least-privilege database accounts, and never build dynamic SQL by string concatenation with untrusted input - that is the root cause of nearly every injection vulnerability.
What is the difference between concurrency and parallelism?
Concurrency is a structural property: a program is concurrent if it is composed of independently executing tasks whose lifetimes overlap, which lets it deal with many things at once even on a single core. Parallelism is an execution property: tasks literally run at the same instant on multiple cores. You can have concurrency without parallelism (interleaved tasks on one CPU) and you design for concurrency to manage many things, while you exploit parallelism to do many things faster.
Should business logic live in the database or the application?
Keep core business rules in the application's domain layer where they are testable, version-controlled, and expressed in the ubiquitous language, rather than scattered across stored procedures and triggers. Use the database for what it does best: enforcing data-integrity constraints (foreign keys, unique and check constraints, not-null) as a last line of defence. Heavy logic in the database is hard to test, deploy, and reason about, and it couples your rules to a specific engine; integrity constraints in the database, by contrast, protect you regardless of which code path writes the data.