Interview guides · Attain
Attain Software Engineer Interview Guide
Attain Software Engineer interview guide: the four reported rounds, system design, coding and behavioral questions, and a 7-day prep plan.
The 60-second brief
Attain's consumer applications include Klover and Merryfield, which candidates describe as consumer finance and rewards products. The engineering work candidates describe sits behind those apps: backend services that process transactions and hold consumer data, payments infrastructure on the Klover side, and a measurement platform fed by data pipelines. The reported topic breakdown for the role lists backend engineering, platform engineering and ad-tech measurement methods, namely attribution modeling and incrementality or sales lift measurement. Responsibilities candidates list include building and maintaining backend services, code review, observability, mentoring mid-level and junior engineers, and working with Product Managers on requirements.
This guide is for backend-leaning Software Engineer candidates at Attain, including the Staff and Lead levels that candidates mention. It covers the four reported rounds, all fifteen reported system design, coding and behavioral questions, the stack candidates list (Go, Python, Java or Node.js, PostgreSQL or MySQL plus NoSQL stores, AWS, Kafka-style streaming), and a seven-day plan. Data Scientist and Machine Learning Engineer loops are separate guides.
Candidates report four rounds over roughly 3-5 weeks: a Recruiter Screen, a Technical Phone Screen, a Virtual Onsite Loop covering coding, system design and behavioral topics, and Cross-Functional Interviews with partners such as Product Managers. The sequence can shift slightly with the level you interview for.
Focus areas
- Backend Engineering
- Platform Engineering
- Measurement Methodologies (Ad Tech)
Overview
The reported system design questions point at services where wrong answers cost money or trust. One asks for a secure, idempotent payment processing service, one for a leaderboard and rewards system for Merryfield, and one for a service that aggregates financial data from several third-party APIs. The others are more general: a high-throughput event tracking system for a mobile app, and scaling a relational database with high read latency. Preparing for this set means having a repeatable shape for a backend design (requirements, data model, write path, read path, failure handling, scaling) and a firm grasp of idempotency keys, retries, transactions, caching and queues, because candidates also report being asked how to keep data consistent when a payment API call fails midway.
The reported coding set is a group of classic structure problems rather than puzzles: the longest substring without repeating characters (sliding window), merging overlapping time intervals (sort then sweep), a key-value store with a time-to-live (hash map plus an expiry strategy), serializing and deserializing a binary tree (traversal plus a null marker), and an LRU cache (hash map plus doubly linked list). Each rewards choosing the right structure and then writing tidy, tested code in one language you know well. Candidates describe the role as listing Go, Python, Java and Node.js, so pick one and practise in it until the syntax is not a distraction.
The behavioral and cross-functional side matters because the role is described at Staff and Lead level in places. The reported prompts ask for a technical decision made with incomplete information, pushing back on a product requirement, mentoring a struggling junior engineer, debugging the most complex system failure you have seen, and advocating for technical debt work. Prepare stories where you made the call and can give numbers. If the measurement platform is your target team, also be able to explain attribution modeling and incrementality (comparing exposed and control groups to estimate lift) in plain language, since they appear in the reported topic breakdown.
The Attain interview process
These are the four stages candidates describe for the Software Engineer loop. The sequence and the presence of a dedicated product-collaboration round can vary by level, so confirm the format with your recruiter.
Recruiter Screen
Candidates describe the Recruiter Screen as a conversation about your background, what the role involves and where you want to work. Chicago and Redwood City are the engineering hubs candidates mention, and hybrid expectations are reported to vary by team and location. Treat it as a fit-and-logistics call: show that your experience maps to a backend role and use it to learn the shape of the rest of the loop.
What to demonstrate
- How your background lines up with a backend role at the level discussed (Staff and Lead are mentioned)
- Your location and work-arrangement preferences against what the team offers
- Whether your stack experience overlaps the listed languages, databases and cloud platform
How to prepare
- Write a short career summary that leads with backend systems you owned and ends with why payments, rewards or measurement work interests you
- List which of Go, Python, Java, Node.js, PostgreSQL, MySQL, a NoSQL store, AWS and Kafka you have used in production, and for what
- Prepare questions about the loop format, the team you would join, and hybrid expectations in Chicago or Redwood City
- Decide how you will answer a compensation question, without quoting a figure from memory
Technical Phone Screen
The Technical Phone Screen is reported as a technical discussion with an engineering leader or a senior peer, centred on core programming concepts, data structures and your past projects. Approach it as two halves: be fluent on fundamentals such as hash maps, arrays and trees, and be able to take someone through a project you built without losing the thread.
What to demonstrate
- Command of core programming concepts and data structures
- How clearly you explain a past project, including your own contribution and its trade-offs
How to prepare
- Rehearse the structure problems in the reported coding set (sliding window, interval merging, LRU cache, tree serialization) out loud, stating time and space complexity for each
- Pick two projects and outline each as problem, architecture, your role, one decision you would revisit, and the measurable outcome
- Practise explaining why you chose a hash map, a heap or a linked list in a given case, not just that you did
Virtual Onsite Loop
The Virtual Onsite Loop is reported as a series of focused interviews covering coding, system design and behavioral or leadership topics. This is where the coding, design and leadership questions on this page are practised. Candidates also describe reported evaluation areas for backend design (microservices, SQL versus NoSQL, Redis-style caching, Kafka or RabbitMQ queues, idempotency), coding patterns (sliding window, two pointers, BFS/DFS, dynamic programming) and payments topics (ACID, distributed transactions, PII handling, REST or GraphQL API design).
What to demonstrate
- Working, readable code with stated complexity and tested edge cases
- Backend design that states requirements and data model, then handles scale and failure
- Awareness of consistency, security and privacy in a payments or consumer-data setting
- Leadership stories told with concrete decisions and results
How to prepare
- Complete the idempotent payment service design end to end: idempotency key table, transaction boundary, behavior on timeout or unknown outcome, reconciliation
- Design the event tracking system and the third-party aggregation service with a queue, a retry policy and a stated consistency model
- Implement the LRU cache and the TTL key-value store from memory in your chosen language, with tests for capacity one and expired keys
- Write five story outlines covering the reported behavioral prompts, each with one number
Cross-Functional Interviews
The Cross-Functional Interviews are reported as meetings with partners such as Product Managers, to see how you work with others and turn business needs into technical execution. Candidates also say this round may be shaped differently depending on level. Approach it as a conversation about outcomes: lead with the user or business effect, then explain the technical choice and its cost in language a non-engineer can follow.
What to demonstrate
- Collaboration with product partners and handling of disagreement
- Ability to translate a business requirement into an engineering plan
- Clear explanation of technical concepts to non-technical stakeholders
How to prepare
- Prepare the story about pushing back on a product requirement, with the evidence you used and the alternatives you offered
- Practise turning a vague request, such as a rewards leaderboard for Merryfield, into questions about the goal, success metric, scale and rollout
- Rehearse explaining idempotency, eventual consistency and caching to someone who has never written a service
- Have an example of working with designers, QA or product on a deadline and what you traded off
Where candidates lose points
An LRU cache or TTL store that gets evictions or expiry wrong
Use a hash map plus a doubly linked list so get, put and eviction are O(1), and move a key to the front on every read. For the TTL store, decide between lazy expiry on read and a background sweep or min-heap, and state the cost of each. Test capacity one, overwriting an existing key, and a key read just after it expires.
A payment service design with no answer for a retry or a timeout
Persist the idempotency key with a unique constraint in the same transaction as the ledger write, return the stored result on a repeat request, and say what you do when the downstream outcome is unknown: mark it pending and reconcile instead of charging again.
Choosing NoSQL for write speed without naming the consistency you give up
Split the data by need. Money movement and balances belong in a store with transactions (PostgreSQL or MySQL); high-volume event data can sit in an append-oriented store or a queue. Say which reads may be stale and which must not be.
Scaling a slow relational database by jumping straight to sharding
Work in order: check the slow queries and indexes, add caching for hot reads, add read replicas and account for replication lag, and only then discuss partitioning or sharding with its cost to joins and transactions.
Behavioral stories told as "we" with no personal decision and no result
For each of the five reported prompts, write a story where you made the call, name the alternatives you rejected, and finish with a number. Practise saying "I" for your actions and naming teammates only for theirs.
Questions & practice
Write an algorithm to find the longest substring without repeating characters.
Medium · Coding & Data Structures
Approach
- Sliding window with a map from character to its last index: for each right index, if the character was last seen at or after left, move left to that index plus one; record the new index; best = max(best, right - left + 1). O(n) time, O(min(n, alphabet size)) space.
- Name the baselines: checking every substring is O(n^3), or O(n^2) with a set per start index. A set-based window that shrinks one character at a time is also O(n), because each character enters and leaves the window at most once.
- The classic bug is moving left backwards. On "abba", the final 'a' was last seen at index 0, before left = 2, so left must stay put (use max(left, last + 1)); otherwise the answer comes out as 3 instead of 2.
- Edge cases: the empty string returns 0, a run of one repeated character returns 1, and an all-unique string returns n. If the input is ASCII, a 128-entry int array beats a hash map.
- State the character model: Python strings iterate code points, while Java's charAt returns UTF-16 code units, so characters outside the BMP (emoji) need codePointAt handling.
Follow-up
- Return the substring itself, not just its length.
- Find the longest substring with at most k distinct characters.
- How would this work over a stream too large to hold in memory?
Implement a system to merge overlapping time intervals (e.g., user session times).
Medium · Coding & Data Structures
Approach
- Sort by start time, O(n log n), then sweep: if the current start is at or before the last merged end, set end = max(end, current end), otherwise start a new interval. The sweep is O(n), and the output takes O(n) extra space.
- Settle the boundary rule first: do [1,3] and [3,5] merge? For user sessions, ask whether a gap shorter than an idle timeout should also merge, and make that threshold a parameter.
- Edge cases: empty input, a single interval, an interval fully contained in another (which is why the update uses max rather than the new end), unsorted input, and invalid intervals with start after end.
- For sessions from many users, group by user id first, then merge each group; per-user groups also partition naturally across workers.
- If sessions arrive one at a time, keep a sorted map keyed by start (TreeMap, or a sorted list with bisect) and merge only the neighbours of the inserted interval, O(log n + k) for k absorbed intervals with a balanced tree.
Follow-up
- Compute the maximum number of concurrent sessions. (Sweep over +1/-1 events; with half-open intervals, process ends before starts at equal times.)
- Return the total time covered instead of the merged list.
- The session log no longer fits in memory. How do you merge it?
Write a function to serialize and deserialize a binary tree.
Medium · Coding & Data Structures
Approach
- Preorder DFS that writes each value and a null marker (for example '#') for every missing child, separated by commas; deserialise by consuming tokens from an iterator with the same recursion. O(n) time and space, and the output holds n values plus n + 1 null markers.
- Explain why the markers matter: preorder alone cannot distinguish tree shapes. The exception is a BST, where preorder alone suffices and decoding uses min/max bounds in O(n).
- A level-order (BFS) encoding with nulls works too, and trailing nulls can be trimmed; whichever you pick, deserialisation must mirror serialisation exactly.
- Edge cases: the empty tree, negative and multi-digit values (hence a delimiter), string values that contain the delimiter (escape them or length-prefix them), and a deeply skewed tree that exceeds the recursion limit (about 1000 frames by default in Python), which calls for an iterative version with an explicit stack.
- Test with a round trip: serialise, deserialise, serialise again, and compare the two strings on an empty tree, a single node, a left-skewed tree and a full tree.
Follow-up
- Make the encoding more compact. What would a binary format look like?
- Extend it to an N-ary tree.
- Serialise a BST without null markers and rebuild it in O(n).
Implement an LRU (Least Recently Used) cache.
Medium · Coding & Data Structures
Approach
- A hash map from key to node, plus a doubly linked list ordered by recency with sentinel head and tail. get moves the node to the front; put updates in place or inserts at the front, and when size exceeds capacity it removes tail.prev from both list and map. Both operations are O(1).
- Store the key inside each node; without it, evicting the tail node cannot delete the matching map entry, which is the most common bug.
- Know the library shortcuts and their semantics: Python's OrderedDict with move_to_end(key) and popitem(last=False), and Java's LinkedHashMap with accessOrder = true and an overridden removeEldestEntry returning size() > capacity. Then write the linked list by hand if asked.
- Edge cases: put on an existing key updates the value and recency without evicting; capacity 0 or 1; get on a missing key returns the agreed sentinel such as -1.
- Concurrency: get mutates the list, so a read lock is not enough. Use one mutex for correctness, segmented LRUs for throughput, or an approximation such as CLOCK or sampled LRU (Redis evicts by sampling) when exact order is not required.
Follow-up
- Make it thread-safe without one global lock becoming the bottleneck.
- Add a per-entry TTL to the cache.
- How would an LFU cache differ, and can it still be O(1)?
Denormalise tenant onto revisions and backfill it live
Hard · denormalisation · online migration · backfill
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
Approach
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
Follow-up
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?
Design a high-throughput event tracking system for a mobile app.
Medium · System Design & Architecture
Approach
- Pin the numbers first: peak events per second, average payload size, how fresh dashboards must be, and whether at-least-once delivery with deduplication is acceptable (it almost always is for analytics).
- Client SDK: batch events, give each a client-generated UUID and client timestamp, persist the queue to local storage while offline, and retry with exponential backoff plus jitter so a recovering backend is not hit by every device at once.
- Ingestion: stateless HTTP collectors behind a load balancer validate the schema, append the batch to a durable partitioned log such as Kafka keyed by device or user id (per-device ordering), and return 202 only after the append is acknowledged.
- Consumers: one stream job writes raw events to object storage as date/hour-partitioned Parquet for batch analysis; another deduplicates on event UUID within a window and updates real-time aggregates. Malformed events go to a dead-letter topic, not the floor.
- Trade-offs to state: a hot key skews one partition; client clocks drift, so store both client and server receive time and use watermarks for late events; when the log is saturated, collectors answer 429 and clients keep buffering.
Follow-up
- How would you produce exact daily counts when delivery is at-least-once?
- Old app versions keep sending a schema you have since changed. How do you handle them?
- One partition is far hotter than the rest. What do you do?
How would you architect a secure, idempotent payment processing service?
Medium · System Design & Architecture
Approach
- Idempotency at the API: the client sends an Idempotency-Key per logical payment; I store key, a hash of the request body, status and the final response under a unique constraint. A replay with the same body returns the stored response, a reuse with a different body is rejected, and a concurrent in-flight duplicate gets a conflict instead of a second charge.
- Model the payment as an explicit state machine (created, pending, succeeded, failed, refunded) in a relational database, and record money movement in an append-only double-entry ledger where every transaction's entries sum to zero; balances are derived, never edited in place.
- External processor calls: pass my own idempotency key downstream. On a timeout the outcome is unknown, so I never retry with a new key; I query the processor's status, and a scheduled reconciliation job compares my ledger with the processor's settlement reports.
- Avoid dual writes with a transactional outbox: the payment row and its event commit in one transaction, a relay publishes the event, and every consumer is idempotent on the event id.
- Security: TLS everywhere, tokenise card data through the processor so raw card numbers never touch my servers (this shrinks PCI-DSS scope), encrypt PII at rest with KMS-managed keys, keep an audit log, and rate-limit per user and per card.
Follow-up
- The processor charged the card but your request timed out. What does the user see, and how does the system settle it?
- How long do you keep idempotency keys, and what are they scoped to?
- How do you process a partial refund without breaking the ledger invariant?
Design a leaderboard and rewards system for the Merryfield application.
Medium · System Design & Architecture
Approach
- Clarify scope: global, friends or weekly boards, how many users, which actions earn points, how fresh ranks must be, and which rewards carry money (those need stricter controls than badges).
- Source of truth is an append-only points ledger in a relational database, one row per earning event with a unique event id so a retried event cannot award points twice; the leaderboard is a derived view you can rebuild from it.
- Serve ranks from a Redis sorted set per board and period: ZINCRBY on each points event, ZREVRANGE for the top N and ZREVRANK for one user's rank, all O(log N) (plus M returned items for a range). Use a key per week with an expiry so resets need no deletes, and break ties by folding a time component into the score while staying within a double's 53-bit integer precision.
- Rewards: a rules consumer reads points events from a queue, checks thresholds and issues each reward idempotently (unique on user, reward and period). Cash-like rewards go through the payment path with limits and fraud checks.
- Scale and abuse: shard very large boards and merge per-shard top-N for the global top; give long-tail users an approximate rank from a score histogram; validate point-earning events server-side and rate-limit them, because a client-reported score is trivially forged.
Follow-up
- How do you show a user's rank when they are far outside the top 100 among tens of millions of users?
- Points are revoked after a fraud review. How do the ledger, the board and any issued reward stay consistent?
- How do you run the weekly reset with no gap in the leaderboard?
How would you scale a relational database that is currently experiencing high read latency?
Medium · System Design & Architecture
Approach
- Diagnose before scaling: find the expensive queries with pg_stat_statements or the slow-query log, run EXPLAIN ANALYZE on them, and check for sequential scans, N+1 query patterns, lock waits, cache hit ratio and whether the host is CPU- or IO-bound.
- Cheapest fixes first: composite or covering indexes that match the WHERE and ORDER BY, keyset pagination instead of large OFFSETs, fixing N+1 access in the ORM, connection pooling (for example PgBouncer), then a larger instance.
- Add a cache-aside layer (Redis) for hot, read-mostly data with a TTL plus invalidation on write; guard against stampedes with request coalescing and jittered TTLs.
- Add read replicas and route reads to them, then handle replication lag: send a user's reads to the primary for a short window after they write, or track the commit position and read from a replica only once it has caught up.
- Structural options, in order of cost: materialised views or denormalised read models for heavy aggregates, partitioning large tables by time, and sharding last, because it makes cross-shard joins and transactions expensive.
Follow-up
- Replica lag jumps to several seconds during a batch job. What breaks for users, and how do you contain it?
- How do you keep the cache correct when two services write the same rows?
- When would you shard, and on which key?
Design a service that aggregates and serves financial data from multiple third-party APIs.
Medium · System Design & Architecture
Approach
- Clarify: which providers, how fresh the data must be, read volume compared with each provider's rate limits, and how user credentials or OAuth consent are obtained and refreshed.
- One adapter per provider behind a common interface that maps responses into a canonical schema (accounts, balances, transactions with amounts as integer minor units plus a currency code and the provider's transaction id). Reads are served from my own store, never by calling providers on the request path.
- Sync workers pulled from a queue, triggered on a schedule or by provider webhooks, with a per-provider token-bucket rate limiter, retries with exponential backoff and jitter, and a circuit breaker; when a provider is down I serve the last good data with its last-updated time.
- Writes are idempotent upserts keyed on provider and provider transaction id, with explicit handling of pending transactions that post later, sometimes under a new id.
- Security and isolation: encrypt tokens with envelope encryption (KMS), apply least-privilege access and audit logs around PII, and keep each provider in its own worker pool so one slow provider cannot starve the others.
Follow-up
- A provider silently changes its response format. How do you detect it before bad data reaches users?
- How do you backfill two years of history for a new user without exceeding the provider's rate limits?
- A pending transaction disappears and reappears with a different id. How do you avoid showing it twice?
Design a key-value store with a time-to-live (TTL) feature.
Medium · Coding & Data Structures
Approach
- Clarify whether this is an in-memory class (set with ttl, get, delete) or a distributed service, and what precision expiry needs.
- Core: a hash map from key to (value, expiresAt), computed from a monotonic clock so wall-clock jumps cannot expire or revive keys. Lazy expiry on get: if now >= expiresAt, delete the key and return a miss. Both operations are O(1) on average.
- Lazy expiry alone leaks memory for keys nobody reads again, so add active cleanup: a min-heap of (expiresAt, key, version) where each pop checks the version and skips entries made stale by an overwrite (O(log n) per set), or a timing wheel, or Redis-style periodic sampling of keys that carry a TTL.
- Concurrency: a single lock is the simple correct version; striped locks or a concurrent map scale better, with the sweeper taking the same locks as writers.
- Distributed extension: consistent hashing to partition keys, replication, storing absolute expiry times so replicas agree, an append-only log for durability, and a memory-pressure eviction policy such as LRU kept separate from TTL expiry.
Follow-up
- Why a monotonic clock, and what happens across replicas that cannot share one?
- Add a call that returns the remaining TTL, and an option to refresh the TTL on read.
- Memory fills up before keys expire. What do you evict?
Describe the most complex system failure you have ever debugged. What was the root cause and how did you fix i
Medium · Behavioral & Leadership
Describe the most complex system failure you have ever debugged. What was the root cause and how did you fix it?
Approach
- Pick a failure whose root cause was not obvious and in which you drove the investigation; open with impact in two or three sentences: the symptom, who was affected and for how long.
- Show the order of work: mitigate first (rollback, feature flag, failover, shedding load), then build a timeline of what changed (deploys, config, traffic, dependencies) before forming theories.
- Walk through the evidence: the metrics, logs or traces you used, each hypothesis you ruled out and the data that ruled it out, and how you separated the trigger from the cause (for example, a deploy that raised connection counts and exposed a pool limit that already existed).
- Close with the fix at two levels: the immediate change, and the systemic one (a test that reproduces the failure, an alert that would have fired earlier, a guardrail or a runbook), plus the postmortem actions you personally owned.
- What sinks the answer: 'we' throughout with no clear personal role, a root cause stated without evidence, or a story that ends at the hotfix with nothing that stops a recurrence.
Follow-up
- Which hypothesis did you rule out, and what data ruled it out?
- What alert or dashboard would have caught it earlier?
- If the same symptom reappeared tomorrow, what would you check first?
Suggested practice checklist
The plan follows the reported loop: coding structures first, then relational and cache questions, payments design, event and rewards design, behavioral stories, and a mixed rehearsal.
Day 1 — Map the loop and your stack
- Write the four reported rounds in order and next to each note the question types it covers: coding and design in the Virtual Onsite Loop, product-facing discussion in the Cross-Functional Interviews.
- List the listed languages, databases and tools (Go, Python, Java, Node.js, PostgreSQL, MySQL, a NoSQL store, AWS, Kafka) and mark which you have used in production.
- Choose the one language you will code in for every coding question this week.
Deliverable: A one-page loop map plus a stack-gap list that names the language you will code in.
Day 2 — Core structures: window, intervals, linked cache, tree
- Implement the longest substring without repeating characters with a sliding window and a last-seen index map, and test an empty string and an all-same-character string.
- Implement merging overlapping time intervals by sorting on start time and sweeping, including touching intervals and one interval fully inside another.
- Implement the LRU cache with a hash map and a doubly linked list, then serialize and deserialize a binary tree with preorder traversal and a null marker.
Deliverable: Four working implementations with tests, each with its time and space complexity written above it.
Day 3 — TTL store and relational read scaling
- Implement the key-value store with a time-to-live: write lazy expiry on read, then add a min-heap or periodic sweep, and note memory and CPU trade-offs.
- Outline how you would scale a relational database with high read latency: query plans and indexes, cache for hot reads, read replicas and replication lag, then partitioning.
Deliverable: A tested TTL store, a one-page ordered scaling checklist, and solved practice exercises with a note on locking and versioning.
Day 4 — Payments and third-party data design
- Design the secure, idempotent payment processing service: idempotency key table, request flow, transaction boundary, outbox or queue for follow-up work, and reconciliation of unknown outcomes.
- Add the security layer: what is encrypted at rest and in transit, which fields count as PII, and what gets logged.
- Design the service that aggregates financial data from multiple third-party APIs: per-provider timeouts, retries with backoff, caching, partial results and a stale-data policy.
Deliverable: Two design write-ups, each with a diagram, a data model and a list of failure modes with the response to each.
Day 5 — Event pipeline, rewards and measurement vocabulary
- Design the high-throughput event tracking system for a mobile app: batching on the client, an ingestion API, a queue, a consumer that writes to storage, and handling of duplicates and late events.
- Design the leaderboard and rewards system for Merryfield: a ranking structure such as a sorted set, a write path that credits rewards exactly once, and a read path that can serve slightly stale ranks.
- Write plain-language definitions of attribution modeling and incrementality or sales lift measurement, with one example of each.
Deliverable: Two design write-ups with capacity estimates and a half-page glossary of the measurement terms.
Day 6 — Behavioral stories
- Write a story outline for each reported prompt: the incomplete-information decision, the pushback on a product requirement, mentoring a struggling junior engineer, and technical debt advocacy.
- Write the outline for the most complex system failure you debugged, with the timeline, the root cause, how you separated trigger from cause, and the fix.
- Add one number to each story and cut every sentence that uses "we" for something you did.
Deliverable: Five story outlines, each with situation, your decision, result and one metric.
Day 7 — Mixed rehearsal
- Do one coding problem and one design question aloud, in your chosen language, narrating requirements and trade-offs as you go.
- Run the pushback story as a role-play with a friend acting as Product Manager and answer in terms of user impact rather than jargon.
- Write the questions you will ask the recruiter and the cross-functional partners about the team, the stack and the hybrid arrangement.
Deliverable: A recording or notes of the mixed rehearsal with a list of three fixes, plus your written questions for the loop.
Stories for the Attain leadership and product conversations
The reported behavioral prompts centre on ownership and influence at a senior level: deciding under uncertainty, pushing back on product requests, mentoring, learning from a major failure and defending technical debt work. Candidates also describe the Cross-Functional Interviews as a conversation with partners such as Product Managers, so have at least one story that shows how you work with product.
- Describe the most complex system failure you have ever debugged. What was the root cause and how did you fix it?
- Tell me about a time you disagreed with a Product Manager on a feature's technical feasibility. How did you resolve it?
Tell me about a time you had to make a critical technical decision with incomplete information.
Medium · Behavioral & Leadership
Approach
- Choose a decision you owned and that was costly to get wrong, such as picking a datastore, queue or retry strategy before real traffic numbers existed. Open with the decision and the one or two facts you did not have, in two sentences, then spend the rest on reasoning.
- Show how you shrank the uncertainty cheaply: a spike or load test on a representative slice, reading existing logs and metrics, or asking someone who had faced the same choice. Say which assumptions you wrote down and agreed with others, so the decision was explicit instead of a hunch.
- Explain the choice through reversibility: prefer the option that is easiest to undo, ship behind a feature flag or a staged rollout, and name a tripwire metric that would make you revisit the call. A one-way door deserves more investigation than a two-way door.
- Report the result with numbers and say which assumption held and which did not. Close with what you now do earlier. The mistake that sinks this story is one where luck decided the outcome, or where the decision looks obvious in hindsight.
Follow-up
- What was the one piece of information you most wanted, and what would it have cost to get it?
- Which assumption turned out to be wrong, and how did you find out?
- How would you have handled it if the decision could not be rolled back?
Describe a situation where you had to push back on a product requirement because of technical constraints.
Medium · Behavioral & Leadership
Approach
- Pick a case where the product request was reasonable and the technical constraint was real: consistency, latency, the data model, a third-party limit or security. A fitting example is a request to show a reward or balance total instantly while the underlying entries settle asynchronously.
- Bring evidence instead of opinion: a measurement, a concrete failure scenario, a load estimate or a small prototype. State the cost of the original request in terms the Product Manager cares about, such as visible errors, delay to other launches or the risk of showing wrong figures.
- Do not stop at no. Offer two or three alternatives with their trade-offs, such as a reduced first version, a phased rollout, an eventually consistent display with clear labelling, or a flag-guarded experiment, and say which one you recommended and why.
- Describe how you ran the conversation: early, with the user problem behind the request understood first, and in writing so the decision is recorded. End with what shipped, what each side gave up and how the working relationship stood afterwards. Avoid a story where you were simply right and the other person was wrong.
Follow-up
- What would you have done if the Product Manager still wanted the original version after seeing your evidence?
- How did you decide the constraint was firm and not just inconvenient?
- How did you check afterwards that the alternative met the user need?
Tell me about a time you mentored a junior engineer who was struggling.
Medium · Behavioral & Leadership
Approach
- Begin with a diagnosis, because the fix depends on the cause: a skills gap, missing codebase context, unclear expectations, or lost confidence after harsh reviews. Say how you found out, for example by pairing, reading their pull requests and holding an open one-to-one conversation.
- Lay out your actions in order: agree one or two small, visible goals; pair on the first task; write review comments that explain the reason instead of dictating the fix; share a simple template for design notes; then step back as they take ownership. Say how you resisted the urge to take the work over yourself.
- Measure progress with something observable: fewer review rounds per pull request, a feature they shipped without incident, their first on-call shift handled alone, or a design they presented to the team. Give a before and after, with numbers where you can.
- Be honest about what did not work and what you changed. Speak about the person respectfully and without blame. At Staff or Lead level, add how you balanced mentoring against your own delivery and how you made the growth outlast your involvement.
Follow-up
- What would you have done if there had been no improvement after a few weeks?
- How did you balance this against your own deliverables?
- How do you give hard feedback to someone who is already discouraged?
How do you advocate for paying down technical debt in a feature-driven environment?
Medium · Behavioral & Leadership
Approach
- Answer with a method and a real example. The method: turn debt into a cost the team already feels. Count the incidents it caused, the review and onboarding time it eats, how long a change in that module takes compared with elsewhere, and which feature work it blocks.
- Tie each item to a goal the Product Manager owns, such as a payment retry bug that generates support tickets or a slow endpoint that limits a launch. Framed that way, the cleanup enables features rather than competing with them.
- Keep it bounded and incremental: fold small cleanups into feature work in the same files, reserve a regular slice of each sprint, and take on a larger refactor only with an owner, an exit metric and a flag-guarded rollout. Do not push for a full rewrite unless you can show why incremental change cannot work.
- Prioritise by interest paid and risk: debt in the payment path or in code changed every week comes before stable code nobody touches. Say what you deliberately leave alone, and track the work through blameless post-mortem action items so it stays visible. The mistake to avoid is a vague appeal to clean code with no cost attached.
Follow-up
- How would you handle a quarter where leadership wants only feature work?
- How do you decide that a rewrite is justified instead of incremental refactoring?
- Which metric would show that the debt work paid off?