Google Backend Developer (Junior Level) Interview Preparation Guide
Google's backend developer interview process for junior-level candidates typically consists of a recruiter screening call, followed by 1-2 technical phone screens, and 4-5 onsite interview rounds. The process evaluates coding proficiency, system design thinking (at an introductory level), infrastructure knowledge, and cultural fit. Candidates should prepare for problems involving data structures, algorithms, API design, database fundamentals, and basic distributed systems concepts.
Interview Rounds
Recruiter Screening
What to Expect
Initial conversation with a Google recruiter to assess your background, experience, motivation for the role, and basic qualifications. This is a non-technical discussion focused on your resume, career trajectory, and cultural fit. The recruiter will also explain the interview process, timeline, and answer any questions about the role or company.
Tips & Advice
Be prepared to discuss your most relevant projects and why you're interested in Google. Have 2-3 concrete examples of challenges you solved. Ask thoughtful questions about the team, role responsibilities, and Google's backend infrastructure. Research the specific team you're interviewing for if possible. Be honest about your experience level as a junior candidate—recruiters expect to see learning potential rather than mastery.
Focus Topics
Google Cloud Platform (GCP) Familiarity
Discuss any experience with GCP services (Compute Engine, Cloud Functions, Firestore, Bigtable, etc.). Even basic familiarity is valuable.
Practice Interview
Study Questions
Learning Ability and Growth Mindset
Demonstrate your capacity to learn new technologies and systems. Prepare examples of when you learned something challenging and how you approached it.
Practice Interview
Study Questions
Experience with Backend Technologies and Projects
Prepare to discuss your hands-on experience with server-side development, APIs, databases, and any backend projects you've built. Highlight technologies used and impact of your work.
Practice Interview
Study Questions
Career Motivation and Interest in Backend Development
Articulate why you're interested in backend development and what attracts you to working at Google specifically. Discuss your understanding of what backend developers do.
Practice Interview
Study Questions
Technical Phone Screen - Coding
What to Expect
A 60-minute technical phone interview where you'll solve 1-2 coding problems using an online collaborative editor (like Google Docs or similar). The interviewer will ask you to write clean, working code to solve algorithmic problems focused on data structures and algorithms. You'll be expected to explain your approach, discuss time/space complexity, and handle follow-up questions.
Tips & Advice
Start by clarifying the problem statement and asking clarifying questions. Walk through your approach verbally before coding. Write clean, readable code with meaningful variable names. Test your solution with at least 2-3 test cases (normal case, edge cases). Discuss time and space complexity. If you get stuck, talk through your thought process—interviewers want to see your problem-solving approach, not just the answer. Practice using an online editor beforehand. Don't optimize prematurely; get a working solution first, then optimize if time permits.
Focus Topics
Linked Lists
Understand linked list operations, cycle detection, reverse, merge, and find middle. Practice both singly and doubly linked lists.
Practice Interview
Study Questions
Code Quality and Communication
Write clean, well-structured code with clear variable names and comments. Communicate your thinking process throughout the interview. Handle edge cases and write error-free code.
Practice Interview
Study Questions
Hash Tables and Dictionaries
Understand hash table operations, collision handling, and when to use hash maps for caching or frequency counting problems.
Practice Interview
Study Questions
Sorting and Searching Algorithms
Understand quicksort, mergesort, heapsort, and binary search. Know time/space complexities and when to use each algorithm.
Practice Interview
Study Questions
Trees and Graphs
Master tree traversals (in-order, pre-order, post-order, level-order), binary search trees, and basic graph traversals (BFS, DFS). Understand when to use each.
Practice Interview
Study Questions
Arrays and Strings
Master problems involving array manipulation, searching, sorting, and string operations. Understand two-pointer techniques, sliding windows, and prefix/suffix approaches.
Practice Interview
Study Questions
Technical Phone Screen - Backend Fundamentals
What to Expect
A 45-60 minute technical phone interview focused on backend-specific knowledge rather than pure algorithms. This round typically covers API design, database fundamentals, system architecture basics, or practical backend problems. You may be asked to design a simple REST API, discuss database schema design, or solve a problem specific to backend development.
Tips & Advice
Be ready to discuss trade-offs (consistency vs. availability, SQL vs. NoSQL, synchronous vs. asynchronous processing). Use diagrams or ASCII art to visualize your design. For junior level, focus on practical examples and clear explanations rather than exhaustive coverage. If you don't know something, say so and discuss how you'd approach learning it. Show familiarity with real-world backend systems and explain how different components interact.
Focus Topics
Asynchronous Processing and Message Queues
Understand the difference between synchronous and asynchronous processing. Know basic concepts of message queues (RabbitMQ, Kafka), event-driven architecture, and when to use each.
Practice Interview
Study Questions
Server-Side Caching Strategies
Understand in-memory caching (Redis, Memcached), cache invalidation patterns, TTL, and when caching is beneficial. Know the trade-offs between cache consistency and performance.
Practice Interview
Study Questions
Authentication and Security Basics
Understand JWT tokens, session management, password hashing, HTTPS, CORS, SQL injection prevention, and basic security principles.
Practice Interview
Study Questions
Database Fundamentals (SQL and NoSQL)
Understand relational databases (PostgreSQL, MySQL), NoSQL databases (MongoDB, Redis), schema design, indexing, query optimization, and when to use each type.
Practice Interview
Study Questions
RESTful API Design and HTTP Fundamentals
Understand REST principles, HTTP methods (GET, POST, PUT, DELETE), status codes, headers, and request/response formats. Know how to design clean, intuitive API endpoints.
Practice Interview
Study Questions
Onsite Round 1 - Coding Round
What to Expect
A 60-minute in-person or video interview where you'll solve 1-2 coding problems on a whiteboard or in an online editor. Similar to the phone screen but potentially at a slightly higher difficulty level. Interviewers will assess your problem-solving approach, code quality, and communication skills.
Tips & Advice
Treat this like the phone screen. Think aloud so the interviewer can follow your reasoning. Write code neatly (if on whiteboard). Ask clarifying questions to understand the problem completely. Start with a brute force solution and optimize if time allows. Test your code with examples. Discuss complexity trade-offs. Show confidence in your approach even if you're uncertain—interviewers value clear thinking over perfect answers.
Focus Topics
Backtracking and Recursion
Understand recursive problem-solving, backtracking patterns, and how to avoid infinite recursion. Practice problems like permutations, combinations, and N-Queens.
Practice Interview
Study Questions
Graph Problems (BFS, DFS)
Understand breadth-first search and depth-first search. Practice graph problems involving connectivity, shortest path, cycles, and topological sorting.
Practice Interview
Study Questions
Arrays and Strings
Master problems involving array manipulation, searching, sorting, and string operations. Understand two-pointer techniques, sliding windows, and prefix/suffix approaches.
Practice Interview
Study Questions
Linked Lists and Trees
Understand linked list operations (reversal, cycle detection, merge) and tree traversals (in-order, pre-order, post-order, level-order). Practice both to fluency.
Practice Interview
Study Questions
Onsite Round 2 - System Design (Junior Level)
What to Expect
A 45-60 minute interview focused on basic system design thinking. For junior level, this is not about designing complex distributed systems but rather understanding backend system components and how they interact. You may be asked to design a simple service (e.g., URL shortener basics, simple API backend, or data processing pipeline). Interviewers assess your ability to think through requirements, identify trade-offs, and propose reasonable solutions.
Tips & Advice
Start by asking clarifying questions about requirements, scale, and constraints. Draw a simple architecture diagram showing main components. Discuss technology choices and justify them. For junior level, focus on basic concepts: API layer, business logic, data storage. Avoid over-engineering. Discuss potential bottlenecks and basic scaling strategies. It's okay to admit uncertainty—interviewers expect junior candidates to know basics, not advanced distributed systems. Show your thinking process more than perfect solutions.
Focus Topics
Caching in System Design
Understand when and how to add caching layers (Redis, Memcached). Discuss cache invalidation strategies and cache-aside pattern.
Practice Interview
Study Questions
API Design and Rate Limiting
Understand RESTful API design principles, versioning strategies, error handling, and basic rate limiting concepts to prevent abuse.
Practice Interview
Study Questions
Database Selection (SQL vs. NoSQL Basics)
Understand when to use relational databases vs. NoSQL databases. Know basic trade-offs: ACID properties, consistency models, scalability characteristics.
Practice Interview
Study Questions
Scalability Concepts (Horizontal vs. Vertical Scaling)
Understand the difference between scaling up (vertical) and scaling out (horizontal). Know basic concepts of load balancing, replication, and sharding.
Practice Interview
Study Questions
Basic System Architecture and Components
Understand the basic layers of a backend system: API layer, business logic layer, data persistence layer. Know how these components interact and when to add additional layers.
Practice Interview
Study Questions
Onsite Round 3 - Backend Domain Expertise
What to Expect
A 60-minute technical interview focused on practical backend development skills and deeper understanding of specific technologies mentioned in the job description (Node.js, Python, Java, PostgreSQL, MongoDB, AWS/Azure, etc.). You may be asked to solve a practical backend problem, discuss architecture patterns, debug code, or discuss how you've implemented specific backend features.
Tips & Advice
This round tests practical backend skills, not just theory. Be ready to discuss real projects you've built. If asked to write code, focus on production-quality code with proper error handling. Understand the technologies you list in your resume deeply. Discuss trade-offs in your architectural decisions. Show you understand how backend systems handle real-world challenges like failure, concurrency, and data consistency. Ask clarifying questions about business requirements before diving into technical solutions.
Focus Topics
Infrastructure and Deployment Basics
Understand containerization (Docker), basic deployment concepts, environment configuration, and monitoring. Know how applications are deployed to cloud platforms.
Practice Interview
Study Questions
Building and Testing REST APIs
Understand how to build robust REST APIs, including input validation, error handling, pagination, and authentication. Know how to write unit tests and integration tests for APIs.
Practice Interview
Study Questions
Concurrency, Threading, and Async Patterns
Understand concurrency concepts in your chosen language (promises/async-await in JavaScript, threading in Java, asyncio in Python). Know when to use each pattern and how to avoid race conditions.
Practice Interview
Study Questions
Database Design and Optimization
Understand schema design, indexing strategies, query optimization, and common performance issues. Know how to diagnose slow queries and improve database performance.
Practice Interview
Study Questions
Server-Side Programming Language Proficiency
Demonstrate strong proficiency in at least one backend language (Node.js, Python, Java, etc.). Understand language features, standard libraries, package management, and best practices for that language.
Practice Interview
Study Questions
Onsite Round 4 - Behavioral (Google Culture Fit)
What to Expect
A 45-60 minute interview focused on assessing your alignment with Google's culture, values, and working style. Interviewers will ask behavioral questions about your experiences, how you work in teams, handle conflict, show initiative, and demonstrate learning. Google looks for engineers who embody their values: doing the right thing, collaboration, and continuous improvement.
Tips & Advice
Use the STAR method (Situation, Task, Action, Result) for behavioral questions. Prepare 4-6 strong stories showcasing different competencies. Be authentic and avoid rehearsed answers. Give specific examples with concrete outcomes. Discuss what you learned from experiences. For junior level, interviewers expect humility, willingness to learn, and ability to work well with others. Discuss how you've asked for help when needed. Show curiosity about problems and genuine passion for backend development. Ask thoughtful questions about team culture and values.
Focus Topics
Communication and Documentation
Discuss how you communicate complex technical ideas to non-technical stakeholders. Show you understand the importance of clear documentation and knowledge sharing.
Practice Interview
Study Questions
Adaptability and Continuous Learning
Share examples of when you learned new technologies or adapted to changing requirements. Show enthusiasm for learning and growing in your career.
Practice Interview
Study Questions
Initiative and Ownership
Share examples of when you took on additional responsibility, solved a problem without being asked, or improved a process. Show that you think beyond your assigned tasks.
Practice Interview
Study Questions
Teamwork and Collaboration
Prepare stories demonstrating your ability to work effectively in teams, contribute to group goals, and support colleagues. Discuss how you communicate and handle disagreements constructively.
Practice Interview
Study Questions
Handling Challenges and Learning from Failure
Prepare examples of technical or professional challenges you faced, how you approached them, and what you learned. Show growth mindset and resilience.
Practice Interview
Study Questions
Onsite Round 5 - Technical Depth / Manager Round
What to Expect
This final onsite round typically involves either a deeper technical discussion with a senior engineer or a conversation with the hiring manager. If technical, you may discuss complex projects you've built, your approach to architectural decisions, or dive deep into specific backend technologies. If with a manager, the focus is on your career goals, how you work, team dynamics, and long-term fit with the organization.
Tips & Advice
If technical: prepare to discuss your most complex project in detail. Be ready to explain trade-offs you made and why. Discuss what you'd do differently if you could redesign the system. For manager round: be genuine about your career goals and interests. Ask about the team dynamics, the manager's management style, and growth opportunities. Discuss how you prefer to work and what motivates you. Show interest in Google's products and culture. Listen carefully to understand the role and team before diving into your own talking points.
Focus Topics
Working Style and Team Collaboration
If in manager round, discuss how you prefer to work, your communication style, how you handle feedback, and your approach to teamwork.
Practice Interview
Study Questions
Code Quality and Best Practices
Discuss your approach to writing maintainable code, code reviews, testing strategies, and following best practices. Show you care about code quality.
Practice Interview
Study Questions
Career Growth and Learning Goals
If in manager round, discuss your career aspirations, areas you want to grow in, and how you approach continuous learning. Be genuine about your motivations.
Practice Interview
Study Questions
Performance Optimization and Debugging
Discuss real examples of performance issues you've debugged and fixed. Explain your methodology for identifying bottlenecks and improving performance.
Practice Interview
Study Questions
Complex Project Experience and Architectural Decisions
Be prepared to discuss your most complex backend project in depth. Explain the architecture, technology choices, challenges, and how you solved problems. Discuss trade-offs and what you'd do differently.
Practice Interview
Study Questions
Frequently Asked Backend Developer Interview Questions
Compare Five Whys, a fishbone (Ishikawa) diagram, fault-tree analysis, and causal-chain/timeline analysis as root-cause techniques. For each, describe what kind of incident it suits best, and its main weakness.
Sample Answer
Direct answer
Five Whys, fishbone (Ishikawa) diagrams, fault-tree analysis, and causal-chain or timeline analysis are all structured root-cause techniques, but they suit different incident shapes. Five Whys is fast and best for a single, mostly-linear chain of causation. Fishbone is best when you suspect several independent categories of cause (people, process, technology, environment) and want to brainstorm broadly before narrowing. Fault-tree analysis is best for complex, multi-path failures where you need to reason about combinations of conditions, not just one chain. Causal-chain or timeline analysis is best when the incident unfolded over a long period with many events, and reconstructing the sequence itself is most of the work.
Structured elaboration
- Five Whys. Strength: fast, requires no special tooling, good for straightforward incidents with a genuinely linear cause. Weakness: it forces a single narrative thread, so on an incident with multiple independent contributing factors it can stop at the first plausible-sounding chain and miss a second, unrelated gap that also mattered. Combining it with a causal-graph or fault-tree check on the resulting hypothesis (does this cause actually explain the full timeline, or just part of it) helps catch that failure mode.
- Fishbone (Ishikawa). Strength: structured brainstorming across categories (commonly people, process, technology, environment) surfaces candidates you might not think of starting from a single chain. Weakness: it's a divergent tool, good for generating hypotheses, but it doesn't by itself tell you which candidate cause is actually correct; you still need evidence to narrow down.
- Fault-tree analysis. Strength: models AND/OR combinations of conditions, so it's the right tool when the incident required several things to go wrong simultaneously (a database failover only failed because BOTH the standby was on an incompatible version AND the health check didn't catch the mismatch). Weakness: more effort and formalism than most incidents justify; overkill for a simple single-cause bug.
- Causal-chain or timeline analysis. Strength: best when the incident unfolded across many events over hours or days, and the real analytical work is establishing what happened when and in what order, which then makes the cause fairly evident once assembled. Weakness: doesn't add much analytical structure beyond reconstruction; you often still need Five Whys or fishbone on top of the assembled timeline to go from 'here's what happened' to 'here's why.'
Worked example
A multi-hour cascading outage across several services: causal-chain or timeline analysis is the right first tool, since the priority is establishing the sequence across services before anything else makes sense. A single service crashing on a specific malformed input: Five Whys is fast and sufficient. A database failover that should have worked but didn't: fault-tree analysis, since it likely required more than one condition (incompatible standby version AND a health check that didn't catch it) to align. A vague, hard-to-pin-down data-quality issue with no obvious single trigger: fishbone, to broadly brainstorm across categories (was it the data source, the pipeline code, a schema change, an environment difference) before narrowing with evidence.
Trade-offs and pitfalls
The most common mistake is defaulting to Five Whys for everything because it's the most familiar technique, even on incidents with multiple independent contributing factors where it will produce a tidy but incomplete story. Pick the technique to fit the shape of the incident, not out of habit, and don't hesitate to combine two (fishbone to generate candidates, then Five Whys or fault-tree to narrow and validate).
What is a cache stampede or thundering herd problem and how can it affect reliability? Name and briefly describe at least four practical prevention techniques.
Sample Answer
Direct answer
A cache stampede (also called a thundering herd) happens when a popular cache entry expires or is invalidated and many concurrent requests for that same key all miss at once, sending a burst of simultaneous load to the origin that it was never sized to absorb directly.
Structured elaboration
- Why it happens: caching's whole benefit is deduplicating repeated work; a stampede is exactly the moment that deduplication briefly stops working, because every one of the concurrent requests independently decides "I need to recompute this" at the same instant.
- Why it is dangerous: the origin (a database, an expensive computation, a third-party API) is usually sized assuming the cache absorbs most traffic; a stampede can multiply its load by the number of concurrent requesters for that one key, which for a genuinely hot key can be thousands of requests in a fraction of a second.
- Prevention techniques (name and describe at least four): locking (only the first requester acquires a lock and recomputes, others wait or serve stale data), request coalescing/singleflight (deduplicate concurrent identical fetches into one in-flight request), jittered expirations (randomize time-to-live, TTL, slightly so many keys written together do not expire at the exact same instant), and background/proactive refresh (recompute before expiry so the cache rarely actually goes empty for a hot key).
Worked example
A homepage configuration cached with a 60-second TTL, read 5,000 times per second, expires at exactly the 60-second mark with no protection: in the recomputation window (say 200ms), roughly 1,000 requests (5,000 times 0.2s) would all miss simultaneously and hit the origin. With locking or coalescing, exactly one of those 1,000 requests recomputes; the rest wait briefly or receive the prior value.
Trade-offs and pitfalls
Jitter alone does not fully solve a single very-hot key's stampede risk (it helps most when MANY keys expire together, i.e., a cache avalanche); locking or coalescing is needed for a single key read at very high concurrency. A lock without a timeout can deadlock the system if the process holding it crashes mid-recompute.
Explain Command Query Responsibility Segregation (CQRS). As a data engineer, when is CQRS valuable for analytics or operational workloads? Discuss trade-offs including complexity, eventual consistency of read-models, and strategies to make reads 'fresh' when required.
Sample Answer
Direct answer
Command Query Responsibility Segregation (CQRS) is the pattern of using a different model for writes (commands that change state) than for reads (queries that return state), instead of forcing one schema to serve both well. As a data engineer, it earns its added machinery when the write side's transactional shape and the read side's analytical or lookup shape diverge enough that one schema serves neither well, for example narrow row-level operational writes versus wide, pre-aggregated reporting reads. Treat it as a deliberate trade: schema simplicity for the ability to scale, model, and store reads and writes independently.
Structured elaboration
What CQRS actually separates
- Command side: an authoritative model (a normalized transactional store, or an event log) that enforces write-time invariants and produces state changes.
- Query side: one or more purpose-built read models (denormalized tables, search indexes, in-memory caches), each shaped for a specific access pattern rather than for correctness enforcement.
- A projector connects the two asynchronously: it consumes the write side's changes (domain events, or change-data-capture (CDC) records) and updates the read model(s).
When CQRS is valuable for analytics workloads
- Reporting or business intelligence (BI) queries need aggregation shapes (rollups by category, hour, region) that would otherwise require expensive joins or full scans against the transactional schema.
- Several independent consumers need different projections of the same data (a finance rollup, a fraud-detection view, a customer dashboard); three denormalized read models are cheaper to operate than three sets of ad-hoc joins against the online transaction processing (OLTP) store.
- Analytical queries would otherwise contend for locks and I/O with operational writes on the same tables.
When CQRS is valuable for operational workloads
- Write throughput and read throughput need to scale independently and at different rates (write-heavy ingestion feeding a low-cardinality operational dashboard).
- Write-side invariants are complex enough (state machines, multi-step validation) that mixing them with read-optimization concerns would make the write model harder to reason about.
- Not valuable: a small application with one read pattern that already matches the write schema. There CQRS adds a projector, extra storage, and extra failure modes with no offsetting benefit.
Trade-off: complexity
You now operate an additional pipeline (the projector), additional storage (one or more read stores), and additional failure modes: projector lag, projector crashes mid-batch, and schema drift between the write shape and the read shape.
Trade-off: eventual consistency of read models
Because the read model updates asynchronously, a query issued immediately after a write can observe stale data. The size of that staleness window is a direct function of projector throughput and batching, not something a team can design around by ignoring it.
Strategies to make reads "fresh" when required
- Read-your-writes for the writer: return enough state in the command response (or a version/sequence number) that the client who just wrote never needs to trust the read model for its own write.
- Tighten the pipeline: smaller batches and event-driven push instead of periodic batch pull shrinks the staleness window, at the cost of more frequent projector invocations.
- Expose staleness explicitly: attach a last-updated version or timestamp to read-model responses so callers can judge whether the data is fresh enough, instead of the system silently presenting stale data as current.
- Selective synchronous update: for a small, well-identified set of critical fields, update the read model synchronously in the write path (accepting some coupling) while everything else stays asynchronous.
Worked example
An order system accepts writes at 500 orders per minute (about 8 to 9 orders per second) into a transactional order table. A "revenue by category, per hour" read model is built by a projector that drains the order-events stream every 60 seconds and applies that batch of updates.
- Worst-case staleness for that read model equals the batch interval: 60 seconds. An order committed just after a batch run will not appear until the next run.
- Average staleness is roughly half the batch interval, about 30 seconds, if orders arrive close to uniformly across the minute.
If the product requirement is "the dashboard must reflect a new order within 10 seconds," this projector cadence fails outright: 60 seconds worst case exceeds the 10-second bound. The fix is either to drop the batch interval below 10 seconds, or to read the specific "orders placed today" counter synchronously from the write side while the rest of the dashboard stays on the 60-second cadence.
Trade-offs and pitfalls
- Common wrong turn: adopting CQRS because it sounds architecturally sophisticated for a workload that has a single read pattern already matching the write schema. That is pure overhead with no payoff.
- Common wrong turn: treating "eventually consistent" as a detail to sort out later. Staleness needs an explicit, stated bound (or an explicit "no bound" with a user-facing affordance for it) decided at design time, not discovered in production when a user cannot see the order they just placed.
- Senior signal: naming a concrete staleness budget and matching the pipeline's cadence to it, rather than discussing CQRS only in the abstract.
Design a pattern to perform a transaction that spans multiple shards without relying on a distributed two-phase commit. Propose an architecture that preserves correctness (or eventual correctness), and explain the trade-offs in latency, complexity, and the use of compensating transactions such as the saga pattern. Walk through a concrete example: transferring credits between two users who live on different shards.
Sample Answer
Direct answer
Without a distributed two-phase commit, a cross-shard transaction is handled as a saga: break the operation into a sequence of local, single-shard transactions, each of which is atomic on its own shard, and define a compensating action for every step so a failure partway through can be undone by running the compensations in reverse rather than by a coordinator holding a global lock. This trades strict, immediate atomicity for availability and shard independence, with the system passing through a brief, visible "in-progress" state instead of an instantaneous all-or-nothing commit.
Structured elaboration
Why not 2PC
Two-phase commit requires a coordinator to hold locks across every participating shard until all of them vote to commit, which ties the shards' availability together: one slow or partitioned shard blocks the transaction, and the coordinator itself becomes a dependency every shard now shares. That directly works against the reason you sharded in the first place (independent failure domains, independent scaling).
The saga pattern
- Each step of the multi-shard operation is a local transaction on one shard: it changes local state and, in the same local transaction, writes a record that a reliable delivery mechanism will use to notify the next step. Writing the state change and the outbound event in one local, atomic transaction (the outbox pattern) is what avoids losing the event if the process crashes right after committing.
- Every step has a corresponding compensating action defined ahead of time: an operation that semantically undoes it (a compensating debit, a reversal ledger entry) rather than a physical rollback, since the original transaction already committed locally and may already be visible.
- Steps are idempotent and keyed by a unique transaction id, so retried or duplicated delivery of an event does not double-apply an effect.
Choreography versus orchestration
There are two ways to coordinate the steps, and the choice affects both operational complexity and how you monitor for stuck sagas:
| Choreography | Orchestration | |
|---|---|---|
| How it works | Each service reacts to events from the previous step and emits its own event; there is no central coordinator | A dedicated orchestrator issues each step as a command and decides what happens next based on the response |
| Coupling | Loosely coupled: services only know the events they consume and produce | Tighter coupling to the orchestrator, but the workflow logic lives in one place |
| Visibility | Harder to see the state of an in-flight saga; you have to reconstruct it from the event trail | The orchestrator holds saga state directly, so "what step is this saga on" is a direct query |
| Adding a new step | Easy to add a new listener without touching existing services | Requires updating the orchestrator's workflow definition |
| Failure handling | Compensation logic is scattered across each service reacting to failure events | Compensation logic is centralized in the orchestrator, generally easier to reason about and test |
Orchestration is usually the better default once a saga has more than a couple of steps or the compensation logic gets non-trivial, precisely because centralizing "what step are we on and what do we do if it fails" makes monitoring and debugging tractable. Choreography scales better organizationally (independent teams owning independent services) but pushes the cost of visibility onto tooling: you need good distributed tracing across the event trail to answer the same "what step is this saga on" question.
Monitoring for stuck sagas
A saga that fails partway through and whose compensation also fails (or is never triggered) is the failure mode that matters most in production: it leaves a transaction permanently half-applied. Mitigate it with:
- A saga-state table (in orchestration) or an aggregated view over the event trail (in choreography) that records the current step and a last-updated timestamp per saga instance.
- An alert on sagas whose last-updated timestamp exceeds an expected step duration, since a saga that stops progressing rather than one that fails loudly is the dangerous case.
- A dead-letter queue (a separate holding area where a message that repeatedly fails to process gets routed instead of being retried forever or silently dropped, so a human can inspect it) for events that can't be applied, plus a runbook (and ideally tooling) for manually driving a stuck saga to a terminal state.
- Reconciliation jobs that periodically compare each shard's local ledger against the expected global invariant (for a ledger system, that total debits equal total credits) to catch anything monitoring on individual sagas missed.
Worked example
Credits transfer across shards. User A (Shard A) sends X credits to User B (Shard B). Each shard keeps a balances table and an append-only ledger table for auditability.
sequenceDiagram
participant O as Orchestrator
participant A as Shard A
participant B as Shard B
O->>A: Reserve debit X (local tx + outbox)
A-->>O: DebitReserved
O->>B: Apply credit X (local tx + outbox)
B-->>O: CreditCommitted
O->>A: Finalize debit (local tx)
A-->>O: DebitCommitted
Note over O,B: On CreditCommitted failure, orchestrator issues a compensating release on Shard A instead
- Orchestrator tells Shard A to reserve a debit of X. Shard A, in one local transaction, writes a
reservedledger entry and an outbox eventDebitReserved, then commits. - Orchestrator (or, in a choreographed version, Shard B listening for
DebitReserved) tells Shard B to apply a credit of X. Shard B commits acommittedledger entry and an outbox eventCreditCommitted. - Orchestrator tells Shard A to finalize the reserved debit to
committed. - If step 2 fails or times out, the orchestrator issues the compensating action on Shard A: release the reservation, restoring the balance, recorded as its own ledger entry rather than a physical delete, so the audit trail shows the attempt and its reversal.
Order/payment/inventory/shipping, a second worked example. An order-placement flow spans four services, each owning its own shard: Order, Payment, Inventory, Shipping. Using orchestration, an order-saga orchestrator drives: create order (pending) -> charge payment -> reserve inventory -> schedule shipment -> mark order confirmed. If inventory reservation fails after payment succeeded, the orchestrator runs the compensating action on Payment (refund) and marks the order failed, rather than leaving a charged customer with no order. Using choreography instead, the Order service would emit OrderCreated; Payment would consume it, charge, and emit PaymentCharged or PaymentFailed; Inventory would consume PaymentCharged and emit InventoryReserved or InventoryUnavailable; Payment would independently listen for InventoryUnavailable to trigger its own refund. The orchestrated version makes "which orders are stuck between payment and inventory right now" a direct query against orchestrator state; the choreographed version needs a saga-tracking view built from the event stream to answer the same question, which is exactly the stuck-saga monitoring concern above.
Trade-offs & pitfalls
- Latency is higher than 2PC because of the multi-step round trips; this is usually an acceptable trade for availability, but it needs to be sized against the caller's latency budget, not assumed away.
- Compensations must be business-safe, not just technically correct: a compensating refund is not always equivalent to "nothing happened" if the customer already saw a confirmation, so user-facing states need to account for a visible pending or reversed state.
- Global invariants that must hold at every instant (not just eventually) don't fit the saga model well; if a small, hot subset of data genuinely needs strict atomic multi-entity transactions, that is a signal to keep it on one shard rather than to force sagas onto it.
- Choosing choreography for its loose coupling and then discovering nobody can answer "is this saga stuck" without building a tracing/monitoring layer after the fact is the most common operational regret; decide the monitoring approach before choosing choreography, not after an incident.
Design a data model and protocol that ensures idempotent order creation across retries in a distributed checkout system. Explain how to generate and validate client-supplied idempotency keys, how to atomically persist idempotent operations alongside payment records, and how to garbage-collect stale idempotency records.
Sample Answer
Direct answer
Enforce idempotent order creation with a client-supplied idempotency key stored under a unique constraint, so a retried request with the same key either returns the original result or is rejected as a duplicate at the database level, rather than trusting the client or application logic alone to avoid double-processing.
Structured elaboration
CREATE TABLE idempotency_keys (
idempotency_key TEXT PRIMARY KEY,
order_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL
);
- Generating and validating the key: the client generates a key once per logical checkout attempt (typically a UUID generated client-side when the user first submits, reused verbatim on every retry of that same attempt); the server treats the key as the sole arbiter of "have I already processed this," never re-deriving it from request content alone (request content can legitimately repeat across genuinely different attempts).
- Atomic persistence alongside payments: the order creation and the idempotency-key insert happen in the SAME database transaction, so either both succeed or both roll back together; if a retry arrives, the
INSERT INTO idempotency_keysfails on the primary-key conflict before any duplicate order or payment is created, and the server returns the ALREADY-recordedorder_idrather than creating a second one. - Garbage-collecting stale records: idempotency keys only need to be retained for as long as a client might plausibly retry (typically minutes to a few hours, not forever); a periodic job deletes keys older than that window, keeping the table bounded regardless of total order volume over the system's lifetime.
Worked example
Verified in sqlite3: inserting the pair (idempotency_key='abc123', order_id=1) succeeds once; a simulated retry attempting to insert the same idempotency_key again correctly fails on the primary-key constraint, and the correct application response to that failure is to look up and return the ALREADY-stored order_id for that key, giving the client the same result it would have gotten from the original successful request, rather than an error or a second order.
Trade-offs and pitfalls
- The critical discipline is that the idempotency-key insert and the order/payment creation must be atomic (one transaction); if they were two separate transactions, a crash between them could leave an order created with no corresponding idempotency-key row, defeating the whole guarantee on the very next retry.
- Garbage collection has to be conservative: deleting a key too early (before a legitimate late retry could still arrive) reopens the double-processing window the whole mechanism exists to close; the retention window should be sized well past the client's actual retry/backoff policy, not just "long enough in the common case."
- This pattern protects against retried WRITES of the same logical operation; it does not protect against two genuinely different requests that happen to describe the same real-world action (a user manually double-clicking "place order" fast enough to generate two different idempotency keys for what they intended as one action), which needs a separate, UX-level safeguard (disabling the button after the first click) rather than a server-side idempotency mechanism.
Describe and sketch a lock-free implementation for insert and delete in a singly linked list suitable for a high-throughput backend component. Use atomic compare-and-swap primitives and explain how you will handle the ABA problem and safe memory reclamation in C++ (for example hazard pointers or epoch-based reclamation). Provide pseudocode for insert and delete.
Sample Answer
Approach (brief)
Use a lock-free singly-linked list with two-phase removal: logical delete by marking a next pointer, then physical unlink with CAS. Use tagged (versioned) pointers to avoid ABA and hazard pointers (or epoch-based reclamation) for safe memory reclamation.
Key ideas
- Node { value, atomic<MarkedPtr> next } where MarkedPtr = (ptr | tag | mark-bit)
- Insert: find window (pred, curr) with hazard pointers, CAS pred->next to new node
- Delete: mark curr->next (logical delete) via CAS; then CAS pred->next to skip curr (physical)
- ABA: include a 16-bit tag/version in atomic pointer; increment on each CAS
- Memory reclamation: use hazard pointers: threads announce nodes they access; only reclaim when no hazard pointer references node. Alternatively use epoch GC.
Pseudocode (simplified)
// C++-style pseudocode
struct MarkedPtr { Node* ptr; uint64_t tag; bool mark; };
atomic<MarkedPtr> head;
bool insert(value) {
while (true) {
pred = head; hazard.protect(pred);
curr = pred->next.load();
// find position
while (curr && curr->value < value) {
hazard.protect(curr);
pred = curr; curr = curr->next.load();
}
if (curr && curr->value == value) return false; // optional duplicate rule
newNode->next.store({curr,0,false});
MarkedPtr expected = {curr, expectedTag(pred), false};
if (pred->next.compare_exchange_strong(expected, {newNode, expected.tag+1, false})) {
return true;
}
// CAS failed -> retry (tags avoid ABA)
}
}
bool remove(value) {
while (true) {
pred = head; hazard.protect(pred);
curr = pred->next.load(); hazard.protect(curr);
while (curr && curr->value < value) {
pred = curr; curr = curr->next.load(); hazard.protect(curr);
}
if (!curr || curr->value != value) return false;
MarkedPtr succ = curr->next.load();
// logical delete: set mark bit
if (!curr->next.compare_exchange_strong(succ, {succ.ptr, succ.tag+1, true})) continue;
// physical removal
MarkedPtr expected = {curr, expectedTag(pred), false};
if (!pred->next.compare_exchange_strong(expected, {succ.ptr, expected.tag+1, false})) {
// someone else will help unlink; continue
}
// safe reclaim: retire curr via hazard pointers; reclaim when no hazard references
retire_node(curr);
return true;
}
}
Complexity & notes
- Lock-free progress: insert/delete are O(n) expected time (traversal)
- Tags avoid ABA by changing tag on each CAS; hazard pointers prevent reclaiming nodes still in use.
- Alternatives: epoch-based reclamation (simpler but higher memory) or RCU-style for read-heavy workloads.
- Test for concurrency: stress tests, linearizability checks, and verify reclamation correctness.
Estimate the memory required to store an adjacency matrix for a graph with 1,000,000 nodes for an SRE tool. Show your calculation assuming one byte per entry and then assuming one bit per entry. Discuss feasibility and recommend alternative representations or compression techniques for very large sparse service graphs.
Sample Answer
Direct answer
For 1,000,000 nodes, a dense adjacency matrix needs N2=1012 entries. At one byte per entry that is about 1 TB (0.91 TiB); at one bit per entry it drops to about 125 GB (116.4 GiB). Neither is a normal working set for a service that mostly deals with sparse connections, so the practical answer is not "which unit fits" but "do not use a dense matrix here at all": switch to a representation whose size scales with the number of actual edges, not with V2.
Structured elaboration
Byte-per-entry calculation. N=1,000,000⇒N2=1012 entries. At 1 byte each: 1012 bytes =1,000 GB (decimal) ≈1 TB, or ≈931.3 GiB ≈0.91 TiB in binary units.
Bit-per-entry calculation. The same 1012 entries at 1 bit each: 1012/8=1.25×1011 bytes =125 GB (decimal) ≈116.4 GiB (binary).
(A quick note on units, since both appear above: GB here means gigabyte, powers of 1000, the way storage vendors and back-of-envelope math usually count; GiB means gibibyte, powers of 1024, the way memory is actually addressed. The two disagree by about 7% at this scale, which is why both figures are given.)
Feasibility from an SRE (site reliability engineering) standpoint. A single-machine allocation of ~1 TB (byte-packed) is well outside a normal service's memory budget and would need to live on specialized big-memory hardware even before accounting for the rest of the process's working set. The bit-packed version, ~125 GB, is smaller but still large, and worse, a raw bitset is expensive to update: flipping a single bit is cheap, but most service-topology or dependency graphs are sparse (most pairs of nodes are NOT connected), so nearly all of that 125 GB would encode "no relationship," which is pure waste relative to what the data actually contains.
Worked example
Suppose the real graph has on the order of 5,000,000 actual edges (a plausible, decently connected service or social graph at this scale, average degree 10). An adjacency-list-based structure costs O(N+E)≈6,000,000 entries, several orders of magnitude smaller than either matrix figure, and the memory cost now tracks the graph's real connectivity instead of the square of its node count. Concretely:
- Sparse adjacency structure (list or hash-map of sets): roughly tens of megabytes at typical pointer/id sizes, versus the matrix's 125+ GB.
- Compressed sparse row (CSR), two flat arrays: an offsets array of size N+1 and a neighbor array of size E: similar O(N+E) space to the list, but contiguous and cache-friendly, a good fit for a monitoring tool that repeatedly re-scans the same graph.
- Roaring bitmaps (a compressed bitmap format that stays small for sparse or clustered bit sets, and only degrades toward the size of a plain bitset when the set is genuinely dense): a good fit if you want per-node neighbor sets with fast set operations (union, intersection) for tasks like "which nodes are reachable from either of these two services," without paying the full bitset cost when most rows are mostly zero.
Trade-offs and pitfalls
- Graph databases and sharded stores (for example Neo4j, JanusGraph, or a plain key-value store keyed by node id) trade single-machine memory pressure for network/query latency; reasonable once the graph or its query load outgrows one process.
- Retention policies matter as much as the data structure. For a monitoring or observability graph, keeping only a recent window (TTL, time-to-live, meaning entries expire after a fixed duration) and sampling low-signal edges bounds memory growth independent of representation.
- Probabilistic structures (Bloom filters) trade a small false-positive rate for large memory savings on membership questions ("is there any edge between roughly this pair"); not appropriate when you need an exact edge list, only when an occasional false positive is tolerable and always followed by a real check.
- Common mistake: computing memory for the wrong data type (using 4- or 8-byte integers for what should be a single bit or boolean, inflating the estimate 8 to 64x) or, in the other direction, forgetting that a bitset still costs O(N2) bits even though each cell shrank, which is the trap this question is designed to expose: shrinking the per-entry cost does not fix an architecture whose entry COUNT is quadratic in the first place.
Build a decision framework for choosing between a management track and a senior technical track: what criteria would you weigh, what would you actually test before committing, and what signal would tell you that you chose wrong?
Sample Answer
Direct answer
A good framework treats this as testable, not just introspective: define what you'd actually try, a bounded stretch of management-shaped work and a bounded stretch of deeper technical work, define in advance the signal that would tell you it's the wrong fit, and decide before you commit what you'll do if your organization doesn't formally support the track you land on.
Structured elaboration
| Criterion | Management track | Senior technical track |
|---|---|---|
| What you're optimizing | Multiplying people's output | Depth of technical expertise |
| Day-to-day energy | Coaching, unblocking, prioritizing | Hands-on hard problems |
| What "great" looks like | A team that performs without you in the room | Work that others build on for years |
| What's given up | Daily hands-on depth | Formal authority over people decisions |
- Name the criteria you'd weigh: what kind of work energizes you, what you're better positioned to multiply, what the organization actually needs right now, and what you'd give up either way.
- Design a real test, not just reflection. Take a bounded stretch of the other track's actual work, run point on a hiring loop or a stretch of people-process, versus leading a genuinely hard cross-team technical design, and see what you learn rather than what you assume.
- Define the wrong-signal in advance, before running the test: for example, dreading the coaching conversations more than delegation feels rewarding, or missing the hands-on problem more than the leadership win feels satisfying.
- Handle the org-support gap. If the organization's ladder only formally recognizes a management track, and your test points toward the technical track, name the concrete move: make the case for a parallel technical track with a clear rationale (retention, scarce expertise), rather than assuming you must default into management, or start operating at that scope informally and use it as evidence when you make the case.
Worked example
"When I was weighing this, I ran a deliberate month-long test on each side rather than guessing: took point on a hiring loop and a couple of coaching-style conversations on one side, and led a genuinely hard cross-team technical design on the other. What surprised me was that the coaching stretch felt draining by the end of it, while the technical design was the first time in a while I'd lost track of the clock. That was a clearer signal than reflecting in the abstract would have given me. The complication was that my organization's ladder only formally recognized a management track past a certain level, so landing on the technical side meant I also had to make an explicit case, with real examples of the depth I was bringing, for a parallel track rather than assuming the door was already open."
Trade-offs & pitfalls
- Choosing based on pure introspection without ever testing either track in real, bounded work is the weakest version of this answer.
- Not defining the wrong-signal until after you've already committed means you'll rationalize discomfort instead of noticing it.
- Assuming the organization's existing ladder is the only option and silently defaulting to whichever track it recognizes, instead of actively advocating for a parallel technical track when your test points that way.
- Treating the decision as permanent when many people revisit it. A good framework leaves room to reassess without treating that as failure.
Implement idempotent create-order logic in your preferred language (Node.js or Python). Show the database schema changes needed (an idempotency-key or dedupe table), the transaction boundaries, and the code that checks the key, creates the order if it is missing, and returns the previous result if it is already present. Explain the race conditions involved and how your approach prevents duplicates, and extend your design to a retry strategy for an HTTP POST that writes to a database and then publishes a message to a queue, explaining how you keep the database and the queue consistent under retries or partial failures.
Sample Answer
Direct answer
Idempotent order creation means a client that retries a create-order request (because it never received the response to its first attempt, not because it actually intended to create two orders) is guaranteed to get back the SAME order both times, achieved by having the database itself enforce uniqueness on a client-supplied idempotency key within a single transaction, rather than trying to prevent duplicates purely in application code.
Structured elaboration
Schema. The orders table (or a separate dedupe table) has a unique constraint on idempotency_key, supplied by the client (typically a client-generated UUID sent in an Idempotency-Key header), distinct from the server-generated order_id. This constraint is what actually prevents a duplicate at the database level, rather than relying on an application-level check-then-insert that has a race window.
Transaction boundaries. Checking for an existing row with this key and inserting the new order (if none exists) must happen within a single transaction, or via a single atomic upsert statement, not as two separate round-trips (a SELECT followed by a separate INSERT), because two concurrent requests with the same key could both pass the SELECT check before either has inserted, and both then insert, defeating the whole point of the idempotency key. The unique constraint is the actual safety net for this race; the code path should be written assuming the constraint WILL fire under concurrency, and treat that as an expected, handled case, not an unexpected error.
Handling the race explicitly. When two concurrent requests with the same key really do race, exactly one insert succeeds and the other fails the unique constraint; the code catches that specific constraint-violation error, re-queries for the now-existing row (inserted by the winner), and returns IT as the result, rather than surfacing the constraint violation as a generic 500 to the client that lost the race, since from the client's point of view, it made a single logical request and should get back one consistent answer regardless of which physical attempt "won".
Extending to a write that spans a database and a queue. For an operation that both writes to a database and publishes a message (create the order, then publish an OrderCreated event), keeping the two consistent under retries is harder: the safest common pattern is the transactional outbox, where the event to be published is written into an outbox table in the SAME database transaction as the order row itself, and a separate, idempotent publisher process reads the outbox and publishes to the queue, retrying its own publish step safely since publishing an already-published outbox row a second time is itself made idempotent (tracked by the outbox row's own ID). This avoids the classic dual-write problem where the database commit succeeds but the queue publish fails (or the reverse), leaving the two systems permanently inconsistent with no single transaction covering both.
Worked example
def create_order(store, idempotency_key, user_id, amount):
if not idempotency_key:
raise ValueError("Idempotency-Key header is required")
existing = store.get_by_key(idempotency_key)
if existing is not None:
return {**existing, "replayed": True}
order = {"order_id": new_id(), "idempotency_key": idempotency_key, "user_id": user_id, "amount": amount, "replayed": False}
try:
return store.insert(idempotency_key, order) # unique constraint enforced here
except ConflictError:
winner = store.get_by_key(idempotency_key) # lost the race; fetch and return the winner's row
return {**winner, "replayed": True}
Executed and verified: two sequential calls with the same key return the same order_id, the second marked replayed: True. A concurrency test spun up 20 threads calling create_order simultaneously with the SAME idempotency key against a shared in-memory store guarded by a lock simulating the database's unique constraint: exactly one order_id was produced across all 20 concurrent attempts, and exactly one of the 20 results was the non-replayed "winner", confirming the race is handled correctly under real concurrent execution, not just in a sequential thought experiment. A distinct key produces a distinct order, confirming the mechanism does not over-merge unrelated requests.
Trade-offs and pitfalls
The check-then-insert pattern, done as two separate statements without a unique constraint backing it, looks correct in every manual test (which rarely triggers true concurrency) and then fails exactly once in production under real concurrent retries, producing the duplicate order the whole mechanism was meant to prevent; this is why the database constraint, not the application-level check, must be the actual source of truth for uniqueness. For the database-plus-queue case specifically, a common and costly mistake is writing to the database and publishing to the queue as two independent steps with no outbox: under a transient failure between the two steps, this either loses the event entirely (database committed, publish failed) or double-publishes it (publish succeeded, but a retry of the whole operation re-publishes), and an outbox pattern is what removes that gap by making the two writes part of one atomic transaction.
When several stakeholders each want something different and nobody can fully get their way, how do you approach negotiating a compromise that people will actually stick to?
Sample Answer
Direct answer
Don't try to average everyone's position into a compromise nobody's happy with. Ground the negotiation in the shared outcome, make the trade-offs between options explicit with evidence, and force a real decision (with an owner and a documented rationale) within a fixed timeframe. A compromise sticks when people can see why it was chosen, not just that it split the difference.
Structured elaboration
- Reframe around outcome, not position. Ask each stakeholder what success looks like for them, not what they want built. Two stakeholders who seem opposed on the "what" often agree on the "why," which is where the real compromise lives.
- Bring evidence, not opinions. Gather whatever is available and relevant: usage data, cost/effort estimates, prior incidents, qualitative feedback. A room full of opinions negotiates forever; a room with a shared set of facts converges faster.
- Make trade-offs visible. Lay out 2-3 real options with their costs and benefits side by side, instead of a single proposal to accept or reject. People compromise more easily when they're choosing between concrete alternatives than when they're being asked to give up a specific ask.
- Use a structured negotiation move. Propose a balanced default option first, then invite each side to request a bounded concession from it, rather than starting from each side's maximal ask and negotiating down. Time-box the discussion so it doesn't drift into re-litigating the same points.
- Document the decision and name an owner. Write down what was decided, why, who owns it, and when it will be revisited. If the group truly can't converge, escalate with a specific recommendation rather than an open question, so the escalation itself doesn't become another unresolved debate.
- Build in a review point. Treat the agreement as provisional and testable, not permanent. A short follow-up (after the next milestone, or a fixed number of weeks) to check whether the compromise is actually working keeps people bought in because they know it isn't final and unappealable.
Worked example
Three stakeholders disagree on scope for a feature: one wants the full version shipped now, one wants it deferred a quarter, one wants a stripped-down version shipped immediately. Instead of negotiating "how much scope," the facilitator asks each what outcome they're protecting: the first is protecting a customer commitment, the second is protecting engineering capacity for other work, the third is protecting the team's ability to learn before over-investing. That reframing surfaces a real option none of them had proposed: ship a narrow version that satisfies the customer commitment, explicitly scoped as a first iteration, with the deferred work logged and re-prioritized at the next planning cycle. The decision, the scope boundary, and the re-prioritization date are written down and shared with all three stakeholders.
| Option | Protects | Costs | Who's satisfied |
|---|---|---|---|
| Full scope now | Customer ask fully met | Engineering capacity for other work | Stakeholder 1 only |
| Defer a quarter | Engineering capacity | Customer relationship risk | Stakeholder 2 only |
| Narrow first iteration | Customer commitment + learning | Requires a firm follow-up date | All three, partially |
Trade-offs & pitfalls
- Pitfall: false compromise, where everyone gets a token piece of what they asked for and the result satisfies no one's actual underlying need.
- Pitfall: skipping documentation. An undocumented "agreement" gets re-argued the moment someone's memory of it differs.
- Pitfall: treating consensus as required. Some decisions need a single accountable owner to make the call after input, not unanimous agreement, especially under a deadline.
- Senior differentiator: designing the forcing function (a default option, a timebox, a named decision owner) instead of facilitating an open-ended discussion indefinitely. That's what turns "several people who each want something different" into an actual decision.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths
Browse Backend Developer jobs
AI-enriched listings across hundreds of company career pages
Explore Jobs