Software Engineer interview questions
100 real questions with model answers and explanations for Middle candidates.
See a Software Engineer resume example →Practice with flashcards
Spaced repetition · Hunter Pass
Questions
I treat this as a deploy-linked performance regression and narrow it down with metrics, traces, and a controlled comparison.
- I start by comparing the deploy diff and correlating the latency jump to the exact deploy timestamp on the metrics dashboard, then check p50 versus p99 to see if it is a broad shift or a tail regression.
- Missing errors usually point at added work rather than failures, so I look for new synchronous calls, N+1 queries, a dropped cache, or a config change like connection pool size.
- I pull traces for slow requests to find which span grew, and if needed I run the old and new build side by side under the same load to confirm the regression.
Why interviewers ask this: The interviewer checks whether the candidate correlates the change to the deploy and reasons about added work versus failures instead of guessing randomly.
I distinguish CPU-bound from I/O-bound latency with profiles, utilization metrics, and traces before choosing a fix.
- I check whether CPU time tracks wall-clock time by sampling with a profiler like py-spy or perf: if on-CPU time dominates the request duration it is CPU-bound, and if the thread mostly waits on sockets or disk it is I/O-bound.
- Metrics help too, since high CPU utilization with low iowait suggests compute, while low CPU with long external-call spans in traces points to I/O.
- The fix differs: CPU-bound work wants algorithmic improvements, caching, or parallelism, while I/O-bound work wants batching, connection pooling, concurrency, or removing round trips entirely.
Why interviewers ask this: A strong answer distinguishes the two cases with evidence from profiling and metrics and maps each to a different class of fix.
I find memory leaks by measuring retained heap growth, tracing ownership, and proving the fix under sustained load.
- I capture heap snapshots at intervals and diff them to see which object types keep growing, using tools like Chrome DevTools or the Node heap profiler for JS, or tracemalloc and objgraph for Python.
- A steadily rising RSS with a growing retained set usually means references are held somewhere, often caches without eviction, event listeners that are never removed, or unbounded queues.
- Once I spot the growing type I trace its retaining path back to the owning structure, fix the retention, and confirm with a soak test that memory plateaus.
Why interviewers ask this: This evaluates whether the candidate uses heap diffing to find the growing objects rather than just restarting the service.
I treat a bug that vanishes under observation as a likely timing-sensitive Heisenbug and investigate it with low-overhead tools.
- This is a classic Heisenbug, and it usually means a timing or concurrency issue where logging shifts the schedule enough to hide a race, or the debugger serializes threads that normally interleave.
- It can also be uninitialized memory or a compiler optimization that a debug build changes.
- I proceed by adding low-overhead observation like counters, ring buffers, or a thread sanitizer instead of blocking logs, and I stress the concurrency with more threads and randomized delays to make the race reproducible.
Why interviewers ask this: The interviewer looks for recognition of concurrency and timing effects and low-overhead investigation rather than confusion.
I profile a realistic workload and optimize the widest meaningful hot paths rather than visually prominent frames.
- I run a sampling profiler under a realistic workload and read the flame graph, looking for the widest frames that represent the largest share of total time rather than the deepest stacks.
- Common mistakes include profiling a warmup or unrepresentative load, trusting a single run instead of aggregating, confusing self time with total time, and optimizing a function that looks hot but is only 2 percent of real traffic.
- I also watch for observer effect from heavy instrumentation and prefer sampling over full tracing for production-like numbers.
Why interviewers ask this: A strong answer reads flame graphs correctly and names the common profiling pitfalls that a weak one ignores.
I reproduce the failure under controlled load and then look for contention, saturation, and resource limits.
- I reproduce the load first with a tool like k6, Locust, or wrk so the issue becomes observable, then watch saturation signals: thread pool and connection pool exhaustion, lock contention, GC pauses, and queue depth.
- Problems that surface only under load are usually contention or resource limits rather than logic bugs, so I look at pool sizing, timeouts, and backpressure.
- Continuous profiling and per-request traces during the load test show me where time and waiting concentrate as concurrency rises.
Why interviewers ask this: This checks that the candidate reproduces the load and reasons about saturation and contention instead of logic bugs.
I trace one request end to end with a propagated trace identifier and fall back to correlated logs when necessary.
- I use distributed tracing with a correlation or trace id propagated across service boundaries, for example via OpenTelemetry and W3C trace context headers, so one request produces a single connected trace in Jaeger or Tempo.
- I search by that trace id to see the full span tree, spot which hop errored or timed out, and read tags for status codes and retries.
- If tracing is missing, I fall back to a request id logged at each service and stitch the logs together in the log aggregator.
Why interviewers ask this: The interviewer wants correlation ids and distributed tracing, not manual guesswork across services.
I use metrics to detect a problem, traces to locate it, and logs to explain the specific event.
- Metrics are cheap aggregate numbers over time that answer whether something is wrong and how often, so I use them for dashboards and alerts.
- Logs are discrete events with detail that answer what exactly happened in one case, useful once an alert fires and I need context.
- Traces show the path and timing of one request across components, answering where the latency or failure lives, and together they form a flow of alert on a metric, find the request with a trace, read the logs for detail.
Why interviewers ask this: A strong answer explains the distinct role of each and how they combine, not just definitions.
I confirm a dependency bug with an isolated reproduction before escalating it or introducing a workaround.
- First I isolate the suspect by writing a minimal reproduction that calls the library directly with the same inputs, ruling out my own wrapper code.
- Then I check the library's issue tracker and changelog for known bugs on my version, pin and bisect versions to find where behavior changed, and read the relevant source since it is available.
- If confirmed I file an upstream issue with the repro, apply a local workaround or patch such as a fork or a monkeypatch, and pin a safe version until a fix lands.
Why interviewers ask this: This evaluates disciplined isolation and a minimal reproduction before blaming a dependency.
I pair each database-timeout hypothesis with a concrete measurement and correlate the evidence with the timing of spikes.
- My first hypotheses are connection pool exhaustion, lock contention or long transactions, a missing index causing occasional full scans, and resource saturation on the database host.
- I test them with pool utilization metrics, the slow query log and pg_stat_activity for blocking, EXPLAIN ANALYZE on the suspect queries, and host-level CPU, IO, and memory graphs during the spikes.
- Intermittent usually means it correlates with something, so I line the timeouts up against traffic peaks, batch jobs, or lock waits to find the trigger.
Why interviewers ask this: The interviewer checks for concrete hypotheses paired with specific tools to confirm or reject each one.
I choose the test mix by risk, boundary complexity, feedback speed, and the criticality of user journeys.
- I follow the test pyramid idea: many fast unit tests for logic and edge cases, a solid layer of integration tests for the parts that touch real boundaries like the database or a queue, and a thin set of end-to-end tests for the few critical user journeys.
- The balance depends on where the risk and complexity live, so a service heavy on orchestration and I/O needs more integration tests than a pure calculation library.
- I optimize for confidence per second of runtime and avoid a top-heavy suite of slow, flaky end-to-end tests.
Why interviewers ask this: A strong answer reasons about risk and runtime cost rather than reciting the pyramid by rote.
I replace routine external API calls with controlled test doubles while retaining a smaller real integration check.
- I isolate the external call behind an interface and inject a fake in tests, and for HTTP I stand up a stub server like WireMock or a recorded fixture so no real network call happens.
- Contract tests or a recorded cassette keep the stub honest against the real API shape, and I run a small number of real calls in a separate, less frequent integration suite.
- This keeps the common test run fast, deterministic, and free of flakiness from rate limits or downtime.
Why interviewers ask this: This checks that the candidate isolates the dependency and keeps tests deterministic without real network calls.
Contract tests verify that independently deployed consumers and providers still agree on their interface.
- Contract tests verify that a provider and a consumer agree on the shape and semantics of their interface without spinning up both systems together, typically with a tool like Pact.
- The consumer records its expectations as a contract, and the provider replays them in CI to prove it still satisfies every consumer.
- They solve the problem of independently deployed teams breaking each other silently, catching incompatible changes before they reach production.
Why interviewers ask this: The interviewer wants the coordination problem contract tests solve, not just a definition of the tool.
Mocks make tests worse when they couple the suite to implementation details or misrepresent real dependencies.
- Mocks hurt when they over-specify interactions so the test asserts how the code works rather than what it produces, making every refactor break the tests.
- They also mislead when a mock drifts from the real dependency's behavior, giving green tests against a fantasy, and when you mock types you do not own so the stub encodes wrong assumptions.
- I prefer mocking at true boundaries, using real objects or fakes for in-process collaborators, and testing observable outcomes over call sequences.
Why interviewers ask this: A strong answer knows mocks can couple tests to implementation and drift from reality.
I restore trust in a flaky suite by separating known noise, fixing root causes, and making red builds meaningful again.
- I make flakiness visible by quarantining known-flaky tests into a separate lane and tracking a flaky rate, so the main build goes back to green-means-green and red actually blocks merges again.
- Then I fix root causes one by one: shared state and test order, real time and sleeps, network and timing races, and nondeterministic ordering.
- Restoring trust in the signal is the real goal, because a suite everyone ignores gives zero protection no matter how many tests it holds.
Why interviewers ask this: This evaluates both the technical fix and restoring trust in the build signal.
I make time an injected dependency so time-sensitive behavior can be tested deterministically without sleeping.
- I never read the wall clock directly in business logic, I inject a clock abstraction so tests can supply a fake or fixed time and advance it deterministically, using tools like libfaketime or a controllable clock in the language.
- For scheduled jobs I test the trigger logic separately from the schedule and verify boundaries like just before and just after expiry.
- This makes token expiry, retries with backoff, and cron windows fully reproducible without waiting real seconds.
Why interviewers ask this: The interviewer looks for an injected clock rather than sleeps and real time.
I add protection around risky legacy behavior first, then create seams that permit more focused tests and safe changes.
- I start with characterization tests that pin the current behavior, even if it is quirky, so I have a safety net before changing anything, following Michael Feathers' approach of finding seams to break dependencies.
- I add tests at the highest level I can reach cheaply first, often around a whole module, then push coverage down as I introduce seams and injection points.
- Priority goes to the code I am about to change and the highest-risk paths, not chasing a coverage number across dead code.
Why interviewers ask this: A strong answer names characterization tests and seams instead of chasing coverage blindly.
I test failure handling by deliberately creating faults and verifying the complete, correct recovery behavior.
- I deliberately force failures by injecting faults: making a mock throw, returning error responses, simulating timeouts and dropped connections, and feeding malformed input.
- I assert not just that it fails but that it fails correctly, with the right status, a clean rollback, no resource leak, and a useful message, and I check that retries and fallbacks actually trigger.
- Fault injection and tools like a fault-injecting proxy or chaos experiments help cover the paths that rarely run in normal operation.
Why interviewers ask this: This checks deliberate fault injection and asserting correct failure behavior, not just that it errors.
A fast suite minimizes expensive boundaries and preserves a short, reliable feedback loop for the team.
- Speed comes from keeping most tests in-process and pure, avoiding real I/O, sharing expensive setup, running in parallel, and using test containers only where a real dependency is essential.
- Slow suites get run less often, so bugs are found later and developers lose flow while waiting, which quietly erodes the whole feedback loop.
- I treat suite time as a first-class metric and budget it, since a suite that runs in seconds gets run on every save while a twenty-minute suite gets skipped.
Why interviewers ask this: The interviewer wants concrete speed levers plus why fast feedback protects the team.
I use TDD for understood, non-trivial behavior and relax it during genuine exploration or for disposable code.
- I use TDD where the behavior is well understood and the logic is non-trivial: write a failing test, make it pass simply, then refactor, which keeps the design testable and the feedback tight.
- I skip or relax it when I am exploring an unknown design or spiking a prototype where the interface is still fluid, and for trivial glue code or throwaway scripts.
- Even then I usually backfill tests once the shape settles, so the exploration does not ship untested.
Why interviewers ask this: A strong answer applies TDD where it pays off and is honest about when to skip it.
Locked questions
- 21
How do you design a module boundary so other developers can use it without reading its internals?
design - 22
You maintain a shared library used by several teams and need to ship a breaking change. How do you do it?
versioning - 23
How do you design good error responses for an API?
designapi - 24
How do you design list endpoints with pagination, filtering, and sorting that stay stable as data changes?
endpointspaginationdesign - 25
When would you choose gRPC or GraphQL over plain REST?
restgraphqlgrpc - 26
How do you make a write endpoint idempotent, and why does it matter?
endpointsidempotency - 27
What strategies exist for versioning an API, and what are their trade-offs?
versioning - 28
Where should input validation live in a layered application, and why?
validation - 29
Which design patterns do you actually use in day-to-day code? Give a concrete example.
design - 30
How do you refactor a 2000-line class safely while the team keeps shipping features around it?
refactoring - 31
What is the difference between concurrency and parallelism? Give a practical example of each.
concurrency - 32
How do you protect shared state accessed from multiple threads, and what are the costs of locking?
lockingconcurrency - 33
What conditions produce a deadlock, and how do you prevent deadlocks in application code?
locking - 34
How does async/await work under the hood in an event-loop runtime?
async - 35
What happens when you block the event loop or exhaust a thread pool, and how do you notice it?
event-loopconcurrency - 36
How do you implement timeouts and cancellation for long-running operations correctly?
resilience - 37
Two concurrent requests read the same balance and both write an update, losing one change. How do you fix this class of bug?
concurrency - 38
When do you enforce concurrency control in the database versus handling it in application code?
databaseconcurrency - 39
What is an atomic operation, and when are atomics enough instead of locks?
concurrency - 40
How do you make a background job safe to retry after a crash mid-execution?
resiliencejobs - 41
A SQL query got slow as the table grew. Walk me through finding and fixing the cause.
sqlqueries - 42
What is the N+1 query problem, and how do you detect it in a real codebase?
queriesn+1 - 43
How do composite indexes work, and why does column order in them matter?
indexesschema - 44
What do transaction isolation levels mean in practice, and which anomalies does each level prevent?
transactions - 45
When would you denormalize a schema, and what does denormalization cost you later?
schemadenormalization - 46
How do you change the schema of a large, busy table without taking the application down?
schema - 47
Why do applications use database connection pools, and what goes wrong when a pool is sized badly?
databasepooling - 48
How do access patterns drive the choice between a relational database and a key-value or document store?
database - 49
Why does OFFSET pagination degrade on large tables, and what do you use instead?
pagination - 50
How does lock contention in the database show up in application behavior, and how do you reduce it?
database - 51
How do you make sure backups actually work, not just exist?
backups - 52
Your app sometimes reads stale data right after a write because reads go to a replica. What is happening and what are your options?
replication - 53
What caching layers exist between a user and your database, and how do you decide where to cache?
cachingdatabase - 54
Compare TTL expiry, write-through, and explicit invalidation for caches. When does each fail?
caching - 55
What is a cache stampede, and how do you prevent it?
caching - 56
Why do average latency numbers lie, and what do p95 and p99 tell you that averages do not?
latency - 57
Server response times look fine, but users say the product feels slow. How do you improve perceived performance?
performance - 58
You doubled the number of instances but throughput barely grew. What do you check?
throughput - 59
Everything feels slow and everyone has a theory. How do you decide what to optimize first?
optimization - 60
How do payload size and serialization affect API performance, and what can you do about them?
serializationapiperformance - 61
How do you decide between keeping a monolith and splitting into services for a product at your team's scale?
monolith - 62
What problems does a message queue solve, and what new problems does it introduce?
queuesdata-structures - 63
What is eventual consistency, and where is it acceptable in a typical product?
consistency - 64
How do you design retries across service boundaries so they help instead of amplifying an outage?
design - 65
How do you keep feature flags from becoming permanent technical debt?
tech-debtfeature-flags - 66
You need to split one bounded area out of a monolith. How do you do it without a big-bang rewrite?
monolith - 67
Where do you put business logic in a layered application, and what goes wrong when it leaks into controllers or SQL?
sql - 68
How do you manage configuration and secrets across dev, staging, and production?
secretsconfig - 69
What is graceful degradation, and how do you design a feature to fail partially instead of completely?
design - 70
When do events and asynchronous messaging fit better than direct synchronous calls between components?
componentsasync - 71
What does a good CI pipeline contain, and in what order do you run the checks?
ci-cd - 72
How do you keep CI fast as the codebase and the test suite grow?
ci-cd - 73
Compare rolling, blue-green, and canary deployments. How do you pick one?
deployment-strategiesdeployment - 74
What makes a change easy to roll back, and what kinds of changes are hard to roll back?
rollback - 75
What is the difference between liveness and readiness checks, and what happens when you get them wrong?
health-checks - 76
What do you log at which level, and how do you avoid logs that are useless during an incident?
incidents - 77
Which metrics would you emit for a typical service, and how do you choose alert thresholds?
monitoringalerting - 78
What is distributed tracing, and what does it give you that logs do not?
distributed - 79
What are the trade-offs between trunk-based development and long-lived feature branches?
git - 80
How do containers change the way you build, ship, and run applications?
containers - 81
How do you review a large pull request, and when do you ask for it to be split?
code-review - 82
How do you deliver critical review feedback so it lands well and the code actually improves?
feedback - 83
A teammate consistently merges code with weak tests. How do you address it?
testing - 84
How do you keep review turnaround fast without rubber-stamping?
code-review - 85
What belongs in a pull request description, and why does it matter for the reviewer?
code-review - 86
How do you separate style preferences from substantive problems in code review, and how do you handle each?
soft-skillscode-review - 87
Design a rate limiter for a public API. Which algorithm and storage would you pick, and why?
rate-limitingdesignalgorithms - 88
Design a URL shortener at a high level. What are the key components and trade-offs?
componentsdesign - 89
How would you design a notification system that sends email and push messages without losing any?
system-designdesign - 90
A product needs search over its data. How do you choose between database full-text features and a dedicated search engine?
database - 91
Tell me about a project you owned end to end. How did you drive it from idea to production?
story - 92
How do you estimate a multi-week piece of work, and what do you do when the estimate turns out to be wrong?
estimation - 93
Tell me about a technical disagreement with a teammate that you resolved. What did you do?
storyconflict - 94
How do you make the case for paying down technical debt to a product manager focused on features?
tech-debt - 95
Describe a production incident you handled. What was your role, and what changed afterwards?
incidents - 96
You inherit a service that nobody left on the team understands. What do you do in the first month?
ownership - 97
As a mid-level engineer, how do you balance your own tasks with helping juniors who come to you?
mentoring - 98
Tell me about a time you shipped something you were not proud of because of a deadline. What happened next?
storyestimation - 99
How do you keep scope creep under control in a feature you own?
scope - 100
Where do you want to grow next as an engineer, and what are you doing about it?
growth