Interview guides · Baazi Games
Baazi Games Software Engineer Interview Guide
Baazi Games Software Engineer interview guide: four reported rounds, LLD and system design questions, behavioral prompts and a 7-day prep plan.
The 60-second brief
Candidates describe Baazi Games as a company that runs real-money gaming platforms. The Software Engineer role they describe sits on the back-end side of those platforms: building and maintaining services that must stay correct and fast while many players act at once. The work they list includes distributed systems, real-time data processing and system optimization. Day-to-day duties include writing low-latency services, reviewing code, hunting bottlenecks and joining architecture discussions, and product managers are named as the people engineers turn requirements into technical specifications with. For preparation, this means the reported questions lean toward state held in memory, thread safety and clean interfaces.
This guide is for engineers interviewing for the Software Engineer role at Baazi Games. It covers the four reported stages (Initial Screening, Technical Rounds, Collaborative Discussion, System Design Focus), the six reported questions (three behavioral prompts about past projects and three design and storage questions), the requirements candidates describe (one object-oriented language among Java, C++ or Python, solid data structures, multi-threaded programming) and a seven-day plan. Gaming or real-time low-latency experience is described as a plus, not a requirement.
Candidates report four rounds over roughly 3-5 weeks: Initial Screening, Technical Rounds, Collaborative Discussion and System Design Focus, with low-level design and system design described as the main filters.
Focus areas
- Data Structures & Algorithms (DSA)
- System Design
- Low-Level Design (LLD)
Overview
The reported work is back-end engineering for real-money gaming, so the loop's themes follow from that: many players touching shared state at once, low-latency responses, and code that other engineers can maintain. Candidates list multi-threaded programming and data-structure depth as must-haves, and one object-oriented language (Java, C++ or Python). Pick one of those languages now and learn its concurrency tools well enough to use them without looking anything up: in Java that means ConcurrentHashMap, ReentrantReadWriteLock and synchronized blocks; in C++ std::mutex and std::shared_mutex; in Python threading.Lock, remembering that the GIL does not make a check-then-update sequence atomic.
Six questions are reported. Three are behavioral and ask about a project challenge, a change of technical strategy and disagreements over architecture. Three are design and storage questions: design an in-memory file system, structure the classes and interfaces of a gaming leaderboard, and weigh file-based storage against an in-memory approach for high-frequency data. Candidates also report two example scenarios under the design area: a file system with directory navigation and file creation, and a cache for frequently accessed game state. The common thread is choosing a data structure, protecting shared state, and defending the trade-off out loud.
Candidates describe the later interviews as discussions in which you share your reasoning and adjust when new information arrives. One reported example is being pushed from a file-based design toward an in-memory one. Prepare for that by keeping storage behind an interface from the start, so that swapping the backing store changes one class instead of the whole design, and by writing your assumptions down before you commit to a structure.
Because behavioral prompts sit beside the design work, plan time for both. Build two or three real project stories with the trade-off and the reason behind each architectural decision, since candidates are advised to be ready to discuss past projects in detail.
The Baazi Games interview process
Candidates describe four stages spread over roughly 3-5 weeks. The names below are the reported ones; what each stage asks beyond that is thinly reported, so the preparation advice targets the question types.
Initial Screening
Candidates report this as the opening stage, used to check basic qualifications and general fit for the role. Little else is reported about its format, so treat it as the place where your background has to be crisp and consistent with the requirements candidates describe: an object-oriented language (Java, C++ or Python), data structures and multi-threaded work. Expect to be asked about past projects, and candidates are advised to explain the trade-offs behind their decisions rather than just list technologies.
What to demonstrate
- Whether your background matches the described requirements: one object-oriented language, data structures, multi-threaded programming
- How clearly you describe a past project, including the trade-offs you chose
- Any gaming or real-time low-latency experience you can speak to
How to prepare
- Write a short summary of your most relevant back-end project: what it did, the part you owned, one concurrency or latency issue and how you handled it
- Decide which of Java, C++ or Python you will use throughout the loop and be ready to say why
- Have your answer to the biggest-project-challenge prompt ready in outline form
Technical Rounds
Candidates report technical rounds with senior or principal-level engineers, centred on design philosophy and coding standards. A coding stage inherently needs working code, so practise writing it in your chosen language with correct complexity analysis. Candidates also report that being able to explain your design philosophy matters, so prepare to justify choices such as which data structure you picked and why, not only to produce code that runs.
What to demonstrate
- Code quality: clean, readable code with its time and space complexity explained
- Design philosophy: why you chose a structure, pattern or synchronization mechanism
- Data-structure depth in your core language
How to prepare
- Write the in-memory file system class outline in your language and state the complexity of each operation
- Rehearse the architecture-disagreement prompt so your design reasoning comes out as evidence rather than opinion
Collaborative Discussion
Candidates describe this stage as a collaborative discussion where you share your thought process and adapt to new information. One reported example of new information is being moved from a file-based design to an in-memory one. Approach it as a working session: state your assumptions, think aloud, and when a requirement changes, say what it breaks, then adjust the design instead of defending the first version.
What to demonstrate
- How you reason out loud when the problem is open
- How you adapt a design when a requirement changes mid-conversation
- Trade-off analysis between storage approaches
How to prepare
- Practise the file-based versus in-memory storage question and change one requirement halfway through, such as durability or read frequency
- Write down your assumptions before choosing a structure, so a change has a clear thing to update
- Use the pivot-your-strategy prompt to rehearse how you describe a change of direction and its cost
System Design Focus
Candidates report that low-level design and system design are described as key filters for engineering roles, and this stage is named for that focus. Reported evaluation areas under it are concurrency control, data-structure selection and API and interface design, with example scenarios of an in-memory file system and a cache for frequently accessed game state. Prepare by practising the reported design questions as a set: the file system, the leaderboard class structure and the storage trade-off discussion.
What to demonstrate
- Clean abstractions and sensible use of design patterns
- Concurrency control for state shared between threads
- Choice of data structures for search, insertion and deletion
How to prepare
- Design the leaderboard classes with explicit interfaces and say the cost of submitting a score and of reading the top N
- Sketch a cache for frequently accessed game state: eviction, invalidation and what happens under concurrent readers and writers
- Write the methods an in-memory file system exposes (create, list, navigate) and decide the locking granularity
Where candidates lose points
Designing the in-memory file system as a tree of nodes with no thread-safety story
After the class sketch, say which operations mutate shared state (create, delete, move, write) and choose a locking scheme: one lock per directory, or a read-write lock for the whole tree. Name what happens when two threads create the same path at once.
Defending the first design when the interviewer changes the requirement, for example from file-based to in-memory storage
Put storage behind an interface from the start. When the requirement changes, say what it invalidates (durability, capacity, eviction), update the assumptions out loud, and swap the implementation behind the interface.
Calling in-memory storage faster and stopping there for high-frequency game data
State the cost too: the data vanishes if the process dies, and memory is bounded. Mention a write-ahead log or periodic snapshot for recovery, and an eviction or size bound for the cache.
Listing classes for the leaderboard without an interface contract or complexity
Define the operations first (submit score, get rank, get top N, update a score), give the complexity of each for your chosen structure, and say what a tie and a score update do to ordering.
Telling project stories in the team's voice with no trade-off, so your own decision cannot be identified
For each of the three behavioral prompts, outline the options you weighed, the one you chose and why, and one number you can defend. Use 'I' for your decisions.
Questions & practice
Discuss the trade-offs between using a file-based storage versus an in-memory approach for high-frequency data
Medium · System Design & Low-Level Design (LLD)
Discuss the trade-offs between using a file-based storage versus an in-memory approach for high-frequency data.
Approach
- Frame it as latency and throughput against durability. Reads and writes to RAM are orders of magnitude faster than a write that waits for fsync to reach disk, so a design that fsyncs every event caps throughput at what the device can flush; batching writes (group commit) raises throughput at the cost of a small window of unacknowledged loss.
- State what each option loses on a crash: pure in-memory state is gone when the process dies, so pair it with an append-only log plus periodic snapshots and replay the log tail on restart. Recovery time then grows with the log length since the last snapshot, which sets the snapshot interval. Redis's AOF with fsync every second is the standard example: up to about one second of writes at risk.
- Match the file layout to the access pattern: sequential appends are cheap on any disk, random in-place updates are not, which is why write-heavy stores use an LSM-tree (append, then compact) and read-heavy ones a B-tree. Reads of a hot file are often served from the OS page cache at close to memory speed, so file-based does not automatically mean slow reads.
- Cover capacity and runtime cost: memory is bounded and expensive, so in-memory data needs an eviction or expiry policy (LRU, TTL) and, on the JVM, very large heaps bring longer garbage-collection pauses; files hold far more data but need compaction and disk-space monitoring.
- Decide per data type by acceptable loss: a balance change or a settled game result must be durable before the client gets an acknowledgement, while a cached view of game state or a leaderboard can be rebuilt from the durable source. The usual answer is a hybrid: hot state in memory, every change appended to a log, history persisted asynchronously.
Follow-up
- The process crashes halfway through writing a record. How does recovery detect and handle the torn write?
- Your in-memory data set is growing past the RAM of one machine. What are your options and what does each cost?
- If fsync on every write is too slow but losing a second of writes is unacceptable, what would you do?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Identify the heaviest tenants in a five-minute window under memory pressure
Medium · top-k · heavy hitters · streaming
The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.
Approach
- Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
- Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
- State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
- Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.
Follow-up
- The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
- Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?
Hold a per-tenant active cap against concurrent creates
Hard · write skew · isolation levels · constraints
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
Design an in-memory file system.
Medium · System Design & Low-Level Design (LLD)
Approach
- Pin the API before modelling anything: mkdir(path), ls(path), addContentToFile(path, content), readContentFromFile(path), plus whether delete, move and relative paths ('.', '..') are in scope. Agree that paths are absolute and '/'-separated so parsing is a split on '/' with empty segments dropped.
- Model the tree with the Composite pattern: an abstract Entry (name, parent, timestamps) with Directory holding a map of name to child and File holding content (a StringBuilder or list of chunks). Resolving a path walks one map lookup per segment, so cost is O(depth) average with a HashMap.
- Pick the child map by what ls needs: a HashMap gives O(1) average lookup but ls must sort, O(k log k) for k children; a TreeMap gives O(log k) lookup and insert and returns names already sorted. ls on a file path returns just that file's name.
- Spell out the edge cases in code: mkdir creates missing intermediate directories (mkdir -p), writing a file where a directory of that name exists is an error, a path that runs through a file is an error, root '/' is valid for ls, and appending to a missing file creates it.
- Address concurrency explicitly: the simple correct version is one ReadWriteLock over the tree (many concurrent ls/read, exclusive mkdir/write). Finer locking is per-directory, and a move that touches two directories must take locks in a fixed order (for example by path string) to avoid deadlock.
Follow-up
- How would you implement move or rename so a reader never sees the entry in both directories or in neither?
- How would you return the total size of a directory without walking the whole subtree on every call?
- How would you snapshot this structure to disk so it survives a restart?
How would you structure classes and interfaces for a gaming leaderboard system?
Medium · System Design & Low-Level Design (LLD)
Approach
- Separate the interfaces from the storage: a Leaderboard interface with submitScore(playerId, score), getTop(k), getRank(playerId) and getAround(playerId, n); a ScorePolicy strategy (keep best score, or accumulate) so the board class does not hard-code the rule; a factory or registry that returns a board per game and per period (daily, weekly, all-time).
- Back it with two structures: a HashMap from playerId to current entry, and an ordered set keyed by (score descending, achievedAt ascending, playerId). An update removes the old entry and inserts the new one, O(log n) each in a balanced tree such as Java's TreeSet; reading the top k is O(log n + k).
- Call out that rank lookup is the hard query: TreeSet has no rank operation, so getRank is O(n) unless you use an order-statistic tree, a skip list with span counts (what Redis sorted sets do, giving ZREVRANK in O(log n)), or a Fenwick tree over score buckets when scores are bounded integers.
- Make ties deterministic (earlier achievement wins, then playerId) so two players never swap places between reads. Keep the map and the ordered set consistent under concurrent submits: a lock around the remove-plus-insert pair, or a single writer thread consuming a queue of score events.
- Reset periodic boards by creating a new board instance keyed by the period instead of clearing one in place, so reads of the closing period stay stable; publish rank-change events through an Observer if notifications are in scope.
Follow-up
- With millions of players on one board, how do you shard it and still answer a global top 100 and a single player's rank?
- How would you return an approximate rank for players far outside the top, and what error is acceptable?
- Where would you validate a submitted score before it reaches the board, and what happens to an entry later found invalid?
Edge instances grow 400 MB per hour until the nightly restart
Hard · memory leak · heap analysis · metric cardinality
Edge API instances start at 700 MB resident and grow about 400 MB/hour; a nightly rolling restart has hidden it for weeks. Growth continues unchanged when request rate halves overnight, p99 degrades in the last hours before an instance is recycled, and heap used immediately after a forced full GC rises monotonically. The service holds no product state. Name the discriminating measurement that separates the plausible causes, give the most likely cause, and give the fix and how you would verify it.
Approach
- Separate resident memory from live heap first, because they fail differently. Resident size can grow from fragmentation, native buffers or thread stacks while the heap is flat; heap used after a full GC rising monotonically is the measurement that says objects are reachable and not being released. You already have it, so this is retention, not fragmentation, and that closes off half the candidate list.
- Use the rate's independence from traffic as the discriminator. Growth that continues at half the request rate rules out per-request objects that are merely slow to collect and points at a structure that grows with distinct values observed rather than with call volume. Write the candidates that have that property: a metrics registry keyed on a high-cardinality label, an unevicted cache, an interner, a per-key lock map.
- Take two heap snapshots an hour apart and diff by retained size, reading the dominator tree, not by allocation count or instance count. Expect one root holding a map with millions of entries, then follow the reference chain to the code that inserts and never removes. Allocation profilers point at churn, which is the wrong signal here.
- The candidate that fits this service is an observability label carrying an identifier, such as a request path recorded before templating so that /v1/resources/48213 becomes its own metric series. That grows with distinct ids seen, is independent of rate, and explains the late p99 degradation, since GC cost rises with the size of the live set.
Follow-up
- Post-GC heap is now flat but resident size still creeps. What are you looking at, and does it matter?
- How would you have detected this before an OOM, given the nightly restart masked the trend?
Suggested practice checklist
The plan follows the reported shape: coding and data-structure depth first, then concurrency, the three design and storage questions, the three behavioral prompts, and a final mock where the requirement changes mid-answer.
Day 1 — Map the loop and choose your language
- Write the four reported rounds (Initial Screening, Technical Rounds, Collaborative Discussion, System Design Focus) and next to each the question types it is likely to include, marking the weakest
- Choose Java, C++ or Python and write a one-page list of its concurrency tools: locks, read-write locks, concurrent maps, atomic types
- List your two or three most relevant back-end projects, each with one trade-off you made
Deliverable: A one-page loop map with the weakest round marked, a concurrency-tools cheat sheet for your language and a list of three project candidates with their trade-offs.
Day 2 — Data structures and complexity in your language
- Rewrite one solution as production-quality code: clear names, edge cases handled, complexity written in a comment
Deliverable: Two solved exercises with stated complexity and one cleaned-up production-style solution.
Day 3 — Concurrency control
- For each, write the in-memory equivalent in your language: a version check with compare-and-set, and a counter guarded by a lock
- Write a short note on when you would pick a lock, a read-write lock or an atomic variable
Deliverable: Two worked exercises, two small in-memory concurrency snippets and a lock-selection note.
Day 4 — Design an in-memory file system
- Write the class outline: nodes for files and directories, a path resolver, and operations for create, make directory, list, read and write
- State the complexity of path lookup and listing, and decide the locking granularity
- Rewrite the storage layer behind an interface so a file-backed implementation could replace the in-memory one, and list what changes
Deliverable: A class outline for the in-memory file system with complexities, a locking decision and a storage interface.
Day 5 — Leaderboard design and storage trade-offs
- Define the leaderboard interfaces and classes: submit score, rank of a player, top N, update; choose a structure and give each operation's complexity
- Write a trade-off table of file-based against in-memory storage for high-frequency data: latency, durability, capacity, recovery, concurrency
Deliverable: A leaderboard interface and class sketch, a trade-off table, a cache sketch and a diagnosis of the memory-growth exercise.
Day 6 — Behavioral answers
- Write outlines for the project-challenge, strategy-pivot and architecture-disagreement prompts: situation in two sentences, options, decision, evidence, lesson
- Mark which of your stories are reused and make sure each of the three prompts has a different project behind it
- Say each answer aloud, replace 'we' with 'I' for the parts you did, and cut anything that does not carry a trade-off
Deliverable: Three outlined behavioral answers, each with a distinct story and one defensible number.
Day 7 — Mock with a changing requirement
- Run a mock of the file system or leaderboard design aloud; after the first design, change one requirement (add durability, or require the data to be in memory) and adapt without restarting
- Run the storage trade-off discussion once more, adding a concurrency scenario
- Write down two questions to ask your interviewers about the team's systems and how engineers review code
Deliverable: A recorded mock with the mid-answer change handled, plus two questions for interviewers.
Behavioural preparation
Candidates report three behavioral prompts, all about past project work: the biggest challenge, a change of technical strategy and architecture disagreements. Candidates are also advised to be ready to discuss past projects in detail, including trade-offs and the reasoning behind design decisions. For a back-end role like this one, pick stories involving concurrency, latency, scaling or storage choices, and keep each tied to a decision you made.
- Walk me through an architectural decision on a past project and why you chose it over the alternatives.
- Tell me about a time someone pointed out a flaw in your design and what you changed.
What was the biggest challenge you faced in your most recent project, and how did you resolve it?
Medium · Behavioral & Project Challenges
Approach
- Choose a challenge that is technical and back-end flavoured: a race condition, a latency regression, a memory or scaling bottleneck, or a data migration. A missed deadline is a weak choice because it gives you no design decision to explain.
- Open with the situation in two sentences, then spend most of the answer on the options you considered, the trade-off between them, and why you picked one. Candidates are advised to focus on the why behind architectural decisions.
- Separate your part from the team's. Say 'I' for what you decided, wrote or measured, and name teammates for what they did.
- Close with evidence from your own project: a before and after number, an error rate or a metric you tracked, plus what you would change. Use only figures you can defend if asked how you measured them.
Follow-up
- Which alternative did you reject, and what would have made you pick it?
- How did you confirm the fix worked and that it did not cause a regression elsewhere?
- What would you do differently if you faced the same problem now?
Describe a time you had to pivot your technical strategy due to unforeseen constraints.
Medium · Behavioral & Project Challenges
Approach
- Pick a pivot driven by a real constraint that surfaced after you started: a load figure, a memory limit, a dependency that could not do what you assumed, or a consistency need you missed. Say what the original plan was and why it looked right at the time.
- Explain how you spotted the problem: the signal, the test or the measurement. Being able to say how early you noticed it shows judgement.
- Describe the decision to change course: the options, the cost of the rework already done, and how you kept sunk cost from deciding for you.
- Say what the pivot cost you (schedule, a feature cut, extra complexity) and how you communicated it to teammates and to product. A story on a data-layer change, such as moving between storage approaches, mirrors the file-based to in-memory shift candidates report being asked about.
Follow-up
- Could you have found that constraint earlier, and what would you now check up front?
- How did you decide the earlier work was not worth saving?
- How did you bring the rest of the team and the product manager along with the change?
How do you handle disagreements regarding architectural choices within your team?
Medium · Behavioral & Project Challenges
Approach
- Use a specific disagreement between two real designs, such as a synchronous call against a queue, or a database read against an in-memory cache. A vague 'we sometimes disagree' answer shows no method.
- State the other person's position fairly before yours, then name the decision criteria you both cared about: latency, consistency, operability, cost of change.
- Replace opinion with evidence: a small prototype, a benchmark, a load test or a failure scenario you walked through together. Say what the evidence showed.
- Describe how the decision was made and recorded (you convinced them, they convinced you, or someone with ownership decided) and how you committed to the outcome afterwards. Include a case where you were the one who turned out to be wrong; a story where you are simply right sounds rehearsed.
Follow-up
- What if the person with final say disagreed with you after the evidence?
- Tell me about a time you were wrong in an architecture debate.
- How do you disagree with a more senior engineer without stalling the project?