The cross-cutting questions every software engineering loop hits: coding fundamentals, design judgment, collaboration, and debugging under pressure.
Start by scoping impact: what is broken, for whom, since when. Correlate the start time with deploys, config changes, and dependency status before reading any code. Form one hypothesis at a time and test it with evidence (logs, metrics, a reproduction), rather than changing things speculatively. Communicate status at intervals even when the news is 'still investigating'. Once mitigated, separate the fix from the follow-up: root cause, detection gap, and prevention each get their own action item.
Concurrency is structuring a program to make progress on multiple tasks in overlapping time periods — the tasks may interleave on a single core. Parallelism is executing multiple tasks literally at the same time on multiple cores. A single-threaded async server is concurrent but not parallel; a data pipeline fanning work across 8 cores is parallel. The distinction matters because concurrency bugs (races, deadlocks) come from shared state and interleaving, not from core count.
Use STAR and pick a real disagreement with a resolution you can defend. Strong answers show you argued from evidence (benchmarks, failure modes, maintenance cost), escalated respectfully when needed, and then committed fully to the decision even if it went the other way — and they name what you learned about when you were right versus wrong.
Average case is O(1): the key is hashed to a bucket and the value read directly. It degrades toward O(n) when many keys collide into the same bucket — caused by a poor hash function, adversarial inputs, or a load factor that has grown too high without resizing. Well-implemented maps mitigate this by resizing when the load factor crosses a threshold and, in some standard libraries, by converting long collision chains into balanced trees, bounding worst-case lookups at O(log n). The follow-up interviewers want: resizing itself is O(n), so a single insert can be expensive even though inserts are O(1) amortized.
An idempotent operation produces the same result whether it is applied once or many times — setting a value is idempotent, incrementing it is not. It matters because networks fail ambiguously: a timeout does not tell you whether the request was processed, so safe systems retry, and retries are only safe against idempotent operations. In practice you make non-idempotent operations idempotent with an idempotency key: the client sends a unique ID per logical operation, and the server records processed IDs and returns the stored result on a duplicate. Payment APIs are the canonical example — double-charging on retry is exactly the failure this prevents.
The core rule: additive changes are safe, everything else needs a strategy. Adding optional fields or new endpoints does not break clients that ignore unknown fields; removing fields, renaming them, changing types, or tightening validation does. For breaking changes, version explicitly (URL path or header), run old and new versions side by side, and communicate a deprecation window with usage metrics telling you when the old version is actually safe to remove — not when the calendar says so. Also design for tolerance from day one: clients should ignore unknown fields, and servers should treat missing optional fields as defaults, so most evolution never needs a version bump at all.
Test where the risk lives, at the cheapest level that catches the failure. Pure logic with tricky edge cases gets unit tests: they are fast, precise, and pinpoint failures. Component boundaries — database queries, serialization, external API contracts — get integration tests, because that is where unit-level mocks lie to you. A small set of end-to-end tests covers the critical user journeys, kept small because they are slow and flaky by nature. The classic mistake in both directions: mocking so much that unit tests verify the mocks rather than the behavior, or leaning on end-to-end suites so heavy that nobody trusts or waits for them.
Correctness and safety before style: does the change do what it claims, what happens on the failure paths, does it handle the edge cases the tests skip, and could it corrupt data or break an interface consumers depend on. Second, comprehensibility: will the next engineer understand why this code exists — naming, structure, and whether the tests document the intent. Style nits come last and should mostly be delegated to formatters and linters rather than humans. A good signal to mention: distinguish blocking comments from suggestions explicitly, and review the tests as carefully as the code, because tests are where silent wrongness hides.
Never couple the schema change to the code change — use the expand/contract pattern. Expand first: add the new column or table in a backwards-compatible way (nullable or with a default), deploy code that writes to both old and new locations, then backfill historical rows in throttled batches. Once reads are switched to the new location and you have verified parity, contract: remove the old writes, and only later drop the old column. Every step must be individually deployable and reversible, because you will have old and new code running simultaneously during any rolling deploy. Also know your database's locking behavior — some ALTER operations lock the table, which is its own outage on a hot table.
Decompose until the pieces are small enough that you have done something similar before — estimation error lives in the lumps you have not broken open. Separate the known work from the risks: integration points, unfamiliar systems, and anything involving another team get explicit uncertainty, not padding hidden in the total. Give a range, not a number, and state the assumptions that would invalidate it. Then track actuals against estimates so your calibration improves. The behavior interviewers are probing for: what you do when the estimate turns out wrong — the honest answer is to communicate the slip as soon as you see it and renegotiate scope, not to work weekends to hide it.
It means the code can fail without surprising anyone. Concretely: errors are handled and surfaced rather than swallowed; there are logs and metrics good enough to debug an incident you have not imagined yet; alerts fire before users do; the deploy can be rolled back; configuration and secrets are managed outside the code; and load, timeout, and retry behavior have been thought through, not defaulted. It also includes the human side — documentation or runbooks sufficient for whoever is on call. The trap in this question is describing perfection: strong answers acknowledge that production-readiness is proportional to blast radius, and an internal tool needs less of it than a payment path.
First quantify 'sometimes': is it a fixed percentage of requests, specific users, specific times of day, or specific inputs? Tail latency is a distribution problem, so look at p95/p99, not averages, which hide it entirely. Common causes to hypothesize in order of likelihood: a cache miss path versus a cache hit path, garbage-collection or resource contention pauses, a query whose plan degrades on certain parameters, lock contention, a retry hiding a failing dependency, or a cold start. Tracing a slow request end to end beats guessing — find where the time actually goes before optimizing anything. Strong answers also mention the fix might be architectural (hedged requests, timeouts, precomputation), not a faster function.