How to Ace Your Technical Interview (With Sample Questions and Answers)
· updated

Technical interviews are a skill separate from being good at the job, which is annoying but also good news: skills can be practised. This guide covers what each round is actually testing, a method that works for coding and design questions alike, and fifteen sample questions with the kind of answers that get a yes.
The short answer: to ace a technical interview, find out the format in advance and practise each round’s specific kind of problem out loud and under time. In the room, use the same method every time:
- Clarify the problem and its constraints.
- Talk through an approach before writing anything.
- Implement cleanly.
- Test with your own examples, including edge cases.
- Discuss complexity and trade-offs.
Interviewers score your reasoning and communication as much as your answer, so a partially solved problem explained well often beats a complete solution reached in silence. Bring two or three project stories with real technical depth, and prepare for the behavioural questions too; they’re in every loop.
What technical interviews actually test
Most loops have some combination of these rounds, and each tests something different:
| Round | What it tests | Typical length |
|---|---|---|
| Coding | Problem solving, code quality, testing habits, communication | 45–60 min |
| System design | Structuring ambiguity, trade-offs, scale, breadth | 45–60 min |
| Knowledge / fundamentals | Depth in your language, databases, networking, tools | Woven through |
| Practical / take-home | Realistic work: build or debug a small thing | 2–4 hours |
| Project deep dive | Whether you really did what your resume says | 30–45 min |
| Behavioural | Collaboration, ownership, learning from failure | 30–45 min |
Ask the recruiter which of these you’ll face and what each covers. They’ll usually tell you, and it’s the highest-value question you can ask before the loop.
The method: five steps for any technical question
Use this for coding, design and knowledge questions alike. Interviewers recognise it and score it well because it’s what good engineers do with real problems.
1. Clarify. Restate the problem in your own words. Ask about input size, value ranges, edge cases (empty input, duplicates, negative numbers, unicode), and what matters most: speed, memory, simplicity. Thirty seconds here prevents ten minutes solving the wrong problem.
2. Plan out loud. Say the obvious approach first, even if it’s slow: “brute force would be to compare every pair, that’s O(n²).” Then improve it: “a hash map would let me check each element once.” Get a nod before you write code. If you’re stuck, say what you’re thinking; silence is the worst outcome.
3. Implement cleanly. Real variable names, small functions, no cleverness. Narrate as you go. If you’d normally use a library, say so and ask whether to use it or write it.
4. Test it yourself. Before the interviewer does. Walk through a normal example, then the edge cases you identified in step 1. Finding your own bug is a strong positive signal; having the interviewer find it is a weak negative one.
5. Discuss. State time and space complexity. Name what you’d change with more time, and what would break at scale. This is where senior candidates separate from junior ones.
Sample coding questions and answers
The point of these isn’t to memorise them; it’s to see the method applied. Each one follows the same shape you should use in the room: clarify, state the approach, write it, give the complexity, and name the follow-up. The code is Python because it’s the most common interview language, but the ideas are the same in any language.
1. Find two numbers in an array that add up to a target
Clarify: Is the array sorted? Exactly one answer or could there be several? Return the values or the indices? Can the same element be used twice?
Approach: The brute force checks every pair, which is O(n²). The trick is to notice that for each number you only need to know whether target - number has already appeared. A hash map gives you that lookup in O(1), so one pass is enough.
def two_sum(nums, target):
seen = {} # value -> index
for i, n in enumerate(nums):
need = target - n
if need in seen:
return [seen[need], i]
seen[n] = i
return None
Complexity: O(n) time, O(n) space.
Follow-up to expect: “What if the array is sorted?” Then two pointers, one at each end, moving inward: O(n) time and O(1) space. Say that trade-off out loud before they ask.
2. Check whether a string is a palindrome, ignoring case and non-letters
Clarify: Does “non-letters” include digits? Should an empty string count as a palindrome (usually yes)? Any unicode concerns?
Approach: Two pointers from each end, skipping characters that aren’t alphanumeric, comparing lowercased values. Avoid the tempting one-liner (s == s[::-1] after filtering) unless you can also explain the pointer version; interviewers often ask for it.
def is_palindrome(s):
left, right = 0, len(s) - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
Complexity: O(n) time, O(1) extra space.
Edge cases to test out loud: "", "a", "A man, a plan, a canal: Panama", "!!!".
3. Find the first non-repeating character in a string
Clarify: Case-sensitive? Return the character or its index? What if none exists?
Approach: Two passes. First count every character; second, walk the string in order and return the first with a count of one. Two passes is fine; it’s still linear.
from collections import Counter
def first_unique(s):
counts = Counter(s)
for i, ch in enumerate(s):
if counts[ch] == 1:
return i
return -1
Complexity: O(n) time, O(k) space where k is the alphabet size.
Follow-up: “Can you do it in one pass?” Yes, with an ordered dictionary of first-seen positions, removing entries when they repeat, but it’s not clearer. Saying “I could, but two clear passes beat one clever one” is a good senior answer.
4. Merge two sorted lists
Clarify: Linked lists or arrays? Ascending order? Duplicates allowed? Should it be in place?
Approach: Two pointers. Always take the smaller head, then append whatever remains when one list runs out. For linked lists, a dummy head node keeps the code free of special cases.
def merge(a, b):
dummy = tail = ListNode(0)
while a and b:
if a.val <= b.val:
tail.next, a = a, a.next
else:
tail.next, b = b, b.next
tail = tail.next
tail.next = a or b
return dummy.next
Complexity: O(n + m) time, O(1) extra space for linked lists.
Follow-up to expect: “Merge k sorted lists.” Keep a min-heap of size k holding the current head of each list. Pop the smallest, push its successor. That’s O(N log k) for N total nodes.
import heapq
def merge_k(lists):
heap = [(node.val, i, node) for i, node in enumerate(lists) if node]
heapq.heapify(heap)
dummy = tail = ListNode(0)
while heap:
_, i, node = heapq.heappop(heap)
tail.next = tail = node
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
The index i in the tuple is a tie-breaker so Python never has to compare two ListNode objects when values are equal. Mentioning why it’s there is a small detail that interviewers notice.
5. Given a binary tree, return its values level by level
Clarify: What should an empty tree return? Left to right within a level? Do you need the levels separated or one flat list?
Approach: Breadth-first search with a queue. The trick for keeping levels separate is to record the queue’s length at the start of each iteration and process exactly that many nodes.
from collections import deque
def level_order(root):
if not root:
return []
result, queue = [], deque([root])
while queue:
level = []
for _ in range(len(queue)): # exactly one level's worth
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
Complexity: O(n) time, O(w) space where w is the widest level.
Follow-ups: zigzag order (reverse every other level before appending), right-side view (keep only the last value of each level), or the same thing with depth-first search and a depth parameter.
6. Detect a cycle in a linked list
Clarify: Can you modify the nodes? (If yes, marking visited nodes is trivial, so assume no.) Do you need to return where the cycle starts?
Approach: Floyd’s algorithm. A slow pointer moves one step, a fast pointer moves two. If there’s a cycle, the fast pointer laps the slow one and they meet; if there isn’t, the fast pointer hits the end.
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
return True
return False
Complexity: O(n) time, O(1) space, which is the whole point compared with a hash set of visited nodes.
Follow-up: “Where does the cycle start?” After the pointers meet, reset one to the head and advance both one step at a time; they meet at the cycle’s entry. If asked why: when they first meet, the distance from the head to the entry equals the distance from the meeting point to the entry (going around the loop). Being able to sketch that on the whiteboard is a strong signal.
7. Count the number of islands in a grid of 1s and 0s
Clarify: Is diagonal adjacency an island connection (usually no)? Can I modify the grid? How large can it be?
Approach: Scan every cell. When you hit an unvisited 1, you’ve found a new island: increment the count and flood-fill it (depth-first or breadth-first) so it’s never counted again. Marking visited cells by flipping them to 0 avoids a separate visited set, if modification is allowed.
def count_islands(grid):
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
count = 0
def sink(r, c):
stack = [(r, c)]
while stack:
r, c = stack.pop()
if 0 <= r < rows and 0 <= c < cols and grid[r][c] == "1":
grid[r][c] = "0"
stack.extend([(r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)])
for r in range(rows):
for c in range(cols):
if grid[r][c] == "1":
count += 1
sink(r, c)
return count
Complexity: O(rows × cols) time; O(rows × cols) worst-case space for the stack.
Why the explicit stack: a recursive flood fill on a 1,000 × 1,000 grid of all 1s recurses a million deep and crashes. Saying that unprompted is the kind of production awareness that separates candidates.
8. Given a list of intervals, merge the overlapping ones
Clarify: Are they sorted? Do touching intervals count as overlapping, for example [1, 3] and [3, 5]? Are they inclusive?
Approach: Sort by start. Walk through, keeping a current interval; if the next one starts before the current one ends, extend the current end; otherwise, push the current one and start a new one.
def merge_intervals(intervals):
intervals.sort(key=lambda x: x[0])
merged = []
for start, end in intervals:
if merged and start <= merged[-1][1]: # overlaps (or touches)
merged[-1][1] = max(merged[-1][1], end)
else:
merged.append([start, end])
return merged
Complexity: O(n log n) for the sort, O(n) space for the output.
Walk through out loud: [[1,3],[2,6],[8,10],[15,18]] → after [1,3] comes [2,6], which overlaps, so extend to [1,6]; [8,10] doesn’t overlap, push; [15,18] doesn’t overlap, push. Result [[1,6],[8,10],[15,18]].
9. Implement an LRU cache
Clarify: What operations? (Usually get(key) and put(key, value), both expected in O(1).) What happens on get of a missing key? Does get count as “use” (yes)?
Approach: You need two things: O(1) lookup by key, and O(1) “move this item to the front” plus “evict from the back”. A hash map gives the first; a doubly linked list gives the second. The map stores key → node; the list keeps recency order.
In Python, OrderedDict does both, and it’s fine to use it if you say what it’s doing underneath:
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cap = capacity
self.data = OrderedDict()
def get(self, key):
if key not in self.data:
return -1
self.data.move_to_end(key) # mark as most recently used
return self.data[key]
def put(self, key, value):
if key in self.data:
self.data.move_to_end(key)
self.data[key] = value
if len(self.data) > self.cap:
self.data.popitem(last=False) # evict least recently used
Complexity: O(1) for both operations.
Follow-up to expect: “Now do it without OrderedDict.” Have the manual version ready: a dict of key → node, and a doubly linked list with sentinel head and tail nodes so insert-at-front and remove-from-back never touch None. Be able to sketch the four pointer updates for “move to front” on the whiteboard.
10. Find the longest substring without repeating characters
Clarify: Return the length or the substring itself? ASCII only or unicode? Empty string returns 0?
Approach: Sliding window. Keep a left edge and a map of each character’s last-seen index. As the right edge advances, if the current character was seen inside the window, jump the left edge to just past its previous position.
def longest_unique_substring(s):
last_seen = {}
left = best = 0
for right, ch in enumerate(s):
if ch in last_seen and last_seen[ch] >= left:
left = last_seen[ch] + 1
last_seen[ch] = right
best = max(best, right - left + 1)
return best
Complexity: O(n) time, O(k) space for the alphabet.
Walk through out loud with "abcabcbb": the window grows to abc (length 3), then hits the second a, so left jumps to 1; it keeps sliding, never exceeding 3. The answer is 3. Tracing an example like this is what convinces the interviewer you understand the >= left check, which is the part most people get wrong.
If your role is data-heavy, expect SQL alongside these; our SQL interview questions covers the common ones.
Sample system design questions and answers
Design rounds are about structure and trade-offs, not the “right” diagram. (For a full study plan, see how to prepare for a system design interview.) Use the same shape every time:
- Requirements and scale. What must it do, what’s nice to have, and roughly how many users, requests and bytes? Ask; don’t assume.
- High-level design. The main components and how a request flows through them.
- Data model. The tables or key-value shapes, and which fields are indexed.
- The hardest part, in depth. Every system has one. Find it and spend most of your time there.
- Bottlenecks and scale. What breaks at 10× traffic, and what you’d change.
Here’s that shape applied to three common questions.
11. Design a URL shortener
Requirements: Turn a long URL into a short code; redirect visitors fast; optional expiry and click counts. Ask about scale: say 100 million new links a year and a read-to-write ratio around 100:1, which means reads dominate everything.
High-level design: An API service in front of a key-value store, with a cache in front of the store for redirects.
POST /shorten { "url": "https://example.com/very/long/path" }
-> 201 { "code": "aZ3kQ9" }
GET /aZ3kQ9
-> 302 Location: https://example.com/very/long/path
Data model: one table or key-value entry per link.
| Field | Notes |
|---|---|
code |
Primary key, 6–7 characters |
long_url |
The target |
created_at, expires_at |
For expiry and cleanup |
owner_id |
If users can manage their links |
clicks |
Optional; see below |
The hardest part: generating unique codes. Two approaches, and you should name both:
- Counter encoded in base 62. Keep a global counter; encode each new ID with the 62 characters
a–z,A–Z,0–9. Six characters gives 62⁶ ≈ 56 billion codes. Simple and collision-free, but sequential codes are guessable and the counter is a single point of contention at scale (fix: each server takes a block of IDs at a time). - Random string with a collision check. Generate six random characters, insert, retry on the rare collision. Unguessable, no shared counter, slightly more write work.
Scale and trade-offs: Cache hot codes in memory (most traffic goes to a small fraction of links). Return 301 if you want browsers to cache the redirect and reduce load, 302 if you want to count every click. If you count clicks, don’t write to the main table on every redirect; push an event to a queue and aggregate separately. Deleted or expired links should return 404 or 410, not a redirect to nowhere.
12. Design a rate limiter
Requirements: Allow each client at most N requests per window (say 100 per minute); reject the rest with a clear response; add minimal latency; work across many API servers so a client can’t dodge it by hitting a different machine.
High-level design: A small check at the API gateway or as middleware, backed by a fast shared store (Redis is the usual answer) so all servers see the same counts.
The hardest part: choosing the algorithm. Name the options and their weaknesses:
| Algorithm | How it works | Weakness |
|---|---|---|
| Fixed window | Count requests per calendar minute | Allows 2N requests across a boundary (100 at 12:00:59, 100 at 12:01:00) |
| Sliding window log | Store a timestamp per request, count those in the last 60 s | Accurate but memory grows with traffic |
| Sliding window counter | Weighted blend of the current and previous fixed windows | Approximate, but cheap and good enough for most APIs |
| Token bucket | Bucket refills at a steady rate; each request takes a token | Allows short bursts up to the bucket size, which is often what you want |
Token bucket is the most common production choice. The check-and-decrement must be atomic, which is why it lives in Redis (a Lua script or INCR with an expiry) rather than in application memory.
What to return: 429 Too Many Requests with a Retry-After header and the standard X-RateLimit-Limit, X-RateLimit-Remaining headers, so well-behaved clients can back off.
The question they’ll push on: “What if Redis is down?” Fail open (allow everything, protect availability) or fail closed (reject everything, protect the backend)? There’s no universal answer; say which you’d choose for this system and why. For a public API, failing open with an alert is the usual choice.
13. Design a news feed
Requirements: Users follow other users; each user sees recent posts from the people they follow, newest first (ranking comes later). Ask about scale: how many users, how many follows per user, and are there accounts with millions of followers? That last question decides the design.
High-level design: A post service (write posts), a follow graph (who follows whom), a feed service (assemble a user’s timeline), and a cache holding pre-built timelines.
The hardest part: fan-out. When someone posts, how does it reach their followers’ feeds?
- Fan-out on write (push). On each new post, look up the author’s followers and insert the post ID into each of their cached timelines. Reads are instant: just fetch the pre-built list. Writes are expensive, and a celebrity with 20 million followers turns one post into 20 million inserts.
- Fan-out on read (pull). Store nothing per follower. When a user opens the app, fetch recent posts from everyone they follow and merge them. Writes are trivial; reads are slow, especially for users who follow thousands of accounts.
- Hybrid. Push for ordinary accounts, pull for high-follower accounts, and merge the two at read time. This is what large social networks actually do, and it’s the answer interviewers are looking for.
Data model, roughly:
| Store | Shape |
|---|---|
| Posts | post_id → author_id, text, created_at |
| Follows | user_id → set of followed_ids (and the reverse index for fan-out) |
| Timelines | user_id → list of post_ids, capped at a few hundred, in a cache |
Scale and trade-offs: Paginate with a cursor (the last post ID seen), never with offsets. Cap cached timelines and rebuild from the pull path if a cache entry is missing. Ranking replaces “newest first” with a score, which is a separate service reading the same inputs. Deleted posts must be removed from every timeline they were pushed to, which is another argument for the hybrid model.
Sample knowledge questions
14. What’s the difference between a process and a thread?
Give the definition, then the consequence, then an example from your own language.
- Definition. A process has its own memory space; threads run inside a process and share its memory.
- Cost. Creating a process is expensive (new address space); creating a thread is cheap. Switching between threads is cheaper than switching between processes.
- Communication. Processes talk through explicit channels: pipes, sockets, shared memory you set up deliberately. Threads can read and write the same variables directly.
- The catch. That shared memory is exactly where race conditions come from. Two threads incrementing the same counter without a lock will lose updates. So threads need synchronisation: locks, atomics, queues.
- Isolation. One process crashing doesn’t take the others with it; one thread crashing usually kills the whole process.
Then relate it to your language. In Python, mention the global interpreter lock: threads help with I/O-bound work but not CPU-bound work, which is why you reach for multiprocessing for heavy computation. In Java or Go, talk about the runtime’s threading model. This last step is what turns a textbook answer into evidence that you’ve actually hit these problems.
15. What happens when you type a URL into a browser and press enter?
This is a breadth question; the interviewer wants to see the whole chain and will then go deep on whichever step you sound confident about. Walk it in order:
- Parsing. The browser works out whether it’s a URL or a search, extracts the scheme, host, port and path, and checks its HSTS list to decide whether to force HTTPS.
- DNS. The browser checks its own cache, then the operating system’s, then asks the configured resolver, which walks the root, TLD and authoritative servers if it doesn’t have the answer cached. Result: an IP address.
- TCP connection. A three-way handshake (SYN, SYN-ACK, ACK) with the server on port 443. If HTTP/3 is in use, this is QUIC over UDP instead.
- TLS handshake. The client and server agree on a cipher, the server presents its certificate, the browser verifies it against trusted certificate authorities, and they establish session keys. Everything after this is encrypted.
- HTTP request.
GET /path HTTP/1.1(or HTTP/2, multiplexed) with headers: host, cookies, accepted content types, user agent. - Server. Possibly a CDN edge first, then a load balancer, then the application server, which may query databases and caches before returning a response with a status code, headers and a body.
- Rendering. The browser parses HTML into the DOM, fetches linked CSS, JavaScript, images and fonts (each one repeating steps 2–6, often over the same connection), builds the render tree, lays it out, paints it, and runs the scripts, which may change everything.
Then stop and let them pick. “Which part would you like me to go deeper on?” is a good move here. For web roles, expect the follow-up to be about HTTP methods, status codes and caching headers; our what is API testing guide is a quick refresher on those.
The project deep dive
Almost every loop includes “tell me about a project you worked on”, and it’s where resume claims get checked. Prepare two or three projects and, for each, be ready to explain:
- The problem and why it mattered to the business or users.
- The design, including the alternatives you rejected and why.
- The hardest technical problem and how you solved it.
- What went wrong, and what you’d do differently.
- Your specific contribution, if it was a team project.
Interviewers push on details to see whether you really did the work. “We used a queue” invites “which one, why that one, what happened when it backed up?” Have the answers.
Take-home assignments
If there’s a take-home, treat it as a code review, not a puzzle. Read the brief twice. Build the core requirement first and make it work end to end before adding anything. Write tests. Add a short README explaining your decisions, what you’d do with more time, and how to run it. Stay within the suggested time; a polished small solution beats an ambitious unfinished one. And be ready to walk through it live, because you probably will.
Preparation plan
Four weeks out: get the format from the recruiter. Pick a practice platform and do two problems a day, timed, out loud. Re-read your own resume and refresh anything you claim.
Two weeks out: shift to mixed practice: one coding, one design or knowledge question per session. Rehearse your project stories with a friend or a recording. Do at least two mock interviews; the tools in best tools for interview preparation include several for mock practice.
The week of: taper. Review your notes, re-do the problems you got wrong, sleep. Prepare three questions to ask each interviewer.
The day: test your setup if remote. Have water, paper and a pen. Read each problem twice before speaking.
Mistakes that cost offers
- Jumping straight into code. Skipping the clarify and plan steps is the single most common failure.
- Going silent when stuck. Say what you’re considering. The interviewer can only help if they can hear you.
- Not testing your own code. Walk through it before you say you’re done.
- Ignoring the hint. If the interviewer suggests something, they’re steering you towards the answer. Take it.
- Bluffing on knowledge questions. “I haven’t used that, but here’s how I’d reason about it” scores far better than a confident wrong answer.
- Neglecting the behavioural round. It’s scored like the others. Our behavioral interview questions guide covers the format.
Getting to the technical interview
None of this matters until your resume gets you into the loop, and technical listings are specific about what they want: this language, that framework, this kind of system. Read each listing and lead with what it names. Tailr is a browser extension that does this from the job page you’re viewing: it tailors your resume to the specific listing, generates a matching cover letter and tracks the application, so the resume that reaches the hiring manager already speaks their stack. Try Tailr on the next engineering listing you open.
Related guides
- 50 Group Discussion Topics and How to Score in a GD
- 30 Most Asked HR Interview Questions and Answers
- 30 Situational Interview Questions With Sample Answers
- 25 Interview Tricks You Should Know Before Your Next Interview
Conclusion
Acing a technical interview comes down to a repeatable method, practised until it’s automatic: clarify, plan out loud, implement cleanly, test your own work, discuss trade-offs. Know the format in advance, practise each round’s kind of problem under time, bring project stories with real depth, and treat being stuck as a chance to show how you think rather than something to hide. The questions vary; the method doesn’t.
Frequently asked questions
01How do I prepare for a technical interview?
Find out the format first: ask the recruiter which rounds there are and what each covers. Then practise the specific kind of problem in each round out loud, with a timer, not just in your head. Revise the fundamentals for your role, prepare two or three project stories with technical depth, and rehearse the method: clarify, plan out loud, implement, test, discuss trade-offs. A few weeks of focused practice beats months of passive reading.
02What are the most common technical interview questions?
Coding rounds lean on arrays, strings, hash maps, two pointers, sorting, recursion, trees and graphs, and simple dynamic programming. System design asks you to design familiar systems such as a URL shortener, a rate limiter, a news feed or a file storage service. Knowledge questions cover your language, databases, HTTP and APIs, concurrency, and testing. Almost every loop also asks you to explain a project in depth.
03What should I do if I don't know the answer in a technical interview?
Say what you do know and reason from there out loud. Start with a brute-force approach and say it's a starting point, ask a clarifying question, or relate the problem to something similar you've solved. Interviewers score how you think under uncertainty; silence and bluffing both score badly, while honest reasoning that gets partway often scores well.
04How long should I practise before a technical interview?
For most mid-level roles, two to four weeks of an hour or two a day is enough if you already have the fundamentals: enough to re-learn the common patterns, do twenty or thirty timed problems, and rehearse two system design questions. If you're switching fields or haven't interviewed in years, give yourself six to eight weeks. Consistency matters more than total hours.
05Is it okay to ask questions during a technical interview?
It's expected. Clarifying the input size, edge cases, constraints and what matters most is part of what's being assessed, because that's what good engineers do with real requirements. Ask early, before you start coding, and ask again if something surprises you. The only questions to avoid are ones you could answer yourself by reading the problem carefully.
06Should I use AI coding tools in a technical interview?
Only if the interviewer says you can. Some companies now allow or encourage assistants and judge how well you direct and verify them; others forbid them and treat undisclosed use as cheating. Ask before the round starts. If they're allowed, use them the way you would at work: to speed up boilerplate, then read and test everything before you rely on it.