Attentive's platform sends personalized SMS, email and mobile messages for e-commerce brands. Software Engineers work on the systems behind that: real-time messaging pipelines, high-velocity subscriber list processing, distributed metrics monitoring, and stream integration via Kafka. The role also covers low-latency REST APIs, event-driven data ingestion and dashboard applications, and React front-end experience is listed for full-stack tracks, so the role spans backend services and, on some tracks, front-end state management.
Engineers on the role are described as owning features from architectural design through deployment and operational monitoring, and as working on technical debt from rapid company growth. Both show up in the reported behavioral questions: delivering a project on top of existing debt, and weighing performance work against shipping features under a deadline.
The reported questions follow that domain closely. Coding prompts include an evaluator for nested expressions such as ( MULT 3 ( ADD 3 4 ) ) with LET bindings, a binary search over timestamped logs, a common-ancestor check between entities, and top-k frequency tracking with a heap. Practical prompts cover async JavaScript bugs, deep cloning with circular references, normalizing third-party JSON, and a paginated React list. Design prompts cover a metrics monitoring platform, a photo upload and CDN service, an SMS broadcast scheduler, and an integration engine that pulls subscriber lists under rate limits.
Given that spread, prepare in the languages you will actually type in (Python, Java, Go or TypeScript/JavaScript are named in the requirements) and have one messaging-shaped design worked through end to end: scheduling, queueing, deduplicated dispatch and rate-limited delivery.
Recruiter Phone Screen
reportedCandidates describe this call as covering your technical background, your career trajectory and whether expectations line up on both sides. Treat it as the place to connect your experience to the work the role describes (messaging pipelines, data ingestion, APIs or front-end dashboards) and to surface any hard constraints, such as start date, location or a competing timeline, before a loop is scheduled. Ask which track you are being considered for, since the later loop can include either practical application coding or front-end React work depending on the track.
What to demonstrate
- How clearly you tie your past projects to backend, streaming or front-end work of the kind the role describes
- Whether your constraints and timeline are stated plainly enough to plan the rest of the process around
- Whether you leave the call knowing your track and what the technical screening will cover
How to prepare
- Write a short career narrative that ends on why messaging or event-driven systems are relevant to what you have built, and say it aloud until it no longer needs notes
- List your constraints (start date, location, authorisation, other processes and their deadlines) in one line each before the call
- Prepare two questions: which track you are interviewing for, and which language and editor the technical screening uses
Technical Screening
reportedCandidates report one or two remote coding sessions in a collaborative editor, covering algorithmic problems, debugging asynchronous code, or small live-coding scenarios. Reported coding questions such as a nested expression evaluator, binary search over timestamped logs, top-k frequencies with a heap, and fixing bugs in promise chains are good practice for this stage. Requirements may be left open-ended, so state your assumptions aloud and confirm edge cases before writing code. Without a test runner, tracing a sample input by hand is how you show the code works.
What to demonstrate
- Whether you pin down the input format and edge cases (malformed expressions, empty log ranges, ties in frequency) before coding
- Whether your approach matches the input size, for example binary search bounds over sorted timestamps rather than a linear scan
- Whether you can locate an async bug by reasoning about execution order instead of rewriting the code
- Whether you trace a sample through your own code and state its complexity without being prompted
How to prepare
- Implement a tokenizer plus recursive evaluator for ADD, MULT and LET from a blank file, including nested scopes and a clear error for unbalanced parentheses
- Write lower-bound and upper-bound binary search from memory and use them to return all log entries between two timestamps, inclusive and exclusive variants
- Take three broken async snippets (a missing return inside .then, async callbacks inside forEach, an unhandled rejection in a polling loop) and explain the output order before fixing each
- Solve top-k frequent elements with a size-k min-heap and say why it is O(n log k)
Virtual Interview Loop
reportedCandidates describe a virtual loop of several back-to-back sessions: data structures and algorithms, practical application coding (or front-end React work, depending on your track), scalable system design, and a behavioral session with an engineering manager or a member of the leadership team. For the system design session, practise the reported design questions, which are messaging and data shaped: an SMS broadcast scheduler, an integration engine pulling subscriber lists under rate limits, a metrics monitoring platform, and a photo upload and CDN service. Because the sessions run back to back, rehearse switching between coding, design and behavioral answers without a reset between them.
What to demonstrate
- In design, whether you start from functional requirements, peak throughput and the API and data model before drawing infrastructure
- Whether delivery guarantees are explicit: retries deduplicated by an idempotency key, the remaining duplicate window stated, and back-pressure toward downstream gateways or third-party APIs
- In practical coding, whether you handle real-world data problems such as missing fields, bad types, circular references and pagination
- In the behavioral session, whether your stories show your own decisions and the trade-offs behind them
How to prepare
- Work the broadcast scheduler end to end: schedule storage, a claim-with-lease dispatcher, per-subscriber idempotency keys, a rate limit toward the SMS gateway, and the duplicate window that remains if the gateway cannot deduplicate
- Work the third-party ingestion design: checkpointed pagination cursors, backoff with jitter on rate-limit responses, TTL on cached records, and what a partial sync leaves behind
- If your track includes front-end work, build a React list with filtered input and API pagination, and write a deep clone that survives circular references using a WeakMap
- Prepare one story per reported behavioral theme: technical debt, pushback on a design, your hardest project, and performance versus features
6 candidate reports. Individual accounts describe a particular role and hiring cycle.
Attentive Software Engineer Interview Experience — Two Onsite Rounds and an Unexpected Catalog Design
I was contacted and invited to an onsite. Round 1 combined coding and system design. Round 2 included coding, system design, and behavioral questions. I felt that my system design and behavioral performance in Round 2 was poor, and I was ultimately rejected. There were not many interview reports about this company, so I summarized everything I could find. All the questions I received turned out t…
Read full experienceAccount Executive interview at Attentive: interview experience
The intended process was recruiter screen, hiring-manager round, VP screen, and panel presentation, but my own process ended after the hiring-manager call. I did not reach the later stages. That manager interaction threw me off. She did not build rapport and came across as stand-offish, as though she was checking boxes. The questions were not the problem, but the call felt like a prove-it-fast ex…
Read full experienceAttentive Software Engineer Interview Experience: role closed after the phone screen
I started with an initial phone screen. After that, I expected a fairly standard sequence: a second round with coding and system design, a third round with more of the same assessment, and then a meeting with the team. The role closed before the process could get that far, so I never reached those later rounds. It left me feeling as though the interview process was just about to expand, and then…
Read full experienceAttentive Software Engineer Interview Experience: dismissive managerial conversation
After a recruiter screening, I had a managerial round. I received the job description, but the details about the structure and expectations turned out to be wrong. Before the questions even began, the interaction felt uncomfortable because the tone did not feel respectful. The conversation came across as skeptical and dismissive. My answers were treated as though they had been discounted from the…
Read full experienceAttentive New Grad Software Engineer Interview Experience — Coding and System Design Blended Into One 75-Minute Screen
It was 75 minutes long. They said it would be coding + system design, but in practice the two were mixed together, and the questions were pretty scattered and casual. The last 5 minutes were reverse behavioral questions, where I got to ask them things. The problem wasn't quite like a LeetCode problem — they didn't clearly spell out what the input was. They wanted me to design it myself and then j…
Read full experiencePracHub editorial advice for the preparation topics above.
Evaluating ( LET X ... ) with one global variable map, so nested or shadowed bindings leak out of their scope
Separate tokenizing from evaluating, and have the evaluator take a scope that is a stack of maps (or a parent-linked environment). Push a new frame when entering LET, bind names in order so later bindings can use earlier ones, and pop it when the expression returns. Before calling it done, test ( LET X 2 ( LET X 3 X ) ) alongside the outer X, an undefined variable, and unbalanced parentheses, and say what each should return or raise.
Finding the start of the time window with binary search and then scanning, or getting the end boundary off by one
Ask whether the window is inclusive on both ends and whether timestamps repeat. Then use two searches: the first index with timestamp >= start and the first index with timestamp > end, and return the slice between them. Trace it on an empty range, a range before all logs, a range after all logs, and a run of identical timestamps. State the cost as O(log n + k) for k results.
Rewriting the whole async snippet instead of naming the bug that causes the wrong output
Predict the output order first, then check it against the observed behaviour. The usual causes are a .then callback that does not return its promise, an async function passed to forEach (which does not await it), a missing await in a loop, or a rejection with no handler. Name the mechanism in one sentence, make the smallest fix, and say whether the calls should run in sequence or together with Promise.all, since that choice changes ordering and error behaviour.
Designing the SMS broadcast engine without saying what happens when a worker dies halfway through a send
Make delivery semantics the centre of the design, not a note at the end. Store each scheduled broadcast with a state, let workers claim due batches with a lease that expires, and record an idempotency key per (broadcast, subscriber) so a retried batch skips sends already recorded as done. Then state what the key cannot cover: if a worker crashes after the gateway accepts a message but before the send is recorded, the retry sends it again unless the gateway deduplicates on a key you pass it. Without that support, delivery is at-least-once, and you should say so. Put a rate limiter between the dispatcher and the gateway, and walk through a crash mid-batch to show which messages are resent and which are skipped.
Treating the third-party subscriber sync as a simple loop over API pages, ignoring rate limits, partial failure and stale cache
Give each external account a token bucket sized to its API quota, persist the pagination cursor after every page so a failed sync resumes rather than restarts, and back off with jitter on rate-limit responses. Say what TTL the cached records carry and what a reader sees while a sync is half complete. Tie the bank question on reliable third-party ingestion to this before your loop.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Build a custom expression parser that can evaluate nested mathematical…
Build a custom expression parser that can evaluate nested mathematical operations such as ( ADD 3 4 ) or ( MULT 3 ( ADD 3 4 ) ), and extend it to support variable assignment like ( LET X ( ADD 5 2 ) ).
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Implement an optimal solution to process streaming data and track top …
Implement an optimal solution to process streaming data and track top element frequencies using heaps or custom priority queues.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Merge partitioned event streams into one ordered feed with bounded lateness
The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.
Approach
- Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
- Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
- Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
- Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.
- Choose the late-event policy from what the projection is keyed on. The projection upserts on (aggregate_id, aggregate_version) and discards a version it has already applied, so a late event is safe to apply out of order and correctness never depended on the merge at all. Apply it, recompute the affected feed page, and count lateness so the 30-second budget can be re-derived from data rather than folklore.
- Say what the merge does not buy: ordering is guaranteed within one aggregate by the log's partitioning, and no watermark makes the cross-aggregate order authoritative. Two events from different aggregates in the same millisecond have no true order, so the feed's order is a presentation choice that must be stable rather than correct.
Worked solution 35 min
- Write the heap comparator on (occurred_at, event_id) and the per-partition head refill.
- Write the watermark computation and the emit-loop condition, then list which buffered events are held at a chosen instant.
- Compute the buffer at 4,000 events per second, 30 seconds and 1 KB per event, and state what fraction of a worker's heap that represents.
- Add the idle-partition marker and trace the watermark with one silent partition, both with and without the marker.
- Write the late-event path and name the key that makes applying it safe.
Follow-up
- The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
- The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?
- One partition is ten minutes behind because its producer is slow. Do you stall the feed or emit without it?
Stop tag and share joins from fanning out a page
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
- Attach the tags for display after the page has been cut — LEFT JOIN LATERAL (SELECT array_agg(rt.tag_id) FROM resource_tag rt WHERE rt.resource_id = p.resource_id) ON TRUE over the 50 returned rows. Aggregate over the page, never over the tenant.
- Index both directions and say which query each serves: PK (resource_id, tag_id) serves the lateral lookup, (tag_id, resource_id) serves the EXISTS probe by tag, and resource_share needs (shared_with_user_id, resource_id) for the same reason. An index covering one direction only leaves the other as a scan.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
- Where does the correct total come from when the tenant holds 4M resources and the header must not cost 200 ms?
Denormalise tenant onto revisions and backfill it live
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.
- Step three, backfill: batch by primary key rather than by created_at so the cursor is dense and resumable — UPDATE resource_revision rr SET tenant_id = r.tenant_id FROM resource r WHERE r.resource_id = rr.resource_id AND rr.revision_id > $1 AND rr.revision_id <= $1 + 5000 AND rr.tenant_id IS NULL — committing per batch and persisting the cursor. Throttle on replica replay lag and on dead-tuple count, since each batch writes 5,000 new row versions. Run the backfill before the index exists so those updates can stay HOT.
- Step four, index then enforce then contract: CREATE INDEX CONCURRENTLY (cannot run inside a transaction block, scans the table twice, waits on open transactions, and leaves an INVALID index to drop concurrently if it fails); ADD CONSTRAINT ... CHECK (tenant_id IS NOT NULL) NOT VALID, then VALIDATE CONSTRAINT, which takes only SHARE UPDATE EXCLUSIVE, after which SET NOT NULL uses the validated check instead of re-scanning on PostgreSQL 12 and later. Only then move the audit reads onto the column and, in a later deploy, delete the join path.
Worked solution 40 min
- Write the five steps as separate scripts and state, for each, the lock mode it acquires and the deploy it pairs with.
- On a 20M-row copy, run the ADD COLUMN while a 30-second transaction holds a lock on the table, and record how long unrelated queries queue behind it.
- Run the batched backfill at 5,000 rows, kill it mid-run, restart from the persisted cursor, and confirm no row is processed twice and none is skipped.
- Build the index concurrently under concurrent write load, then add the CHECK ... NOT VALID, VALIDATE it and SET NOT NULL, timing each.
- Compare the audit-feed plan before and after: join-and-filter versus an index seek with no Sort.
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?
- A resource must now be movable between tenants. What does that do to the composite foreign key and to the revisions already written?
Architect a high-volume photo upload and CDN storage service similar t…
Architect a high-volume photo upload and CDN storage service similar to Imgur, addressing image transformation, caching strategies, and storage scaling for millions of users.
Approach
- Choose a partition key and say what query it makes expensive.
- State the consistency you need, and where you are willing to be stale.
- Name the failure you are designing for, then the recovery path.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
Design a data integration engine that connects to third-party customer…
Design a data integration engine that connects to third-party customer management APIs, pulling large subscriber lists while managing rate limits, time-to-live (TTL), and cache storage.
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- State the consistency you need, and where you are willing to be stale.
- Name the failure you are designing for, then the recovery path.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Design an automated subscriber message broadcast engine that schedules…
Design an automated subscriber message broadcast engine that schedules, queues, and dispatches SMS messages based on scheduled times or dynamic triggers.
Approach
- Name the read and write paths separately; they rarely have the same bottleneck.
- Choose a partition key and say what query it makes expensive.
- Fix the scope first: who calls this, how often, and what they do when it fails.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
Parse and transform raw third-party JSON payload structures into inter…
Parse and transform raw third-party JSON payload structures into internal data models while handling missing fields and bad data types.
Approach
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Choose what to break when replication lag reaches forty seconds
Reads are served from two replicas: 14k requests/second, about 85% absorbed by cache, so roughly 2.1k reads/second reach the database. Writes go to the primary at 1.2k/second. A tenant's backfill drives replication lag from under 100 ms to 40 seconds and it is still climbing. Sessions that have just written are pinned to the primary. Decide, endpoint class by endpoint class, whether to serve stale, fail, or route to the primary, and justify each choice with the load it adds to the primary. Then state what you would have built beforehand.
Approach
- Establish blast radius before cause, because mitigation and diagnosis have different deadlines. The decisive arithmetic is what happens if the database reads move to the primary: 2.1k reads/second on top of 1.2k writes/second roughly triples its operation count, on the node already absorbing the backfill that caused this. Reads and writes are not equal in cost, so treat that as an argument against a blanket move rather than as a capacity model - but it is enough to rule out routing everything to the primary.
- Classify endpoints by what staleness costs, not by how important they feel. Reads whose staleness is invisible - listings, search, counters - stay on the replica and return the watermark so the client can tell. Reads that immediately follow that same session's write keep their primary pin, which is a small bounded slice of traffic rather than the whole 2.1k/second. Reads that feed a decision with a side effect - authorisation, quota, the read half of a read-modify-write - must not be stale at all, because a 40-second-old permission row is the stale-permission failure wearing a different costume; those go to the primary or fail.
- Shed instead of queueing. If the must-be-fresh class alone exceeds the primary's headroom, refuse its lowest-value slice with 503 and a retry-after. A request queued behind a saturated primary holds a connection for a client that has already given up, and the retry storm that follows is what turns degradation into an outage. Bound the connection pool per role so the read fallback cannot consume the write path's connections - that bulkhead is the single decision that determines whether writes survive the next ten minutes.
- Attack the cause in parallel, since it is the one thing that can be stopped. The backfill is the load generator. A backfill that reads replication lag as its throttle signal and pauses above a threshold would have made this a non-event, with batch sizes small enough that each batch's write volume is a fraction of what a replica can apply per second. That is most of the answer to what should have existed beforehand.
- Name the mechanism you would prefer over session pinning. Capture the write position at commit and require the read path to be at or past it: compare the primary's pg_current_wal_lsn() at commit time against the replica's pg_last_wal_replay_lsn(), and fall back to the primary only for the specific request that is ahead of the replica. Session pinning is the cheap approximation and it over-pins - every read in the window goes to the primary whether or not it needed to, which is a share of the cost being paid right now.
Worked solution 35 min
- List the endpoints in three buckets - staleness invisible, staleness visible to the writer only, staleness unsafe - and attach the share of the 2.1k reads/second each bucket carries.
- Compute the primary's operation count under each routing option and mark which options are arithmetically available.
- Write the pin rule and its window, then the shed rule: which slice, what status code, what retry-after.
- Write the backfill's throttle predicate against a measured lag value, including its pause threshold and resume condition.
Follow-up
- Lag returns to normal in nine minutes. Which mitigation do you remove first, and which one stays permanently?
- A user reports their change did not save, and the write committed. Trace the path that produces that report and name the signal that would have shown it before the report arrived.
- The replica is 40 seconds behind but otherwise healthy. Do you take it out of rotation? What does that do to the other replica's lag?
Edge instances grow 400 MB per hour until the nightly restart
Edge API instances start at 700 MB resident and grow about 400 MB/hour; a nightly rolling restart has hidden it for weeks. Growth continues unchanged when request rate halves overnight, p99 degrades in the last hours before an instance is recycled, and heap used immediately after a forced full GC rises monotonically. The service holds no product state. Name the discriminating measurement that separates the plausible causes, give the most likely cause, and give the fix and how you would verify it.
Approach
- Separate resident memory from live heap first, because they fail differently. Resident size can grow from fragmentation, native buffers or thread stacks while the heap is flat; heap used after a full GC rising monotonically is the measurement that says objects are reachable and not being released. You already have it, so this is retention, not fragmentation, and that closes off half the candidate list.
- Use the rate's independence from traffic as the discriminator. Growth that continues at half the request rate rules out per-request objects that are merely slow to collect and points at a structure that grows with distinct values observed rather than with call volume. Write the candidates that have that property: a metrics registry keyed on a high-cardinality label, an unevicted cache, an interner, a per-key lock map.
- Take two heap snapshots an hour apart and diff by retained size, reading the dominator tree, not by allocation count or instance count. Expect one root holding a map with millions of entries, then follow the reference chain to the code that inserts and never removes. Allocation profilers point at churn, which is the wrong signal here.
- The candidate that fits this service is an observability label carrying an identifier, such as a request path recorded before templating so that /v1/resources/48213 becomes its own metric series. That grows with distinct ids seen, is independent of rate, and explains the late p99 degradation, since GC cost rises with the size of the live set.
- Fix by bounding cardinality at the source: template the path to /v1/resources/{id} before it becomes a label, move tenant id from a label to a log field or an exemplar, and cap the registry with a bounded map that evicts. Add a cardinality ceiling that fails loudly in a lower environment rather than growing quietly in production.
- Verify with a soak rather than a restart. Hold one instance out of the nightly recycle for 48 hours with the fix and compare post-GC heap and series count against an unfixed control taking the same traffic.
Follow-up
- Post-GC heap is now flat but resident size still creeps. What are you looking at, and does it matter?
- How would you have detected this before an OOM, given the nightly restart masked the trend?
- That label is what makes one dashboard useful. How do you keep the dashboard and lose the leak?
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Parsers and recursion: the expression evaluator
- Build a tokenizer and a recursive evaluator for ( ADD a b ), ( MULT a b ) and nested forms such as ( MULT 3 ( ADD 3 4 ) ) in a plain editor with no autocomplete
- Extend it with ( LET X expr ... ) using a scoped environment, and test shadowing, undefined names and unbalanced parentheses
- Trace one nested input by hand, stating the call stack at each step and the overall complexity
Deliverable: A working evaluator with scoped LET and a written list of the edge cases it handles.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Search, heaps and graphs from the reported coding set
- Write lower-bound and upper-bound binary search from memory and return all logs between two timestamps; test empty, out-of-range and duplicate-timestamp cases
- Solve top-k frequent elements with a size-k min-heap, then adapt it to a stream where counts keep changing and say what the heap can no longer guarantee
- Model entities with parent links and write a common-ancestor check with BFS or DFS; state its complexity
- Solve a stack-based string problem such as removing adjacent duplicate runs of length k
Deliverable: Four solutions, each with a one-line complexity statement and the test inputs you traced.
Practice prompt ↗Practice prompt ↗03Practical web and async engineering
- For three broken async snippets, predict the output order before running anything, then name the mechanism and make the smallest fix
- Write a deep clone that handles nested arrays, objects and circular references with a WeakMap
- Write a function that maps a raw third-party JSON payload into an internal model, rejecting or defaulting missing fields and wrong types explicitly
- If your track is front-end, build a React list with filtered input and API pagination, including loading and error states
Deliverable: Working code for each task plus a one-sentence explanation of every async bug you fixed.
Practice prompt ↗Practice prompt ↗04Messaging design: scheduling and dispatching SMS
- Design the subscriber broadcast engine: requirements, APIs to schedule and cancel, storage of scheduled sends, a lease-based dispatcher and per-subscriber idempotency keys
- Walk a worker crash mid-batch through your design, say which messages are resent and which are skipped, and state the duplicate window that remains if the gateway cannot deduplicate
- Add a rate limiter toward the SMS gateway and explain what happens to a burst of sends due at the same moment
- Work the existing merge exercise on partitioned event streams to practise ordering and watermark reasoning for Kafka-style logs
Deliverable: One design sheet for the broadcast engine with its failure walkthrough, plus the completed stream-merge exercise.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Data-heavy design: ingestion, metrics and uploads
- Design the third-party integration engine: per-account rate limits, checkpointed cursors, backoff with jitter, TTL on cached subscriber records
- Design storage for a metrics monitoring platform: ingestion path, write-optimized storage, rollups and a low-latency query API
- Sketch the photo upload and CDN service at interface level: upload path, transformation, cache strategy and storage growth
- Work the existing replication-lag design exercise to practise choosing what to serve stale and what to refuse under load
Deliverable: Three one-page designs, each naming its main bottleneck and the trade-off you chose.
Practice prompt ↗Practice prompt ↗06Behavioral stories for the reported themes
- Write one story each for technical debt on a critical project, pushback on an architectural decision, your most technically challenging project, and performance versus features under a deadline
- For each, state what you personally decided, the trade-off, and a measured result; cut anything that describes the team rather than you
- Prepare a decision you later reversed, with what it cost to undo, and deliver all five aloud
Deliverable: Five behavioral stories, each rehearsed aloud with your own decision and a measured outcome stated.
Practice prompt ↗Practice prompt ↗07Back-to-back mock of the virtual loop
- Run consecutive mocks with no break: one algorithms problem from days 1-2, one practical problem from day 3, one design from days 4-5, and one behavioral story
- In each technical mock, state assumptions and edge cases before writing code or diagrams, and trace your code by hand at the end
- Review the SQL migration exercise for data-modelling fluency, and rehearse the recruiter-screen narrative and questions about your track
Deliverable: A list of the two weakest moments from the mock, each with the specific fix you will apply in the real loop.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
The reported behavioral questions center on technical debt, pushback on design decisions, your hardest project, and trading performance work against feature delivery. Whatever the prompt, show how you bounded the problem: the assumption you wrote down, who confirmed it, the narrow version you shipped first, and the measured result. Describe your own decisions, not the team's.
Describe a situation where you received pushback on an architectural d…
Describe a situation where you received pushback on an architectural decision and how you built consensus across the engineering team.
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
Tell me about a time you had to deliver a critical engineering project…
Tell me about a time you had to deliver a critical engineering project while dealing with significant existing technical debt.
Approach
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Reverse your own decision and price the reversal
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
- Finish on the process change: the smallest experiment that would have produced the same measurement in a day, and why you did not run it the first time.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?
- What do you now measure before committing to a change of this size?
- 01
Tell me about a time you had to deliver a critical engineering project while dealing with significant existing technical debt.
- 02
Describe a situation where you received pushback on an architectural decision and how you built consensus across the engineering team.
- 03
Walk me through the most technically challenging project on your resume, explaining your specific individual contribution and key design trade-offs.
- 04
How do you prioritize performance optimizations versus shipping new product features under tight business deadlines?
- 05
Describe how you handled vague feature requirements by clarifying scope, aligning stakeholders, and making trade-offs to ship effectively.
- 06
Describe a technical decision you made and later reversed: what you believed at the time, the measurement that changed your mind, and what the reversal cost.
Is this an official Attentive interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Attentive. Rounds and questions reflect what candidates have reported, not a process Attentive has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗What kind of coding questions should I expect?
Reported coding questions center on fundamentals rather than puzzles: a custom parser for nested expressions with variable binding, binary search over timestamped logs, a common-ancestor or kinship check between entities, and top-k frequencies using a heap. Practical questions include debugging async JavaScript, deep cloning nested objects with circular references, and normalizing third-party JSON. Drill stacks and recursion, binary search bounds, heaps, and BFS/DFS.
PracHub interview research ↗Should I prepare system design for a non-senior role?
Yes, prepare design at any level. For a mid-level role, be ready with clean API boundaries, database choice and basic caching. For a senior role, also prepare Kafka stream processing, partitioning and high-throughput scaling. Reported design questions include an SMS broadcast scheduler, a subscriber data integration engine, a metrics monitoring platform and a photo upload service, so practise at least one of them end to end.
PracHub interview research ↗What programming languages are permitted during the coding rounds?
Confirm the permitted languages with your recruiter before the technical screening. Practise in a language named in the role requirements (Python, Java, Go or TypeScript/JavaScript), and use JavaScript or TypeScript if you are on a front-end track. Work in a plain editor so the screen does not expose gaps an IDE was covering.
PracHub interview research ↗How long does the hiring process take?
Candidate reports differ, and timing depends on scheduling and team matching. Ask your recruiter for the expected timeline on the first call, and mention any competing deadline early so the loop can be scheduled around it.
PracHub interview research ↗Do I need to prepare React?
Only if your track calls for it. Candidates describe the virtual loop as including either practical application coding or front-end React work, depending on the track. A reported front-end question asks for a React component that renders an interactive list, filters input and handles API pagination. Ask your recruiter which applies to you on the first call.
PracHub Software Engineer practice ↗Do I need Kafka experience?
The role requirements list streaming technologies such as Apache Kafka as a nice-to-have rather than a must-have, and Kafka appears in the reported design topics. You do not need to have run it in production, but you should be able to explain why a log-based broker decouples event producers from downstream consumers, how partitioning affects ordering, and what consumer retries do to delivery guarantees.
PracHub Software Engineer practice ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24