Amazon Senior Game Developer Interview Preparation Guide
Amazon's game developer interview process for senior-level candidates consists of a recruiter screening, one technical phone screen, and five onsite interview rounds spanning 4-6 weeks. The process evaluates technical game development expertise, system design thinking, gameplay architecture, performance optimization, and alignment with Amazon's 16 Leadership Principles. Candidates will be assessed on their ability to design and implement complex game systems, optimize for multiple platforms, and demonstrate leadership in technical decisions.
Interview Rounds
Recruiter Screening
What to Expect
Initial 30-45 minute call with an Amazon recruiter to assess background fit, career goals, and overall suitability for the senior game developer role. The recruiter will discuss your experience with game engines, platforms you've developed for, and team collaboration background. This is also an opportunity to ask questions about the role, team structure, and Amazon's game development initiatives.
Tips & Advice
Be prepared to discuss your most impactful game projects with emphasis on technical decisions and team impact. Highlight experience with both Unity and Unreal Engine. Mention any multiplayer or cross-platform optimization work. Ask thoughtful questions about the team's current projects and technical challenges. Prepare 2-3 stories demonstrating leadership and impact.
Focus Topics
Motivation for Amazon and Game Development at Scale
Clear articulation of why you're interested in Amazon's game division and how you view game development at enterprise scale.
Practice Interview
Study Questions
Leadership and Cross-Functional Collaboration
Examples of leading technical initiatives, mentoring junior developers, and collaborating with artists, designers, and audio engineers.
Practice Interview
Study Questions
Game Development Experience Overview
Summary of your background with game engines (Unity/Unreal), platforms (mobile, console, PC, web), and scale of projects you've shipped.
Practice Interview
Study Questions
Technical Phone Screen
What to Expect
60-minute technical interview with a senior game developer or hiring manager focused on core gameplay programming and problem-solving. Candidates will implement a gameplay mechanic or game system in a plain text editor. The interviewer starts with barebones requirements and iterates, adding complexity if the candidate finishes early. Questions typically involve game logic implementation, state management, or interactions between game components. The second half involves discussion of past game projects and technical decision-making.
Tips & Advice
Practice implementing game mechanics from scratch in plain text editors without IDE assistance. Common patterns include tic-tac-toe game logic, turn-based systems, and collision/interaction mechanics. Think aloud and explain your approach before coding. Ask clarifying questions about requirements. Be prepared to refactor and extend your solution. Use C++ or C# depending on your strength. For the project discussion segment, focus on technical decisions, performance trade-offs, and what you learned. Have a structured narrative prepared for 2-3 complex projects.
Focus Topics
Problem-Solving and Iteration Under Time Pressure
Ability to start with simple implementations and incrementally add features, handle edge cases, and refactor code when asked.
Practice Interview
Study Questions
Technical Project Discussion and Trade-offs
Articulating past game development projects, explaining architectural decisions, discussing performance optimization choices, and reflecting on lessons learned.
Practice Interview
Study Questions
Game State Management and Architecture
Understanding how to structure game state, manage object lifecycles, handle communication between game components, and maintain data consistency.
Practice Interview
Study Questions
Gameplay Mechanics Implementation
Ability to code core game logic including turn systems, state machines, win conditions, player interactions, and game rule enforcement in C++ or C#.
Practice Interview
Study Questions
Onsite Technical Round 1: Game Engine Architecture and Gameplay Systems
What to Expect
90-minute in-depth technical interview focusing on game engine architecture, gameplay systems design, and implementation patterns. Candidates will discuss or implement a gameplay system (e.g., inventory system, skill/ability system, quest system) with consideration for extensibility, performance, and cross-platform compatibility. The interviewer probes understanding of Unity or Unreal Engine architecture, GameObject/Actor patterns, and how gameplay logic integrates with engine systems.
Tips & Advice
Be deeply familiar with your preferred engine's architecture (Unity's scene system, component system, lifecycle; Unreal's Actor/Component model). Practice designing extensible systems that can accommodate new features. Discuss concrete examples from shipped games. Explain how you handle cross-platform differences (mobile vs. console constraints). Be ready to diagram systems and explain communication patterns. Mention use of design patterns (Observer, Command, Factory) where applicable.
Focus Topics
Cross-Platform Considerations
Understanding platform-specific constraints (mobile performance, console memory, web compatibility), input handling differences, and optimization strategies for different hardware.
Practice Interview
Study Questions
Communication Patterns and Decoupling
Implementing loose coupling between game systems using events, messaging, or other patterns to maintain clean architecture as gameplay complexity grows.
Practice Interview
Study Questions
Gameplay Systems Design and Extensibility
Designing robust systems (abilities, inventory, progression, economy) that are modular, extensible, and can accommodate new features without refactoring core logic.
Practice Interview
Study Questions
Game Engine Fundamentals (Unity/Unreal)
Deep knowledge of your primary engine: architecture, GameObject/Actor systems, component-based design, lifecycle management, serialization, and core systems integration.
Practice Interview
Study Questions
Onsite Technical Round 2: Graphics, Animation, and Performance Optimization
What to Expect
90-minute technical interview covering graphics programming, animation systems, visual effects implementation, and performance optimization. Candidates will discuss rendering optimization, memory management, animation state machines, or visual effect systems. The focus is on understanding how to deliver visually impressive games while maintaining target frame rates across platforms. May include discussion of shaders, draw calls, LOD systems, or animation blending.
Tips & Advice
Have concrete examples of performance optimization work you've done: frame rate improvements, memory reduction, visual quality enhancements. Understand profiling tools (Unity Profiler, Unreal Insights, platform-specific tools). Be ready to discuss trade-offs (quality vs. performance, memory vs. CPU). Know fundamentals of rendering pipelines, draw call optimization, and batching. If you've worked with shaders, discuss specific optimizations. Prepare metrics-driven stories showing before/after improvements.
Focus Topics
Visual Effects and VFX Integration
Implementing particle systems, shader-based effects, integrating pre-made VFX, and optimizing visual effects for performance budgets.
Practice Interview
Study Questions
Animation Systems and State Machines
Designing character animation systems, blending states, handling locomotion, synchronizing animations across network (for multiplayer), and integrating with gameplay code.
Practice Interview
Study Questions
Memory Management and Profiling
Strategies for reducing memory footprint on mobile/console, understanding memory hierarchies, asset streaming, and using profiling tools to identify bottlenecks.
Practice Interview
Study Questions
Graphics and Rendering Optimization
Understanding rendering pipelines, draw call optimization, material/shader batching, LOD systems, and techniques to maintain visual quality while hitting frame rate targets.
Practice Interview
Study Questions
Onsite Technical Round 3: System Design for Game Architecture
What to Expect
90-minute system design focused on large-scale game systems and architecture. Candidates will design systems such as a multiplayer match-making system, persistent game economy, real-time multiplayer state synchronization, or a game progression/progression system. The emphasis is on scalability, reliability, and handling distributed game states. This round evaluates ability to think beyond a single machine to networked, scalable game architecture—critical for games at Amazon's scale.
Tips & Advice
Practice designing systems that handle thousands of concurrent players: think about latency tolerance, state consistency, failover, and data persistence. Use clear communication and diagrams. Discuss trade-offs explicitly (consistency vs. availability, latency vs. accuracy). Mention concrete technologies if relevant (databases, message queues, caching). For multiplayer systems, address synchronization strategies, input prediction, and reconciliation. For economy systems, discuss fraud prevention and balancing. Ask clarifying questions about scale, latency requirements, and consistency expectations.
Focus Topics
Latency and Performance Trade-offs in Networked Games
Understanding network round-trip implications, designing for acceptable latency windows, choosing synchronous vs. asynchronous patterns, and optimizing data transmission.
Practice Interview
Study Questions
Data Consistency and Reliability
Addressing data consistency in distributed game systems, handling race conditions, ensuring atomic transactions for critical operations, and designing for fault tolerance.
Practice Interview
Study Questions
Scalable Game Backend Systems
Designing backend services for match-making, persistent progression, economy systems, and analytics that can handle millions of players with appropriate throughput and latency.
Practice Interview
Study Questions
Multiplayer Architecture and Synchronization
Designing real-time multiplayer systems including client/server communication, state synchronization, latency handling, input prediction, reconciliation, and handling disconnections.
Practice Interview
Study Questions
Onsite Behavioral Round 1: Amazon Leadership Principles
What to Expect
60-minute interview focused on assessing alignment with Amazon's 16 Leadership Principles through behavioral questions and past project examples. The interviewer will ask targeted questions about situations where you demonstrated specific principles such as 'Customer Obsession,' 'Ownership,' 'Invent and Simplify,' 'Learn and Be Curious,' 'Earn Trust,' 'Deliver Results,' 'Think Big,' and others. Emphasis is on concrete stories with measurable outcomes and reflection on what you learned.
Tips & Advice
Prepare 6-8 detailed STAR stories that map to different Leadership Principles. For each story: clearly state the Situation, explain your Task, describe the specific Actions you took (emphasizing your role), and quantify Results. Focus on situations where you had to make difficult trade-offs, drive alignment across teams (artists, designers, programmers), or go above and beyond to deliver results. Practice conciseness—2-3 minute stories max. Be ready to discuss how your experience in game development aligns with each principle. For a senior-level role, emphasize stories of influence, mentorship, or technical leadership.
Focus Topics
Amazon Leadership Principle: Think Big
Examples of proposing innovative features, improving systems to enable future scaling, or suggesting strategic direction that initially seemed ambitious.
Practice Interview
Study Questions
Amazon Leadership Principle: Learn and Be Curious
Stories of learning new technologies, adapting to new platforms, incorporating feedback, and continuously improving your craft.
Practice Interview
Study Questions
Amazon Leadership Principle: Earn Trust and Collaboration
Examples of collaborating across disciplines (working effectively with artists, designers, audio engineers), resolving conflicts, and building trust with team members and stakeholders.
Practice Interview
Study Questions
Amazon Leadership Principle: Deliver Results
Examples of shipping features or optimizations under tight deadlines, overcoming technical challenges to meet commitments, and maintaining quality while delivering fast.
Practice Interview
Study Questions
Amazon Leadership Principle: Ownership
Stories of taking responsibility for outcomes, driving projects end-to-end, and holding oneself accountable even when things didn't go as planned.
Practice Interview
Study Questions
Amazon Leadership Principle: Customer Obsession
Examples of deeply understanding player needs, making design decisions based on player feedback, and prioritizing player experience over technical simplicity.
Practice Interview
Study Questions
Onsite Behavioral Round 2: Bar Raiser Interview
What to Expect
60-minute final round with a senior-level 'Bar Raiser'—typically an experienced engineer from another team who is responsible for maintaining hiring standards across Amazon. This round combines behavioral questions, project deep-dives, and probing technical discussions to ensure the candidate meets or exceeds Amazon's hiring bar. The Bar Raiser will challenge assumptions, ask difficult follow-up questions, and assess your growth trajectory. For senior-level candidates, this round often explores leadership impact, influence across teams, and contribution to technical culture.
Tips & Advice
Expect more challenging questions and deeper probes than previous rounds. Be honest about failures and what you learned. The Bar Raiser may challenge your technical decisions or ask 'why' repeatedly to understand your reasoning depth. Prepare your most technically complex project for detailed discussion—be ready to explain trade-offs, alternative approaches you considered, and why you made specific choices. Demonstrate learning from mistakes. For senior-level candidates, articulate your vision for game development, how you've influenced your team, and how you'd approach scaling your impact at Amazon. Show confidence without arrogance.
Focus Topics
Learning from Failure and Course Correction
Honest discussion of a significant technical decision that didn't work out as planned, how you recognized it, what you learned, and how it changed your approach.
Practice Interview
Study Questions
Vision for Game Development and Personal Growth
Your perspective on where game development is heading, how you see your role evolving, and what you hope to accomplish in the next 3-5 years.
Practice Interview
Study Questions
Technical Leadership and Influence
Examples of influencing technical direction, mentoring other developers, setting quality standards, and driving architectural improvements that benefited the broader team.
Practice Interview
Study Questions
Navigating Ambiguity and Making Trade-offs
Stories of situations with incomplete information where you had to make decisions, balance competing priorities (performance vs. features, scope vs. deadline), and drive consensus.
Practice Interview
Study Questions
Frequently Asked Game Developer Interview Questions
Show how to implement dependency injection patterns in modern C++ for a game engine to decouple subsystems while avoiding runtime overhead and dynamic allocation. Provide code sketches that use templates or compile-time injection, describe a lightweight service registry option, and discuss trade-offs between compile-time and runtime DI in terms of flexibility and performance.
Sample Answer
Approach (brief)
Show two zero-overhead patterns: compile-time injection via templates (no virtual calls, no heap), and a lightweight constexpr service-registry that still avoids dynamic allocation where possible. Then discuss trade-offs.
1) Compile-time DI with templates
// System that depends on Renderer and Audio provided at compile time
template<class RendererT, class AudioT>
class GameSystem {
RendererT& renderer;
AudioT& audio;
public:
GameSystem(RendererT& r, AudioT& a) : renderer(r), audio(a) {}
void update() {
renderer.draw(); // inlined, monomorphic
audio.play();
}
};
Usage: instantiate with concrete engine subsystems (no indirection, optimized away).
2) Lightweight service registry (header-only, stack-friendly)
// Simple non-owning registry; no heap, store pointers in static array
template<size_t N>
struct ServiceRegistry {
void* slots[N]{};
template<typename T> void set(T* s, size_t id){ slots[id]=s; }
template<typename T> T* get(size_t id){ return static_cast<T*>(slots[id]); }
};
Populate at init using global or per-context registry; services remain owned elsewhere (engine/singletons on stack/static).
Trade-offs
- Compile-time DI: maximum performance, inlining, no vtable, great for hot loops; but less flexible (binary size growth, recompilation when swapping).
- Registry/runtime DI: flexible, easier to swap mocks for testing, supports plugins; small pointer indirection cost and potential cache effects.
- Hybrid: use templates for hot paths and registry for high-level wiring.
Use for game dev: prefer compile-time for rendering/audio critical loops, registry for game state, editor, or scripting integration.
You have several lightweight ways to reduce risk on an ambiguous ask before committing full effort: for example a timeboxed spike or proof of concept, a scoped ticket built on stated assumptions, deferring the work for more research, or a quick prototype instead of a full build. Walk through two or three of these options, when you would reach for each one, and how you keep whichever one you pick bounded in scope, cost, and time so it does not quietly turn into the real build.
Sample Answer
There's a menu of lightweight ways to de-risk an ambiguous ask, and the named ones (a spike or POC, a scoped ticket built on stated assumptions, deferral, a quick prototype) aren't the whole list; techniques like a fake-door test, a Wizard-of-Oz stand-in, a small pilot, or simply looking at data you already have all belong on the same menu. The skill isn't memorizing the menu, it's matching the technique to what kind of ambiguity you actually have, and then keeping whatever you pick from quietly turning into the real build.
Match the technique to the unknown.
- If you don't know whether the question is even still open: check data you already have first, always, before building anything new. Support tickets, existing analytics, a past retrospective; this costs close to nothing and sometimes the question is already answered.
- If the unknown is demand (will anyone want this): a fake-door test, a button, link, or landing page for something that doesn't exist yet, measuring click-through, tells you demand without building the thing.
- If the unknown is the shape of the interaction, not whether people want it: a Wizard-of-Oz stand-in, a human manually doing what the automation would eventually do, tests the experience without building the automation, which is usually the expensive part.
- If the unknown is technical feasibility, can this even be built the way we're imagining: a timeboxed spike or proof of concept.
- If the ambiguity is small and low-stakes: skip the experiment entirely, write a scoped ticket on a stated assumption, get a quick nod from whoever owns the area, and move.
- If you need real usage signal at modest scale before deciding to go further: a small pilot.
- If the cost of being wrong is low and nobody is actually blocked waiting on you: defer, explicitly, rather than spending effort now.
How you know a lightweight prototype is enough, and don't need something bigger. Three signals: the decision is reversible and low blast radius if you're wrong, the disagreement is about one narrow factual question rather than a whole strategic direction, and a small number of examples or users would plausibly settle it either way. If any of those isn't true, for example the decision is expensive to undo, escalate to a bigger test rather than trusting a five-user prototype.
If it succeeds, what you hand to engineering isn't the throwaway code, it's a one-page brief: the assumption that got validated, the specific approach that worked, the known limitations the prototype deliberately skipped (auth, scale, error states), and a link to the throwaway artifact clearly labeled "not production," so engineering rebuilds the thing properly instead of hardening code that was never meant to survive contact with real load.
Keeping it bounded, in three dimensions.
- Time: a hard calendar boundary with a decision meeting already on the calendar, not "we'll know when we're done."
- Cost: a person-hour ceiling stated up front, for example one engineer for three days, 24 person-hours, and an explicit rule that production-grade requirements (auth, scaling, full error handling) are out of scope for this round.
- Scope: a written "won't do" list next to the "will do" list, and a rule that any request to expand scope becomes a separate, newly-approved ticket rather than silently absorbed into the current one.
Worked example. A PM has an ambiguous ask: would users want a saved-search alert feature. Retrospective check first: support tickets mentioning this over the last quarter are frequent but not conclusive enough to build on their own. Fake-door test: a "Get notified" button on the search results page for 2 weeks to 5% of traffic. Threshold set before launch: above 3% click-through, build it; below 1%, shelve it; between 1 and 3%, run one more cheap check. That check is Wizard-of-Oz: manually send a hand-built digest email to the people who clicked and see if they actually open and engage with a manual version before building the automated one.
A different discipline. An SRE has an ambiguous ask: would customers notice if a non-critical endpoint's data freshness degraded. Instead of building automated degradation logic, they Wizard-of-Oz it, manually holding one internal dashboard's data stale for a day and watching whether anyone notices or complains, before writing a single line of the real feature.
The trap: defaulting to the fanciest technique available, a full pilot or a real prototype, when five minutes checking data you already have would have answered the question. The opposite trap is just as real: using a spike to avoid ever writing an assumption down in a ticket and getting a quick answer, when the ambiguity was small enough that asking didn't need an experiment at all.
Given two strings, compute the minimum number of single-character insertions, deletions, and substitutions needed to turn one into the other. Then explain how the same 2D DP table, read differently, also gives you the longest common subsequence of the two strings, and how you would reduce the space usage from O(mn) to O(min(m,n)).
Sample Answer
Direct answer
Build a 2D dynamic programming (DP) table where dp[i][j] is the edit distance between the first i characters of one string and the first j characters of the other: dp[i][j] = dp[i-1][j-1] when the characters match, otherwise 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) (substitution, deletion, insertion). The exact same table shape, read differently, gives the Longest Common Subsequence (LCS): instead of minimizing a cost over three neighbors, you maximize a match-count over the same three neighbors (dp[i][j] = dp[i-1][j-1] + 1 on a match, else max(dp[i-1][j], dp[i][j-1])). Because each row only depends on the row directly above it, you can keep just two rows in memory instead of the full m by n grid, cutting space from O(mn) to O(min(m,n)).
Structured elaboration
Edit distance, rolling rows.
def edit_distance(a, b):
"""
Levenshtein distance: min single-character insertions, deletions,
substitutions to turn a into b. Rolling-row version: O(min(m,n)) space.
"""
if len(b) > len(a):
a, b = b, a
m, n = len(a), len(b)
prev = list(range(n + 1))
for i in range(1, m + 1):
cur = [i] + [0] * n
ai = a[i - 1]
for j in range(1, n + 1):
cost = 0 if ai == b[j - 1] else 1
cur[j] = min(
prev[j] + 1,
cur[j - 1] + 1,
prev[j - 1] + cost,
)
prev = cur
return prev[n]
Same table, read as LCS.
def lcs_length(a, b):
"""
Longest common subsequence length via the same 2D table shape as edit
distance, but reading it as a max over match/no-match instead of a min
over insert/delete/substitute costs. O(min(m,n)) space with rolling rows.
"""
if len(b) > len(a):
a, b = b, a
m, n = len(a), len(b)
prev = [0] * (n + 1)
for i in range(1, m + 1):
cur = [0] * (n + 1)
ai = a[i - 1]
for j in range(1, n + 1):
if ai == b[j - 1]:
cur[j] = prev[j - 1] + 1
else:
cur[j] = max(prev[j], cur[j - 1])
prev = cur
return prev[n]
Why they're "the same table read differently." Both recurrences look at the same three neighbor cells (dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) and branch on whether a[i-1] == b[j-1]. Edit distance treats a mismatch as "pay 1 and take the cheapest neighbor"; LCS treats a mismatch as "take the better (larger) of the two adjacent neighbors, no cost." The identity connecting them precisely: if you restrict edit distance to insertions and deletions only (no substitution allowed, so a mismatch costs 1 via insert-or-delete rather than a cheaper substitution), that restricted edit distance Dindel and the LCS length satisfy LCS(a,b)=2m+n−Dindel(a,b), since every unmatched character on either side must be individually inserted or deleted, and the matched characters are exactly the LCS.
Space reduction to O(min(m,n)). Because row i of the table only ever reads from row i-1 (never row i-2 or earlier), you don't need the full grid: keep two 1D arrays ("previous row" and "current row") and swap them after each row completes. Always iterating the shorter string as the row dimension makes each row's length min(m,n)+1, so the rolling arrays cost O(min(m,n)) instead of O(mn).
Worked example
def edit_distance_indel_only(a, b):
"""Edit distance restricted to insertions and deletions only (no
substitution): used to show the LCS <-> edit-distance identity."""
m, n = len(a), len(b)
prev = list(range(n + 1))
for i in range(1, m + 1):
cur = [i] + [0] * n
ai = a[i - 1]
for j in range(1, n + 1):
if ai == b[j - 1]:
cur[j] = prev[j - 1]
else:
cur[j] = min(prev[j] + 1, cur[j - 1] + 1)
prev = cur
return prev[n]
a, b = "ABCBDAB", "BDCABA"
ed = edit_distance(a, b)
lcs = lcs_length(a, b)
ed_indel = edit_distance_indel_only(a, b)
m, n = len(a), len(b)
print("edit distance:", ed)
print("lcs length:", lcs)
print("indel-only edit distance:", ed_indel)
print("m + n - indel-only:", m + n - ed_indel, "-> /2 =", (m + n - ed_indel) / 2)
edit_distance_indel_only is the same recurrence as edit_distance but with the substitution branch removed: a mismatch costs 1 via min(prev[j] + 1, cur[j - 1] + 1) only (no prev[j - 1] + cost term), which is what makes it directly comparable to LCS.
Output:
edit distance: 5
lcs length: 4
indel-only edit distance: 5
m + n - indel-only: 8 -> /2 = 4.0
The identity checks out exactly: m+n−Dindel=13−5=8, and 8/2=4, matching the LCS length of 4 computed directly.
Trade-offs & pitfalls
Key points
- Full edit distance (with substitution allowed) is generally smaller than the indel-only edit distance, since substitution can fix a mismatch in one move where insert-then-delete needs two; only the indel-only variant connects directly to LCS via the identity above.
- The same 2D table shape underlies other string-alignment problems too: regular-expression matching (deciding whether a pattern with wildcards matches a string) uses a structurally similar 2D DP over pattern position and text position, though its transition rules differ (handling
*and.instead of insert/delete/substitute costs); and sequence alignment with affine gap penalties (a research-biology framing where opening a gap costs more than extending one) extends the same table with extra state per cell to track "are we currently inside a gap," rather than changing the table's fundamental shape. - A common mistake when reducing to rolling rows is forgetting to swap which string is iterated as rows after ensuring the shorter one is used; getting this backward defeats the space savings.
Complexity
- Time: O(m⋅n) for both edit distance and LCS, since every cell is computed once.
- Space: O(min(m,n)) with the rolling-row optimization, down from O(mn) for the full table.
Edge cases
- One string empty: edit distance equals the length of the other string (all insertions or all deletions); LCS length is 0.
- Identical strings: edit distance is 0; LCS length equals the string length.
- No characters in common: edit distance equals
max(m, n)or higher depending on length difference; LCS length is 0.
Design a CRDT suitable for a real-time collaborative text editor where multiple users can type and delete concurrently without a central lock. Describe the data structure, how concurrent operations from different users merge deterministically, and the practical cost (metadata growth, garbage collection of tombstones) of the approach.
Sample Answer
A text editor where users type and delete concurrently without a lock needs a sequence CRDT (a Conflict-free Replicated Data Type: a data structure whose merge operation is commutative, associative, and idempotent, so concurrent replicas always converge to the same state without coordination). Each character gets a globally unique, immutable identifier, deletions mark a tombstone instead of physically removing the character, and a deterministic tie-break rule orders characters that were inserted concurrently at the same position. The design pays for this with permanent per-character metadata and a garbage-collection problem for tombstones.
Data structure
- Represent the document as an ordered sequence of atoms:
{ id: (site, counter), char, prev_id, tombstone }. idis globally unique because it pairs a site identifier (one per editing client) with that site's own monotonically increasing counter.prev_idrecords which atom this one was inserted immediately after, at the moment of insertion. That is what preserves each user's actual intent (insert right after the character I was looking at), even if other edits land nearby before this one is delivered.
Concurrent insert resolution
- Insert(id, prev_id, char) is broadcast to every replica.
- When two inserts share the same prev_id, both intended the same insertion point, that shared prev_id is exactly what concurrency looks like here. A deterministic comparator orders competing children of the same prev_id by site identifier (higher-precedence site placed first), so every replica, regardless of arrival order, produces the same final sequence.
Deletion and tombstones
- Delete(id) sets tombstone=true on that atom. The atom stays in the structure, because its ordering role must persist: a later, concurrently-arriving insert may still reference it as prev_id and needs it to resolve correctly.
- A tombstoned atom cannot be physically removed until every replica has definitely applied it.
Garbage collection of tombstones
- Replicas exchange a version vector (one counter per site, the highest counter from that site each replica has durably applied) during anti-entropy (a periodic background exchange where replicas compare state and reconcile any differences, rather than waiting for a live update to arrive).
- The element-wise minimum across all replicas' version vectors is the causal stability frontier: any tombstone at or below that frontier has been seen everywhere and can be physically purged.
- Cost: computing and propagating that frontier needs periodic all-to-all or gossip-based exchange, and a replica that stays offline indefinitely blocks compaction for everyone unless it is explicitly evicted from the version-vector set.
Worked example: two concurrent inserts at the same position
Both replicas have already converged on a one-character document containing 'X', a single atom with id (A,1). Two users, on different replicas, both position their cursor right after 'X' at the same time:
- Site A's user types 'p': op1 = Insert(id=(A,2), prev=(A,1), char='p')
- Site B's user types 'q': op2 = Insert(id=(B,1), prev=(A,1), char='q')
Both operations reference prev=(A,1): that shared reference is the concurrency. The merge rule for atoms competing for the same prev is order competing children by site identifier, descending, so a child from site B is placed before a child from site A when both point at the same prev.
- At replica A: op1 applies locally first, giving 'Xp'. When op2 arrives, it is spliced into the children of (A,1) and re-sorted by the rule above, giving order [op2, op1], so the document becomes 'Xqp'.
- At replica B: op2 applies locally first, giving 'Xq'. When op1 arrives, the same re-sort rule applies, again giving [op2, op1], so the document becomes 'Xqp'.
Both replicas land on 'Xqp' even though they applied the two operations in opposite order. That is what merging deterministically means in practice: the comparator, not arrival order, decides the final sequence.
graph LR
X["X (id A,1)"] --> Q["q (id B,1)"]
Q --> P["p (id A,2)"]
Now say 'q' is later deleted by its author. Atom (B,1) is not removed, only flagged tombstone=true, so the document reads 'Xp', but the underlying structure still holds three atoms, two live and one tombstone, until compaction runs.
Trade-offs & pitfalls
- Metadata growth: every character carries an id pair, a prev pointer, and (once deleted) a tombstone bit; on a long-lived, heavily-edited document the tombstone count can exceed the live character count, so uncompacted storage is proportional to live plus deleted atoms rather than just live ones.
- The same observed-remove technique (adding creates a fresh tag, removing targets only the tags actually observed) applies directly to a shared wishlist service: adding an item is an add-tag, removing it is a remove-tag scoped to the tags a client has actually seen, and the tombstone-compaction and consistent-read story are identical to the text editor's, just at item granularity instead of character granularity.
- Formatting spans (bold, italic) and structural moves (relocate a paragraph) do not compose as cleanly as single-character insert and delete; a naive extension can lose the same intention-preservation guarantee, which is why production collaborative-editing CRDTs spend real engineering effort specifically on this.
- Common wrong turn: implementing deletion by removing the atom outright instead of tombstoning it. That breaks any concurrent insert whose prev_id pointed at the now-missing atom, since there is nothing left to splice after.
In GLSL 330, write a simple vertex and fragment shader pair that: transforms positions by a model-view-projection matrix, passes world-space normal to the fragment shader, and implements Blinn-Phong lighting with a single directional light. Provide code (vertex and fragment) and explain where you would minimize per-pixel work to keep this shader performant on mobile platforms. Assume inputs: vec3 position, vec3 normal; uniforms: mat4 model, mat4 viewProj, vec3 lightDir, vec3 lightColor, vec3 ambient.
Sample Answer
Approach
- Calculate world-space position & normal in the vertex shader, transform position by model-view-projection, compute lighting terms where possible per-vertex to reduce fragment cost, then do final Blinn-Phong per-pixel (or optionally per-vertex for cheaper mobile).
Vertex shader (GLSL 330)
#version 330 core
layout(location=0) in vec3 position;
layout(location=1) in vec3 normal;
uniform mat4 model;
uniform mat4 viewProj;
uniform vec3 lightDir; // assumed normalized, in world space
uniform vec3 lightColor;
uniform vec3 ambient;
out vec3 vWorldNormal;
out vec3 vWorldPos;
out vec3 vLightDir; // passing for per-pixel lighting or precomputed N·L
void main() {
vec4 worldPos = model * vec4(position, 1.0);
vWorldPos = worldPos.xyz;
// normal transform (assuming uniform scale / orthonormal model; otherwise use inverse-transpose)
vWorldNormal = normalize(mat3(model) * normal);
vLightDir = normalize(-lightDir); // direction from surface to light (lightDir points to light)
gl_Position = viewProj * worldPos;
}
Fragment shader (GLSL 330)
#version 330 core
in vec3 vWorldNormal;
in vec3 vWorldPos;
in vec3 vLightDir;
uniform vec3 lightColor;
uniform vec3 ambient;
uniform vec3 viewPos; // camera world position
uniform float shininess; // e.g., 32.0
uniform float specularStrength; // e.g., 1.0
out vec4 FragColor;
void main() {
vec3 N = normalize(vWorldNormal);
vec3 L = normalize(vLightDir);
vec3 V = normalize(viewPos - vWorldPos);
vec3 H = normalize(L + V); // Blinn half vector
float NdotL = max(dot(N, L), 0.0);
float spec = 0.0;
if (NdotL > 0.0) {
spec = pow(max(dot(N, H), 0.0), shininess) * specularStrength;
}
vec3 diffuse = lightColor * NdotL;
vec3 color = ambient + diffuse + lightColor * spec;
FragColor = vec4(color, 1.0);
}
Where to minimize per-pixel work for mobile
- Precompute cheap terms in the vertex shader: compute N·L per-vertex and optionally the specular term per-vertex and pass as a single interpolant to the fragment shader to avoid normal/half-vector math per-pixel.
- Avoid conditionals and expensive functions: reduce pow() frequency by using lower shininess or approximations (e.g., precomputed specular lookup or lerp).
- Use normalized lightDir uniform and avoid per-fragment normalization by normalizing in vertex and passing interpolated, but be aware interpolation may slightly change length — re-normalize only when necessary.
- Reduce varying count and texture lookups; pack values when possible.
- For many objects on very constrained hardware, perform full lighting per-vertex (Gouraud) and only do per-pixel for important objects/effects.
If you had to rank the top three or four skills to develop over the next couple of years, what would make your list, and why those over the alternatives?
Sample Answer
Direct answer
Rank by a deliberate criterion, not gut feel. Name the criterion you're using, which skills unlock the most future scope, which have the highest impact weighed against feasibility, or which close the gap between your current level and the next one, and include both technical and non-technical or soft skills rather than defaulting to an all-technical list, since most next-level gaps involve at least one of each.
Structured elaboration
Pick and state your ranking criterion explicitly before naming the skills, since the same three skills can be justified very differently depending on whether you're optimizing for near-term impact, long-term career growth potential, or impact weighed against feasibility. Naming the criterion is itself part of a strong answer.
Include at least one non-technical or soft skill alongside technical ones. An all-technical list usually signals either an early-career stage where that's genuinely the right focus, or a blind spot at a more senior stage, where communication, prioritization, or influence often matter more than additional technical depth.
Give each skill a brief, honest reason it made the cut over an alternative you considered and rejected. A ranked list without visible trade-offs reads as a wish list, naming what you left off, and why, shows the ranking was real.
Tie each skill back to a concrete situation where its absence cost you something or its presence would have helped, rather than justifying it in the abstract.
Worked example
"Using what most limits my scope right now as my ranking criterion, my list was: first, a specific domain depth I'm missing that currently forces me to hand off certain problems to someone else, second, clearer stakeholder communication, because I've noticed my updates sometimes need a follow-up conversation to clarify what I actually meant, third, prioritization under competing demands, since I've occasionally said yes to too much and delivered several things late rather than a few things well. I considered adding a fourth, negotiation, but ranked it below the other three because I have fewer real situations right now where it's the binding constraint, so investing there first would be lower leverage. If I ranked by long-term career growth potential instead of near-term scope, prioritization and communication would likely move above the technical depth item, since those compound more as scope grows."
Trade-offs & pitfalls
- A list with no explicit ranking criterion invites the interviewer to wonder whether it was thought through or assembled on the spot.
- An all-technical list at a more senior stage often signals a blind spot, since interpersonal and organizational skills tend to become the actual constraint past a certain level.
- An all-soft-skills list with no technical or domain component can read as avoiding the harder, more measurable half of growth.
- Naming skills with no honest reason for the ranking, or no example of where the gap actually showed up, turns a specific answer into a generic one that could apply to almost anyone.
You see periodic desyncs between client and server in a physics based online game. Provide a step by step debugging plan to locate the root cause, including logging strategies, hash/checksum placement, determinism tests, floating point handling, engine/compile differences, and how to reproduce in an automated replay harness.
Sample Answer
Step-by-step debugging plan
1) Clarify scope & reproduce reliably
- Collect reports: frequency, map/scene, player count, hardware, platforms.
- Try to reproduce locally with same scene/settings.
2) Add deterministic checkpoints & logging
- Insert tick-aligned checkpoints every N frames (e.g., physics step). Log: tick id, object IDs, positions, velocities, orientations, RNG seed, timestep.
- Use compact binary logs for replay; also human-readable summary for quick triage.
3) Hash/checksum placement
- At end of each fixed physics step compute lightweight checksums:
- Per-entity checksum (transform + velocities + other state).
- Global checksum (xor/sha1 of concatenated per-entity checksums).
- Log mismatch tick and both client/server checksums.
4) Determinism tests
- Run headless client/server on same machine/process with identical seeds to confirm engine determinism.
- Gradually diverge: enable/disable features (interpolation, client-side prediction, rollback).
- Use single-threaded physics build to rule out race conditions.
5) Floating-point handling
- Replace double/float mixes with consistent types; enforce same math order.
- Test with fixed-point or deterministic math library for comparison.
- Insert debug mode that snaps floats to reduced precision to reveal sensitivity.
6) Engine / compile differences
- Verify compiler flags, math libs, CPU instruction sets (SSE/AVX), optimization levels.
- Build identical runtime (same engine version/patches) across client/server. Test with -O0/-ffloat-store variations.
7) Race conditions & non-deterministic inputs
- Log all external inputs (network packets, input timestamps, frame time variations).
- Ensure network message ordering/time are deterministic in replay.
8) Automated replay harness
- Create harness that consumes binary logs and deterministically replays client & server in-process, comparing checksums each tick and halting on first mismatch.
- Add configurable options: single-threaded, fixed timestep, patched math mode.
- Run fuzzing on inputs and seeds; collect mismatch traces automatically.
9) Triage & fix
- Once failing tick isolated, bisect code paths (binary search via inserted toggles) to pinpoint code causing divergence.
- Apply fixes (consistent math, locking, serialization order), re-run harness and CI with regression tests.
10) Prevent recurrence
- Add deterministic unit tests into CI using replay harness and checksum assertions; alert on checksum drift.
This plan prioritizes reproducible logging, localized checksums, isolating FP and concurrency sources, and an automated replay loop to make debugging repeatable and fast.
What are alignment and padding in C structs, and why do they matter when you copy structs across platforms or reason about sizeof?
Sample Answer
Definitions. Alignment is a rule that an object of a type must sit at an address that is a multiple of that type's alignment requirement (often equal to its size for scalar types: a 4-byte int32_t at multiples of 4, an 8-byte double at multiples of 8). CPUs load aligned values fastest, and some cannot load misaligned ones at all. Padding is the unused bytes the compiler inserts inside a struct (between members, or after the last) so that every member is aligned and so that arrays of the struct keep every element aligned. cppreference's C struct page states the language rule: padding may appear between members or after the last one, but not before the first, and members are laid out in declaration order.
Seeing it. Compiled in a gcc:14 container on aarch64 Linux (GCC 14.4, gcc layout.c && ./layout), this program prints the layout:
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
struct A { char c; int32_t i; char d; double x; };
struct B { double x; int32_t i; char c; char d; };
int main(void) {
printf("A: size=%zu align=%zu c@%zu i@%zu d@%zu x@%zu\n", sizeof(struct A), _Alignof(struct A),
offsetof(struct A,c), offsetof(struct A,i), offsetof(struct A,d), offsetof(struct A,x));
printf("B: size=%zu align=%zu x@%zu i@%zu c@%zu d@%zu\n", sizeof(struct B), _Alignof(struct B),
offsetof(struct B,x), offsetof(struct B,i), offsetof(struct B,c), offsetof(struct B,d));
return 0;
}
Output:
A: size=24 align=8 c@0 i@4 d@8 x@16
B: size=16 align=8 x@0 i@8 c@12 d@13
Reading A: c is at 0, three padding bytes follow so i can start at 4, d is at 8, seven padding bytes follow so the double can start at 16, and the struct ends at 24. The members' own sizes add to 14 bytes (1 + 4 + 1 + 8), so 10 of the 24 bytes are padding. B holds the same four members with the large one first and the two chars together, and is 16 bytes: only 2 bytes of padding, at the end, to round the size up to a multiple of 8. Same data, a third less memory, just by reordering. (The compiler may not reorder members for you in C: declaration order is guaranteed.)
Why it matters.
sizeofis not the sum of the fields. Anything computed from a hand-added size (a buffer for N structs, an offset into a file) will be wrong. Usesizeofandoffsetof.- Copying and serializing structs across platforms is unsafe.
fwrite(&s, sizeof s, 1, f)or sending the struct over a socket writes the padding bytes (which hold indeterminate values, so output differs between runs and can leak stale memory) and bakes in this compiler's layout. A different compiler, a different ABI (the binary-level agreement between compiled code, which fixes sizes and alignments), a different word size (the CPU's natural integer width, such as 32 or 64 bits), or different packing options (compiler settings that remove the padding) can put members at different offsets, so the other side reads the wrong bytes. Byte order is a separate problem on top. Serialize field by field into a defined format instead: write each field's bytes at a fixed offset in a fixed byte order, and read them back the same way, never by copying the whole struct. - Memory and cache cost. Large arrays of badly ordered structs waste memory and cache lines (the fixed-size blocks, commonly 64 bytes, in which the CPU moves memory into its cache, so padding means fewer useful elements per block); ordering members from largest alignment to smallest is the usual first fix.
- Comparing structs with
memcmpcan fail even when all members are equal, because the padding bytes differ.
Practical habits. Print sizeof and offsetof on each target you ship to (the numbers above are for this aarch64 Linux GCC run; other ABIs can differ, for example in the size of long or pointers and in the alignment of double). Pin the expectation in the code with _Static_assert(sizeof(struct B) == 16, "layout changed"); (the C11 keyword; the shorter spelling static_assert needs #include <assert.h> before C23, and without it GCC 14 in its default C mode rejects the code, with an implicit-declaration error inside a function and a syntax error at file scope) so an unintended change breaks the build. Packing structs to match a hardware or wire layout brings its own hazards (misaligned access, portability).
Has there been a time you recommended something good for customers that cost short-term revenue? How did you weigh it, and how did you convince others?
Sample Answer
Direct answer
Here is the shape of a good answer, which you fill with your own real story if you have one. I found something that was good for customers but cost near-term revenue, sized the real cost (usually smaller than the headline), identified what we would watch to learn the long-term effect, and got agreement for a time-boxed test with a stop condition. I commit to the customer-good option when the loss is bounded and measurable, and the harm to customers is clear.
Weighing it
- Size the true short-term cost, net of refunds, chargebacks (a customer disputes the charge with their bank, which costs the business the money plus a fee) and support cost, not the gross headline (the top-line revenue figure before refunds and other costs).
- Name the customer harm: who pays or suffers today, and how.
- Choose leading indicators (early measurements that move before the long-term result does) for the long-term benefit (not just "trust will improve"): refund rate, support contacts, repeat purchase at 90 days.
- Propose a bounded test with finance agreeing the cost ceiling (the most revenue the team accepts losing during the test) beforehand.
- Decide the stop rule and what would flip your call.
Worked example (illustrative numbers)
A checkout shows a pre-checked $5 add-on. Assume 10,000 orders a month. With the box pre-checked, 600 orders keep the add-on: gross add-on revenue is 600 times $5 = $3,000 a month. 150 of those 600 later request a refund, so 450 orders at $5 = $2,250 a month is kept. If the box becomes opt-in, assume 200 orders choose it with almost no refunds: 200 at $5 = $1,000 a month.
Headline loss quoted by the revenue team: $3,000 minus $1,000 = $2,000 a month. Net loss after refunds: $2,250 minus $1,000 = $1,250 a month, or $15,000 a year (12 times $1,250). The $15,000 is a real cost; it is also smaller than the headline, and it is bounded. Support cost shrinks it further. At an illustrative $4 of agent time per refund request, 150 requests cost $600 a month, so the net loss is $2,250 minus $600 minus $1,000 = $650 a month, about $7,800 a year (12 times $650). I would present the refund-only $15,000 as the conservative ceiling and the support-adjusted figure as the likely cost.
The case for it is not "customers will love us": 150 refund requests a month is a measurable harm, each one a customer who felt tricked. The proposal: A/B test (two versions shown to different random groups) opt-in for one month, with the cost ceiling agreed with finance, tracking refund rate, support contacts and 90-day repeat purchase. The stop rule: if repeat purchase does not improve or support contacts do not fall, revisit.
Convincing others
Present the net cost, not the gross; let finance own the ceiling; show the customer complaints in their own words; offer a smaller first step.
Pitfalls
- Claiming long-term benefit with no mechanism to check it. "Trust" is not a number.
- Hiding the cost. State the net cost first, as a ceiling.
- Moralising. Frame as risk and customer evidence, not as ethics against colleagues.
Should we build this capability ourselves or buy it? Walk through the framework you would use to decide, and how your answer would change if the same question came up for a Game engine subsystem instead of a backend service.
Sample Answer
Direct answer
I score build vs. buy on total cost of ownership (the full multi-year cost, not just the sticker price), time-to-value, and strategic differentiation, and I treat "buy now with a build trigger later" as a real third option, not a temporary version of "buy." The framework holds for a game engine subsystem too, but the weights shift hard: real-time performance constraints and tight integration with the engine's core loop usually push toward build or a deep customization of a bought component, even when a backend service in the same situation would clearly say buy.
The framework
- Total cost of ownership: upfront build cost plus ongoing maintenance, versus subscription or license fees plus integration cost. Buy is rarely "free" after the sticker price; integration, data migration, and vendor management all cost real engineering time.
- Time-to-value: how fast each option gets you to a working, shippable state.
- Strategic differentiation: does this capability directly differentiate the product, or is it commodity infrastructure everyone needs. The more it's the former, the more building (and owning the roadmap) is worth paying for.
- Lock-in and exit cost: how hard is it to leave a vendor later, and does the vendor's roadmap risk diverging from what you need.
I put these into a simple weighted score so the trade-off is explicit rather than argued from vibes, rather than leaving each criterion as a separate, incomparable argument.
Worked example
Say a team is choosing between building an internal capability and buying a vendor product, with these inputs on a 0-10 scale (higher is better for that option):
| Criterion | Weight | Build score | Buy score |
|---|---|---|---|
| Cost (lower cost scores higher) | 40% | 3 | 7 |
| Time-to-market (faster scores higher) | 40% | 3 | 9 |
| Strategic differentiation | 20% | 8 | 3 |
Build=0.4(3)+0.4(3)+0.2(8)=1.2+1.2+1.6=4.0
Buy=0.4(7)+0.4(9)+0.2(3)=2.8+3.6+0.6=7.0
Buy wins on the initial score. I don't stop there, though: I set an explicit trigger for revisiting, for example if strategic differentiation is later assessed at 7 or higher and the cost gap closes within a defined payback window, that's the signal to build. That turns a one-time decision into a standing policy instead of a decision that quietly goes stale.
How the game engine case changes the answer
The same criteria apply, but two of them move a lot. Time-to-market for a bought subsystem often looks fast on paper but hides a large hidden integration cost: a third-party rendering, physics, or VFX tool has to slot into the engine's frame budget, asset pipeline, and existing tooling, and a mismatch there can cost more engineering time than building the narrower thing you actually need. Cost also shifts, since game middleware often comes with per-seat or per-title licensing and sometimes runtime royalties that compound with scale in a way a typical software as a service subscription doesn't. And lock-in is sharper: proprietary asset formats and pipeline dependencies from a bought tool can be more expensive to migrate away from than a backend vendor's API, because the whole content pipeline gets built around them. A team choosing between building or buying a VFX graph editor for its engine, for instance, is really weighing "commodity enough to trust a vendor's roadmap" against "core enough to the game's visual identity that owning it fully pays for itself," which is the strategic-differentiation axis doing more work than the cost axis.
Where this generalizes
The same weighted framework applies whether the thing under debate is an internal engineering tool, an analytics or observability stack, a feature store or model registry, or a database choice being decided mostly on service-level agreement guarantees versus cost. Two variants are worth naming explicitly because they flip the framework's direction: negotiating a multi-year exclusive vendor contract adds a lock-in cost that should be modeled explicitly as a negative weight on the buy side, not treated as a footnote; and open-sourcing an internal component you already built is the build-vs-buy question in reverse, where the "cost" is ongoing maintenance burden for external users and the "benefit" is community leverage and hiring signal, not revenue.
Trade-offs and pitfalls
- Scoring only the sticker price. The build side's maintenance cost and the buy side's integration and lock-in cost are usually the parts that get underestimated, not the headline numbers.
- Treating "buy" as permanent. Setting no revisit trigger means the decision never gets re-examined even after the strategic picture changes.
- Cutting corners to hit a deadline instead of making the trade-off explicit. Cutting automated test coverage to hit an eight-week deadline is a real build-vs-buy-adjacent trade-off (build fast and thin vs. build right and slower); naming it as a deliberate, documented trade-off is different from letting it happen by default.
- Applying a backend service's weights to a performance-critical or pipeline-integrated subsystem without re-deriving them. The framework is the same; the inputs are not, and skipping that re-derivation is how teams end up with a vendor tool wedged awkwardly into a frame budget it was never designed for.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths