Technical Interview Questions: Answers and Coding Examples
Use these technical interview questions to practice concise answers across coding, debugging, system design, computer science, and project discussions.

Technical interview questions test more than recall. You need to clarify ambiguity, explain your reasoning, choose appropriate trade-offs, and verify that your answer works. This question bank covers the full software engineer technical interview: fundamentals, coding, debugging, backend systems, design, and project depth.
How technical interview questions are evaluated#
Strong answers make your reasoning inspectable, not merely correct.
Interviewers use several question types because each exposes different skills:
- Knowledge questions test whether you can explain concepts such as threads, transactions, or indexes.
- Coding exercises test problem decomposition, implementation, complexity analysis, and verification.
- Debugging tasks test how you investigate incomplete or misleading evidence.
- System design prompts test requirement discovery, interfaces, trade-offs, and failure handling.
- Project deep dives test ownership, technical judgment, and your ability to learn from outcomes.
A reusable answer structure is:
- Clarify the goal. Restate the problem and ask about missing constraints.
- Name the options. Briefly identify plausible approaches.
- Choose and justify. Connect your choice to the stated constraints.
- Work through the details. Explain the algorithm, implementation, or architecture.
- Test the conclusion. Use examples, edge cases, complexity analysis, or failure scenarios.
- Acknowledge trade-offs. State what would make you choose differently.
This is not a scoring formula. Some questions need thirty seconds. Others need sustained discussion. The structure simply prevents you from jumping from prompt to conclusion without showing how you got there.
For a coding problem, clarify input size, duplicates, ordering, and expected output before typing. For design, clarify traffic shape, consistency requirements, and failure tolerance. For a project question, establish the context and your responsibility before describing the implementation.
Programming and computer science fundamentals#
Computer science interview questions usually test whether you can connect a definition to behavior in real software.
Processes versus threads#
A process has its own address space and operating-system resources. Threads within a process share memory but have separate stacks and execution state.
Separate processes provide stronger isolation. Threads make shared-state communication cheaper, but they introduce races and synchronization concerns.
A browser may isolate tabs or services in separate processes so one crash does not corrupt everything. A server might use threads for concurrent work that shares an in-memory cache.
Useful follow-ups include:
- What happens when two threads update the same value?
- When would multiple processes be preferable despite higher communication cost?
- How do CPU-bound and I/O-bound workloads affect the choice?
- What does a mutex protect?
Stack versus heap#
The stack stores function frames, local execution state, and return information. Allocation follows call order and is typically short-lived. The heap stores dynamically allocated objects whose lifetimes do not follow one call stack.
Recursion affects stack depth. Building a large object graph affects heap use.
def total(node):
if node is None:
return 0
return node.value + total(node.left) + total(node.right)This tree traversal uses call-stack space proportional to the tree height. A highly skewed tree can therefore exhaust the stack even when the algorithm is otherwise correct.
Follow-ups:
- Why can a memory leak involve heap objects that are still reachable?
- How would you replace recursion with explicit storage?
- Does every local variable necessarily live on the physical stack?
Mutable versus immutable data#
Mutable objects can change after creation. Immutable values cannot.
Immutability makes sharing safer because callers cannot change a value behind another caller’s back. Mutation can avoid repeated allocation and is often natural for buffers, caches, and stateful components.
def add_tag(tags, tag):
return (*tags, tag)Returning a new tuple prevents the function from modifying the caller’s collection. For a large buffer updated repeatedly, copying on every change may be the wrong trade-off.
Follow-ups:
- Can an immutable container hold a mutable object?
- Why are immutable values useful as hash-map keys?
- How would you prevent unintended mutation across an API boundary?
Recursion#
Recursion works well when a problem contains smaller instances of the same problem. Tree traversal, backtracking, and divide-and-conquer algorithms have this shape.
A correct recursive answer needs:
- A base case.
- Progress toward that base case.
- A clear meaning for the return value.
- A stack-depth analysis.
An interviewer may ask you to convert the solution to an iterative one or explain what happens on deeply nested input.
Common data structures#
Choose structures from the operations you need:
- A list or dynamic array supports indexed access and efficient appends.
- A hash map supports key-based lookup under normal hashing assumptions.
- A set tracks membership without storing a separate value.
- A stack handles last-in, first-out work.
- A queue handles first-in, first-out work.
- A heap repeatedly returns the smallest or largest priority item.
- A tree represents hierarchy or ordered search.
- A graph represents general relationships.
A good follow-up is rarely “define this structure again.” Expect questions about duplicate keys, ordering, memory overhead, adversarial input, or concurrency.
Data structures and complexity questions#
Data structure choices should follow the operations and guarantees the problem requires.
Use an array when you need indexed reads or compact sequential storage. Inserting near the front requires shifting later elements, so that operation is O(n).
A linked list supports O(1) insertion or removal when you already hold the relevant node. Finding that node remains O(n). It is not automatically better for frequent deletion.
A hash map offers expected O(1) lookup, insertion, and deletion when keys distribute well and resizing is controlled. Worst-case lookup is O(n) if many keys collide.
A binary heap supports access to the highest-priority item in O(1), with insertion and removal in O(log n). It does not provide efficient search for arbitrary values.
Balanced search trees keep ordered operations around O(log n). An unbalanced binary search tree can degrade to O(n).
Graphs are usually represented with adjacency lists when edges are sparse. An adjacency matrix uses O(V²) space but makes checking a particular edge O(1).
Big O examples#
A nested loop is not automatically O(n²). Count how often the inner body executes.
for left in range(n):
for right in range(left, n):
inspect(left, right)The inner calls form a decreasing series: n + (n - 1) + ... + 1. Time is O(n²). Extra space is O(1), assuming inspect does not allocate.
Sorting n items with a comparison sort generally costs O(n log n). If you sort and then scan once, the total remains O(n log n), not O(n log n + n) in simplified form.
Hash-based lookup is usually described as expected O(1). State that assumption when the guarantee matters. If the prompt requires strict worst-case bounds, a balanced tree may be more appropriate at O(log n).
Breadth-first and depth-first graph traversal both take O(V + E) with adjacency lists. Each vertex is processed once, and each edge is examined a bounded number of times. Their auxiliary space can reach O(V).
You can practice these choices across the data structure and algorithm pattern hubs.
Worked coding question: implement an LRU cache#
An LRU cache needs constant-time expected lookup, update, and eviction, which leads to a hash map plus doubly linked list.
The contract is:
get(key)returns the stored value or-1when absent.- Reading a key marks it as most recently used.
put(key, value)inserts or updates a key and marks it as most recently used.- When insertion exceeds capacity, evict the least recently used key.
- Capacity is positive.
A hash map finds a node by key. A doubly linked list stores recency order. Moving or removing a known node takes O(1). Sentinel nodes remove special cases at the ends.
class Node:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
if capacity < 1:
raise ValueError("capacity must be positive")
self.capacity = capacity
self.nodes = {}
self.least = Node()
self.most = Node()
self.least.next = self.most
self.most.prev = self.least
def _remove(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def _add_most_recent(self, node):
previous = self.most.prev
previous.next = node
node.prev = previous
node.next = self.most
self.most.prev = node
def _mark_recent(self, node):
self._remove(node)
self._add_most_recent(node)
def get(self, key):
node = self.nodes.get(key)
if node is None:
return -1
self._mark_recent(node)
return node.value
def put(self, key, value):
if key in self.nodes:
node = self.nodes[key]
node.value = value
self._mark_recent(node)
return
node = Node(key, value)
self.nodes[key] = node
self._add_most_recent(node)
if len(self.nodes) > self.capacity:
evicted = self.least.next
self._remove(evicted)
del self.nodes[evicted.key]Tests should verify state transitions, not just insertion:
def test_update():
cache = LRUCache(2)
cache.put(1, 10)
cache.put(1, 20)
assert cache.get(1) == 20
def test_eviction():
cache = LRUCache(2)
cache.put(1, 10)
cache.put(2, 20)
cache.put(3, 30)
assert cache.get(1) == -1
assert cache.get(2) == 20
def test_access_changes_recency():
cache = LRUCache(2)
cache.put(1, 10)
cache.put(2, 20)
assert cache.get(1) == 10
cache.put(3, 30)
assert cache.get(2) == -1
assert cache.get(1) == 10
def test_capacity_one():
cache = LRUCache(1)
cache.put(1, 10)
cache.put(2, 20)
assert cache.get(1) == -1
assert cache.get(2) == 20Each operation performs a fixed number of list changes and an expected O(1) hash-map operation. Strict hash-map worst case is O(n). Space is O(n), where n cannot exceed the cache capacity.
This is LeetCode 146, LRU Cache. The linked list pattern reference covers the pointer operations behind the design.
Debugging and code-review questions#
Debugging interview questions reward disciplined investigation more than fast guessing.
Use this sequence:
- Reproduce the failure.
- Reduce it to the smallest useful input.
- Identify the violated assumption.
- Patch the cause rather than the visible symptom.
- Add a regression test.
Boundary condition#
def contains(values, target):
for i in range(len(values) - 1):
if values[i] == target:
return True
return FalseThe loop never checks the final element. Reproduce it with contains([4], 4). Replace the range with range(len(values)), or iterate over values directly. Keep the one-element case as a regression test.
Shared state mutation#
def append_event(event, events=[]):
events.append(event)
return eventsThe default list is created once, so calls share state. Use None and create a list inside the function. Test two independent calls.
Asynchronous behavior#
async function loadUser(id) {
const response = fetch(`/users/${id}`);
return response.json();
}fetch returns a promise, so response.json is unavailable at that point. Await the response, then decide how non-success status codes should behave.
async function loadUser(id) {
const response = await fetch(`/users/${id}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}Resource cleanup#
def first_line(path):
file = open(path)
return file.readline()An early return leaves cleanup to implementation details. Use a context manager:
def first_line(path):
with open(path) as file:
return file.readline()When reviewing unfamiliar code, first recover its contract and data flow. Check correctness, failure handling, security boundaries, tests, and operational risks. Separate required changes from preferences. Rewriting code into your preferred style can introduce defects while hiding the issue you were asked to review.
Databases, APIs, and backend questions#
Backend technical screening questions test how components behave under concurrency and failure.
Indexes and transactions#
An index trades additional storage and write work for faster reads. For a query filtering by account_id and ordering by created_at, a composite index may help. Column order matters because the database must match the query’s access pattern.
Transactions group changes into one logical unit. If transferring a balance between accounts, both updates should commit or neither should. A strong answer also asks about concurrent transfers, row locking, isolation level, and retry behavior after conflicts.
Idempotency and retries#
An idempotent operation can be repeated without applying the effect twice. This matters because clients may retry after timing out without knowing whether the server completed the first request.
For payment or job creation, accept an idempotency key. Store the key with the resulting operation. A repeated request can return the prior result rather than create another record.
Follow-ups include key expiration, concurrent duplicate requests, and what happens when storage succeeds but the response is lost.
Pagination and caching#
Offset pagination is simple but can become expensive at large offsets. Concurrent inserts can also shift rows between pages. Cursor pagination uses a stable ordered key, such as (created_at, id), to continue after the last item seen.
Caching helps repeated reads, but every cache introduces invalidation and staleness decisions. State:
- What is cached.
- How entries are keyed.
- When they expire.
- How writes invalidate or update entries.
- What happens when the cache is unavailable.
API errors#
Use errors that callers can act on. Distinguish invalid input, missing resources, conflicts, authorization failures, rate limits, and internal failures.
Do not expose stack traces or internal database details. Include a stable machine-readable error code and a request identifier for investigation. Clarify which failures are safe to retry and whether retries need backoff or idempotency protection.
System design questions#
System design answers should move from requirements to interfaces, data, components, bottlenecks, and failures.
Consider a job queue.
First, clarify requirements:
- What creates jobs?
- Must jobs run in order?
- Can a job run more than once?
- How long can execution take?
- How should retries and cancellation work?
- What visibility do operators need?
Then define interfaces such as enqueue, claim, acknowledge, and fail. A job record might contain an identifier, payload reference, status, attempt count, availability time, and lease expiration.
The high-level components are:
- Producers submit jobs.
- Durable storage records them.
- Workers claim available jobs.
- A lease prevents healthy workers from processing the same job concurrently.
- Workers acknowledge completion or record failure.
- A scheduler makes delayed and retryable jobs available.
- Metrics expose queue depth, job age, retries, and worker health.
Now discuss failure. If a worker crashes, its lease eventually expires and another worker can claim the job. That implies at-least-once execution, so handlers should be idempotent. Poison jobs need bounded retries and a dead-letter path. A storage outage should reject or buffer writes according to the durability requirement, not silently lose them.
Likely bottlenecks include hot partitions, slow jobs occupying workers, large payloads, and retry storms. Partitioning can improve throughput, but strict global ordering then becomes difficult.
For longer worked interview material, browse the interview guides on the blog.
Technical project questions#
Technical project interview questions test whether you can explain decisions, ownership, and learning with enough detail to be credible.
Prepare concrete answers to prompts such as:
- Describe the architecture and why the team chose it.
- Explain a difficult production bug and how you isolated it.
- Walk through a migration that could not stop normal traffic.
- Describe an incident, its contributing conditions, and the follow-up work.
- Explain technical debt you accepted deliberately.
- Describe a design decision that did not work as expected.
- Identify a trade-off you would revisit with current knowledge.
Start with context. Name the users, constraints, and prior state. Then separate your contribution from the team’s work.
Useful phrasing is direct:
- “I owned the migration plan and rollback mechanism.”
- “A teammate implemented the storage adapter while I changed the read path.”
- “We chose cursor pagination after I tested the existing query under the expected access pattern.”
- “I proposed the first design. The team review identified a consistency problem, and we revised it together.”
Avoid turning a team result into a solo story. Also avoid hiding behind “we” when asked what you personally did.
For an unsuccessful decision, explain the original evidence, not just the bad result. State what assumption failed, how you discovered it, and what changed afterward. A credible answer might say that you optimized for simple deployment, later found that one workload needed independent scaling, and split that component after measuring queue delays and resource contention.
End with the current lesson. Make it specific enough to affect a future design, review, migration, or incident response.
Frequently asked questions
- How should you answer technical interview questions?
- Clarify the goal and missing constraints, identify possible approaches, justify your choice, work through the details, and test the conclusion. Finish by acknowledging trade-offs and explaining what could change your decision.
- What do technical interview questions evaluate?
- They evaluate knowledge, problem decomposition, implementation, complexity analysis, debugging, requirement discovery, technical judgment, and communication. Strong answers make the reasoning inspectable rather than merely presenting a conclusion.
- What is the difference between a process and a thread?
- A process has its own address space and operating-system resources. Threads within a process share memory but have separate stacks and execution state, making communication cheaper while introducing races and synchronization concerns.
- How do you implement an LRU cache?
- Combine a hash map for key-based node lookup with a doubly linked list for recency order. This supports expected O(1) lookup, update, and eviction, while strict hash-map worst-case time is O(n).
- What is a good process for debugging interview questions?
- Reproduce the failure, reduce it to the smallest useful input, identify the violated assumption, patch the cause, and add a regression test. This demonstrates disciplined investigation rather than fast guessing.
Keep reading

Coding Interview Questions With Worked Answers and Code
A pattern-based question bank with worked solutions, complexity analysis, testing guidance, and a framework for reasoning aloud.

Best Coding Interview Books for Pattern-Based Prep
Compare coding interview books by teaching style, depth, pattern coverage, and fit, then turn one primary book into an active practice plan.

Grokking the Coding Interview: A Practical Study Guide
Use Grokking the Coding Interview as a pattern-first framework built on cold attempts, retrieval, variations, and mixed practice.