cswork / libraryOpen practice workspace

Interview guides · Scribd

Scribd Software Engineer Interview Guide

Scribd Software Engineer interview guide: the 4 reported rounds, coding, system design and API questions, and a 7-day prep plan with worked practice.

Interviewing at Scribd

  • Practice bank: 6+ questions
  • Rounds: 1
  • Typical prep: 1–2 weeks
  • Interview reports: 1

Interview rounds

RoundQuestions
Onsite6

How hard is the Scribd Software Engineer interview?

DifficultyShareQuestions
Easy0%0
Medium100%6
Hard0%0

What Scribd actually tests for

TopicShareQuestions
Data Manipulation (SQL/Python)33%2
System Design33%2
Behavioral & Leadership17%1
Software Engineering Fundamentals17%1

The 60-second brief

Scribd runs a set of digital reading and subscription products: Scribd, Everand, SlideShare and Fable. Software Engineers build the services behind document viewing, content discovery and subscription access, and the pipelines that ingest and enrich uploaded documents. The backend stack described in candidate-facing notes is Python and Ruby on Rails on AWS (ECS, Lambda, SQS, ElastiCache), with Terraform for infrastructure and Datadog for monitoring.

Scribd candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

What a Software Engineer does at Scribd

The stack named in candidate-facing notes is Python and Ruby on Rails on AWS, using ECS, Lambda, SQS and ElastiCache, with Terraform for infrastructure and Datadog for monitoring. Reported questions draw on this stack: one design question asks for an event-driven notification system built on SQS, Lambda and ElastiCache, and one domain question asks how you would provision a microservice cluster on ECS with Terraform.

For preparation, treat this as a backend role with a strong API and data-pipeline emphasis. Reported coding questions are practical rather than puzzle-based: reimplementing map, filter and reduce, extracting metadata tags from a document payload, a sliding-window rate limiter, and traversing nested document outlines. Reported design questions cover document ingestion at scale, a Web API layer in front of legacy services, a multi-brand subscription activation flow across Scribd and Everand, and caching for high-traffic endpoints. Reported behavioral questions are framed around Scribd's GRIT framework (Goals, Results, Innovation, Team).

The Scribd interview process

Scribd candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Recruiter Phone Screen

Candidates describe this as an initial call to align on background, location preferences and role expectations. Two things are worth settling here. First, location: candidate-facing notes describe a flexible model, Scribd Flex, tied to approved hiring hubs, so confirm where you live fits before a loop is booked. Second, team: reported alignments include ML Data Engineering, Web API Platform and Signup & Activation, and those point to different preparation (pipelines versus APIs versus growth funnels). Ask which team the seat sits on so you know where to spend your design practice.

What it evaluates

  • Whether your background, location and timeline are compatible with the role before technical rounds are scheduled
  • Whether you can summarise your backend experience in terms that map to the role: services and APIs you built, pipelines you ran, languages you used in production
  • Whether you give a clear answer on compensation expectations and competing timelines instead of deferring them to offer stage

How to prepare

  • Write a short summary of your backend work that names the languages, datastores and queues you used, so the recruiter can place you against Python, Rails and AWS work
  • Ask which team the role is on and whether the later rounds lean toward API design, data pipelines or product growth work
  • Confirm your location against the hiring-hub model and state any hard constraints (start date, work authorisation, other processes) as facts on this call

Technical Phone Screen

Candidates describe a live coding session focused on data structures, algorithms and practical problem-solving, and source notes say the technical screen is with a hiring manager or senior engineer. In live coding, narrate your reasoning and edge cases as you go so the interviewer can follow your approach, not just the final code. Reported coding questions (not tied to a specific round) include reimplementing map, filter and reduce without built-in helpers, extracting valid metadata tags from an unstructured payload, a sliding-window rate limiter, a word-based sliding-window problem tracking character frequencies, and traversing nested document outlines or category trees; they are the most direct practice material for a practical live-coding session.

What it evaluates

  • Whether you clarify input shape, invalid cases and constraints before coding, especially on loosely stated prompts
  • Whether your code is modular and readable, with helper functions and clear names rather than one long loop
  • Whether you find the edge cases yourself: empty input, malformed data, boundary conditions, deep nesting
  • Whether you can state the time and space complexity and explain why your structure fits the access pattern

How to prepare

  • From the reported coding category, reimplement map, filter and reduce from scratch in your interview language, including an initial-value case for reduce and a callback that receives the index
  • Build a per-user sliding-window rate limiter with a deque of timestamps and decide, out loud, whether a request exactly at the window edge counts
  • Write recursive and iterative traversals that render a nested outline with indentation, then handle a tree too deep for the recursion limit
  • Practise narrating: state the brute force, the target complexity and the edge cases before the first line of code

Virtual Onsite Loop

Candidates describe multiple specialised rounds covering advanced coding, system design and behavioral interviews, and source notes describe meeting multiple engineers and engineering leaders across modules on data structures, algorithm design, system architecture, database design and behavioral alignment. The source does not assign its reported design, database or language questions to this or any other round, so prepare by module rather than by expected question: for system architecture, practise the reported design questions as a category; for database design, the reported pagination, indexing and query-optimisation questions; for coding, the reported coding category; and for behavioral alignment, GRIT-framed stories.

What it evaluates

  • Whether a design answer starts from clarified requirements and stated throughput and latency assumptions rather than boxes
  • Whether APIs you design are constrained: pagination, rate limits, payload size limits, input validation and a versioning story
  • Whether pipeline designs handle failure: at-least-once delivery, idempotent writes, dead-letter queues, replay and monitoring
  • Whether behavioral stories show ownership from decision through rollout and incident follow-up

How to prepare

  • Practise the reported design question on document ingestion and enrichment end to end: upload, queue, workers, retries, dead-letter queue, deduplication and the metrics you would alert on
  • Prepare a pagination answer that compares offset and keyset pagination, names the index each needs, and explains why offset degrades on deep pages
  • Rehearse diagnosing an N+1 query in Rails or Django and the fix (eager loading, a join, or a batched lookup), with how you would confirm the improvement
  • Prepare a Python or Ruby answer on memory growth and concurrency in long-running jobs, including how the GIL or GVL affects CPU-bound versus I/O-bound work

Collaborative Interviews

Candidates describe meeting future teammates and cross-functional partners to work through simulated real engineering problems. Identify early what each session is: working a problem together, discussing how you collaborate with product, design or data partners, or probing work you have described. Source notes describe cross-functional work with Product, Data Science and Design, so expect to explain technical trade-offs to people who are not engineers. The bank's collaboration questions (working with product, creating a sprint, resolving cross-team technical conflict) are useful practice for this.

What it evaluates

  • Whether you work with the interviewer on a shared problem, asking and incorporating their input rather than solving alone
  • Whether you can explain a technical trade-off, such as shipping a narrower version first, in terms a product partner can act on
  • Whether you handle a challenge to your design with evidence and a willingness to change course

How to prepare

  • Pick one feature you shipped with product and design partners and rehearse how scope was agreed, what was cut, and how you communicated the cut
  • Practise a paired problem with a friend acting as a teammate who pushes back on one of your choices, and respond with a test or measurement rather than restating your view
  • Prepare questions for the team about how they manage deployment risk, technical debt and on-call, which gives the conversation an operational focus

Questions Scribd candidates report

Data structures and algorithms

State the bound, then read your own code against it, because the cost you forgot is usually inside a call you did not write. A membership test against a list is linear while the same test against a set is expected constant, so one of those inside a loop turns a linear scan quadratic. Taking a slice copies the elements wherever slices are values rather than views. Repeated string concatenation is quadratic wherever strings are immutable, unless you accumulate into a builder or a list and join once. None of that is visible in the shape of the algorithm, and all of it is visible in the runtime.

  • Technical interviews test your ability to write clean, modular code, handle edge cases, and communicate your problem-solving process in real time.
  • Traversal and manipulation of hierarchical data structures, such as rendering nested document outlines or category trees.
  • Given an unstructured string or document payload, parse and extract valid metadata tags while maintaining optimal time complexity.
  • Implement a rate limiter or sliding window mechanism to throttle incoming API requests for a high-volume service.

System design and architecture

Turn the prompt into a workload before drawing anything: requests per second at peak, the read to write ratio, the size of one stored object, how long it is kept, and how each of those grows. Those numbers decide the architecture, and without them every component is a guess. Size the simplest thing that fits, then add a tier only where a number says the simple version breaks. What separates a strong answer is that every box has a quantity attached to it. A cache, a queue and a partition scheme that each answer a specific number read as judgement; the same diagram without numbers reads as recall.

  • Architecture sessions assess how you build reliable, distributed systems that scale safely under high throughput and real-world operational constraints.
  • How would you design an event-driven system using AWS SQS, Lambda, and ElastiCache to handle asynchronous content notifications?

Languages, concurrency and fundamentals

Before choosing threads, processes or async, classify the work by which resource is saturated. CPU-bound work needs real parallelism, so it needs cores, and a runtime that serialises bytecode execution will not provide any however many threads you start. I/O-bound work is waiting rather than computing, and concurrency alone fixes it: one thread or one event loop can hold thousands of open sockets. Weak answers reach for threads by default. Strong ones name the bottleneck first, then the model, then what the model costs, which is either shared memory and data races or isolated memory and serialisation on every hand-off.

  • Describe how you would write Infrastructure-as-Code using Terraform to provision a resilient microservice cluster on AWS ECS.
  • Domain-specific questions evaluate your hands-on mastery of language fundamentals, framework internals, and cloud infrastructure.

Behavioural and engineering judgement

Most engineering disagreements worth asking about are not settled by evidence, because both positions are defensible on what is known at the time: a schema shape, an abstraction one of you thinks is premature, fixing versus rewriting. The useful move is to say what would settle it, and what you do when nothing will. A common tiebreaker is which option is cheaper to undo, so you take the reversible one and set a date to look again. Weak answers argue who turned out to be right. Strong ones explain how the call got made while it was still undecidable.

  • Describe a situation where you had to balance building an ideal software architecture against tight product deadline constraints.

Practice exercises with worked solutions

The prompts above tell you what gets asked.

Enforce a concurrent-run quota that survives simultaneous requests

A plan allows at most 20 concurrently running rows in job_run per tenant. The table holds run_id, tenant_id, workspace_id, status (queued, leased, running, succeeded, failed, timed_out, cancelled, lost), lease_token, leased_until, started_at and finished_at. Today the service runs select count(*) from job_run where tenant_id = $1 and status = 'running', compares the result to 20, then inserts. Under load a tenant exceeds the cap by exactly the number of concurrent requests. Name the anomaly, say which isolation levels do and do not prevent it, and give a version that holds, as SQL.

How a strong candidate works it

  • Name it: write skew. Each transaction reads a predicate (the count of running rows), neither modifies what the other read, and both then insert rows that jointly violate an invariant no single row expresses. Read committed permits it. So does repeatable read, because snapshot isolation's first-updater-wins check fires only on conflicting row updates, and these are inserts touching disjoint rows.
  • Enumerate the fixes with their real costs. SERIALIZABLE works: PostgreSQL's SSI tracks the predicate read and aborts one transaction with SQLSTATE 40001, which obliges the caller to retry and makes the abort rate rise with contention on a hot tenant. Folding the predicate into the write as insert ... select ... where (select count(*) ...) < 20 narrows the race to the statement's snapshot but does not close it under read committed.
  • Give the version that holds at read committed: serialise on a row both transactions must touch. update tenant_concurrency set running = running + 1 where tenant_id = $1 and running < 20 returning running updates zero rows when the cap is reached, and zero rows is the rejection. This works because at read committed a blocked UPDATE re-evaluates its WHERE clause against the newly committed row; at repeatable read the same statement raises a serialisation error instead, so the isolation level changes the calling contract.
  • State the cost you just bought. That row is now a per-tenant serialisation point, so admission throughput for the tenant is bounded by one divided by the lock hold time; at a 2 ms hold that is roughly 500 admissions/second. Keep the critical section to the single UPDATE, with no network call or scheduling decision inside the transaction, and decrement in the same transaction that writes the terminal status.
  • Close the leak the status enum implies: a run can end as lost, so a crashed worker otherwise consumes a slot forever. Reconcile on a schedule against status = 'running' and leased_until < now(), and treat the counter as a fast path over job_run, which stays the system of record.

Expected result (25 min): Write skew identified; read committed and repeatable read both permit it; serialisable prevents it at the price of 40001 retries; the working read-committed version is update tenant_concurrency set running = running + 1 where tenant_id = $1 and running < 20 returning running, whose zero-row result is the rejection, plus a reconciliation path for runs that terminate as 'lost'.

Check yourself

  • At 8x concurrency against a cap of 20, running rows never exceed 20.
  • The rejection returns zero rows rather than raising, so it is distinguishable from an error in the caller.
  • Killing a worker does not permanently consume a slot.
  • The counter and select count(*) from job_run where status = 'running' agree after reconciliation.

Follow-ups

  • Write the retry loop for the SERIALIZABLE version. What does the caller see when it keeps aborting, and what bounds the retries?
  • Two regions each keep a counter. What is the effective cap, and what does admission do when the counter store is unreachable?
  • The cap changes mid-flight on a plan upgrade. Do running jobs get killed, and what does the counter row look like during the change?

Migrate a live partitioned event table without blocking ingest

usage_event is range-partitioned daily on ingested_at, holds roughly 250M rows per day across 400 live partitions, and is written at 10-40k rows/second. Two changes are required: quantity must move from double precision to numeric(20,6), and a new environment column must become NOT NULL with a default of 'production'. Ingest cannot stop. Give the ordered plan, naming for each step the lock it takes, what that lock blocks, and roughly how long it is held. Identify the one step that cannot be rolled back cleanly once traffic depends on it.

How a strong candidate works it

  • Classify the two changes before planning anything. Adding a column with a non-volatile default has been metadata-only since PostgreSQL 11, so it is cheap. Changing double precision to numeric is not binary-coercible, so alter column ... type rewrites every partition under ACCESS EXCLUSIVE and rebuilds its indexes; on this volume that is hours of blocked ingest and is simply not an option, which is why the plan is expand-and-contract rather than one statement.
  • Expand: add quantity_numeric numeric(20,6) and environment with its default on the parent. Both are catalogue-only but both take a brief ACCESS EXCLUSIVE that cascades to partitions, so run each with lock_timeout set to a second or two and retry on failure. A queued ACCESS EXCLUSIVE request blocks every reader behind it, which is how a metadata-only change turns into an outage.
  • Dual-write: deploy producer code that populates both columns on every insert, and leave it running before anything reads the new column. This is the step that cannot be reverted cleanly. Once readers depend on quantity_numeric, reverting the writer leaves rows with a null there, and the gap is only discoverable by re-reading the old column, which the readers have stopped doing.
  • Backfill older partitions in batches keyed on the primary key, oldest first, committing every few thousand rows with a pause between batches, and skipping the partition still receiving writes until it rotates. Each batch is an ordinary UPDATE taking row locks only. The cost is bloat and WAL rather than blocking, so watch dead tuples and let autovacuum keep pace instead of wrapping 400 partitions in one transaction.
  • Make NOT NULL cheap with the three-step form: add constraint ... check (environment is not null) not valid (brief ACCESS EXCLUSIVE, no scan), then validate constraint (SHARE UPDATE EXCLUSIVE, scans while reads and writes continue), then set not null, which from PostgreSQL 12 uses the validated check and skips its own full scan. Do this per partition, then on the parent.
  • Switch and contract: move reads to the new column behind a flag, verify over a full period that both columns agree on freshly written rows, drop the old column (metadata-only), and only then remove the dual-write. Any index on the new column goes on with CREATE INDEX CONCURRENTLY per partition, since CIC is not supported on a partitioned parent: create the parent index with ONLY, build each child concurrently, then ALTER INDEX ... ATTACH PARTITION until the parent index becomes valid.

Follow-ups

  • A CREATE INDEX CONCURRENTLY fails halfway through the partition list. What state is the table in, how do you detect it, and what do you run?
  • The producer computes quantity itself. What happens to a request already in flight when the dual-write deploy lands, and does it matter?
  • Give two queries that prove the backfill is complete: one cheap enough to run every minute, one authoritative.

Parse and verify a timestamped multi-signature webhook header

An inbound webhook carries a signature header of at most 1 KiB shaped t=<unix seconds>,v1=<64 hex chars>, with up to five v1 values during secret rotation and possibly unknown scheme keys. You hold the raw request body bytes and the currently active signing secrets. Write the parser and the verifier: accept when any active secret reproduces a signature and the timestamp is within a five-minute tolerance in either direction, reject otherwise. Single left-to-right pass over the header, no regular expression. State what is inside the MAC and why.

How a strong candidate works it

  • Parse in one scan: split on ,, then on the first = only, since a value may itself contain = under a future scheme. Accept t exactly once and treat a second t as a reject rather than last-wins. Push every v1 onto a short list and ignore any other key, so a v2 can be introduced later without breaking this verifier.
  • Say what is signed: HMAC-SHA256 over the exact byte string <t>.<raw body bytes>, yielding 32 bytes or 64 hex characters. The timestamp sits inside the MAC because otherwise an attacker replays yesterday's body with its still-valid signature and only has to edit the header timestamp.
  • Hash the bytes as received. Verifying against a re-serialised JSON body is the usual defect: key order, whitespace and number formatting all change the bytes while the parsed objects compare equal, so signatures fail for honest senders and the popular 'fix' is to stop checking.
  • Compare in constant time over fixed-length digests. Decode the hex to 32 bytes, accumulate acc |= a[i] ^ b[i] across the whole length, and test acc == 0 at the end. Evaluate every candidate without an early exit; at five candidates that is five HMACs over the body, linear in body size and negligible beside the network.
  • Apply the tolerance as a two-sided bound, rejecting when |now - t| > 300 seconds. A sender whose clock runs ahead of yours is an ordinary case, and an unbounded future timestamp is a free replay window.
  • Complexity: O(L) over the header producing k candidates, plus k HMACs at O(|body|) each. Space is O(k) beyond the body itself. Do the cheap rejections, including the tolerance check, before any cryptography runs.

Expected result (15 min): The valid case accepts. Both out-of-tolerance cases reject, including the future one. The unknown v2 field is ignored and the v1 still verifies. The re-serialised body fails, which is correct and is exactly why the raw bytes must be retained.

Check yourself

  • Timing the comparison over 10,000 mismatching signatures that differ at the first character versus the last shows no separation you can measure.
  • Removing t from the MAC input makes an old captured body with its original signature pass. Write that test, watch it pass, then put t back.
  • Every rejection except the signature mismatch itself returns before an HMAC is computed.

Follow-ups

  • The body is 40 MB. What changes about where you verify, and what can you do before the whole body has arrived?
  • A customer reports that signatures fail for exactly the requests whose body contains a non-ASCII character. What is your first hypothesis?
  • How do you rotate the signing secret with no failed deliveries, and how long do both secrets stay live?

An idempotent create endpoint that returns a one-time secret

POST /v1/api-keys inserts a tenant_api_key row (tenant_id, workspace_id, name, key_prefix, secret_hash, scopes, status, auth_version, created_at, expires_at) and returns the plaintext secret exactly once, since only its SHA-256 is stored. Write volume is tens per second and clients retry on timeout. Design the idempotency mechanism: what the key is scoped by, where the record lives, its retention, what happens when the same key arrives with a different body, what happens when a retry arrives while the first request is still in flight, and what a replay returns for the secret.

How a strong candidate works it

  • Scope the key by tenant, not globally: uniqueness is on (tenant_id, idempotency_key), or one tenant's key collides with another's and the second caller receives a stored response for a request it never sent. Store a fingerprint of the request alongside it - method, path and a hash of the canonicalised body - so a mismatch is detectable.
  • Let the unique constraint decide the race instead of application logic. In the same transaction as the credential insert, run INSERT INTO idempotency_record (tenant_id, key, request_fingerprint, status) VALUES (...) ON CONFLICT DO NOTHING RETURNING id; no returned row means this is a replay, and the existing record is then read. A select-then-insert here loses to itself under concurrency in exactly the way this endpoint is meant to prevent.
  • Write the three replay outcomes as a decision table rather than as prose: same fingerprint and completed returns the stored response; same fingerprint and still in flight returns 409 with Retry-After, without blocking and without executing; different fingerprint returns 422, because replaying a stored response for a mutated body would tell the caller a credential was created for parameters it never sent.
  • Handle the secret as the part that makes this endpoint different from an ordinary idempotent create. The plaintext cannot be regenerated from secret_hash, so either the stored response holds it - making the idempotency record a secret at rest whose retention is now the secret's exposure window - or a replay returns the key metadata without the secret and the documentation says a lost response is resolved by listing keys and revoking the orphan. The second is the safer default precisely because credentials are listable and revocable.
  • Set retention from the client's retry budget, not from a round number: the record must outlive the SDK's maximum total retry duration, so twenty-four hours is defensible if that budget is minutes. After expiry the key is reusable and a very late retry creates a second credential, which is acceptable here only because the object is listable and revocable, and would not be for an unlistable side effect. Expire with a scheduled delete on an index over created_at.

Expected result (20 min): Unique (tenant_id, idempotency_key); the record inserted in the same transaction as the credential with ON CONFLICT DO NOTHING deciding the winner; 200 with the stored response, 409 for in-flight, 422 for a fingerprint mismatch; a documented rule that the plaintext secret is not replayed and a lost response is resolved by listing and revoking; retention exceeding the SDK retry budget, for example twenty-four hours.

Check yourself

  • Two concurrent requests with the same key must leave exactly one row in tenant_api_key. If the logic is select-then-insert, they will not.
  • A retry with a mutated body must not return 200. If it does, the fingerprint is not being compared or is not covering the fields that matter.
  • The retention you chose must be strictly longer than the client library's maximum total retry duration. Look that number up rather than assuming it.

Follow-ups

  • Two requests with the same key arrive concurrently. Show the exact statements and say which one loses, and how it finds out.
  • The client receives a timeout, retries, and gets 409 in-flight. What should the client library do next, and for how long?
  • How does the design change if the created object is not listable - a one-off payout, say - so an orphan cannot be found afterwards?

Webhook workers leak until OOM and drop in-flight deliveries

webhook-delivery workers grow from 400 MB to a 2 GB limit over about 36 hours, are OOM-killed, restart, and repeat. Each restart abandons in-flight attempts, so webhook_delivery rows sit in in_flight until their leases expire and the backlog spikes. The live set measured after a forced full collection also grows. The fleet serves tens of thousands of subscriptions, several thousand of which have been failing for weeks. Give an ordered checklist, the measurement separating retention from fragmentation, and the fix.

How a strong candidate works it

  • Separate the two failure shapes with one measurement: track resident set size against the live set after a forced full collection. A live set that climbs monotonically is retention; a flat live set under a rising RSS is fragmentation, off-heap or native allocation, or an allocator that never returns pages. The stated symptom puts this in the first category, which rules out allocator tuning as a fix.
  • Characterise the curve rather than the total. Growth linear in uptime implies an unbounded structure keyed by something that keeps arriving; step growth implies buffering a large object. Correlate the slope against event rate and separately against the count of distinct subscriptions seen, because those two diverge and only one of them will fit.
  • Diff two heap snapshots an hour apart by retained size grouped by dominant root, not by allocation count, which is dominated by short-lived objects and will point at the wrong thing.
  • Expect a per-subscription map with no eviction: circuit-breaker or backoff state created on first failure and never removed, so the retained set grows with endpoints that have ever failed, and the several thousand permanently dead endpoints hold theirs forever.
  • Fix in two places. Bound the in-memory structure with a size-capped LRU or a TTL keyed on last use, and move state that must survive a restart onto the subscription or webhook_delivery row, since the worker holding it in memory is exactly why a restart loses it.
  • Repair the second-order damage separately, because it will outlive the leak: workers claim by compare-and-set with leased_until, so a bounded lease returns in_flight rows to pending on a known schedule, and a graceful shutdown releases leases instead of waiting them out.

Follow-ups

  • The backlog spike after a restart is itself a thundering herd against customer endpoints. What stops the recovery from becoming a second incident?
  • Suppose the live set had been flat while RSS still climbed. Name two causes and the measurement that separates them.
  • How would you size the LRU, and what does a miss on an evicted circuit-breaker entry cost a customer whose endpoint is down?

Disclose a cross-tenant webhook delivery to affected customers

An enqueue path took the subscription from one lookup and the payload from another. For nineteen minutes, webhook_delivery rows were created whose tenant_id did not match the subscription's tenant, and eleven payloads were signed and sent to four endpoints belonging to other customers. You hold payload_digest, delivery timestamps and response codes. Describe how you handle a disclosure of this kind: what the records prove, what they cannot prove, what you say before you know everything, the one code change that closes it, and which parts you personally drove.

How a strong candidate works it

  • Bound the population before saying anything externally. The affected set is deliveries in the window where the event's tenant and the subscription's tenant differ; the ones that actually left are those with delivered_at set and a 2xx in last_response_code. Attempted and delivered are two different counts and a disclosure has to use the right one in the right sentence.
  • Separate what the records prove from what they do not, and say both halves rather than the flattering one. They prove which payloads were signed, where they went, and — through payload_digest — exactly which bytes. They do not prove what the receiving system did with them, and they do not bound the window more precisely than your deploy timestamps do.
  • Communicate on the facts you hold, with the scope stated as an upper bound: 'at most eleven payloads, four recipient endpoints, these fields, this window' is more useful and more honest than waiting a day for certainty. The field list matters more than the event count, because a customer cannot assess exposure from 'an event'.
  • Name the code change precisely, because this class never originates in the delivery worker. Compare the event's tenant against the subscription's tenant at enqueue and again immediately before the payload is signed, and make the second comparison drop the delivery rather than log a warning. Say why one check is insufficient: the enqueue check protects against the bug you know about, the pre-signing check protects the boundary itself.
  • Run the history question in parallel and say so: a query over historical deliveries for the same mismatch tells you whether this was nineteen minutes or a year, and you would rather find the second case yourself than have a customer find it after your disclosure.
  • Split the response into workstreams with owners — recipients asked to delete, affected customers notified, the check landed with a test, history swept — and say which you personally drove and which you handed off. Claiming all four is not credible and claiming none is not ownership.

Follow-ups

  • The historical sweep finds two more instances from last year. What changes in what you have already told people?
  • Who approves the wording, and what do you do when you are asked to soften the scope?
  • A customer asks you to prove a redelivery contained the same bytes as the original. What do you show them?

Ship metered billing with a named deduplication horizon

Metered billing must be on in three weeks. usage_event is partitioned daily, so its unique index must include the partition key and deduplicates only within a day: a producer retry that crosses midnight, or a replay run a week later, gets through. A cross-partition dedup store is two weeks you do not have. Describe shipping with debt you named in advance: what you shipped, what you wrote down, the detector you added, the trigger and date for paying it off, and what you would have refused to ship under the same pressure.

How a strong candidate works it

  • Show you can separate the two kinds of debt, because that distinction is what the question actually probes. Debt that costs engineering time later is shippable on a deadline. Debt that silently corrupts a number a customer gets charged for is not shippable unless the corruption is detectable, and detectability is the whole negotiation.
  • Make the exposure narrow and measured rather than gestural. The hole is duplicates whose occurrences straddle a UTC day boundary, plus any replay older than partition retention. Measure it before arguing about it: how often an idempotency_key recurs at all, and the distribution of the gap between first and last occurrence. If the ninety-ninth percentile of that gap is four minutes, the residual risk is a small band around midnight and you can say so numerically.
  • Add the detector before the feature, not after. A nightly job counting keys that appear in more than one partition is one grouped scan over recent partitions, and it converts a silent overcount into a page. State what it costs to run and what it fires on.
  • Buy the cheap half of the real fix immediately: extend partition retention so the dedup horizon exceeds the producer's maximum retry window plus the longest replay you intend to support. That reframes retention as a correctness parameter rather than a storage cost, which is the sentence you need on record before someone optimises the bill.
  • Make repayment mechanical instead of aspirational: a dated entry with a named owner, plus a threshold that pulls the date forward — first detector hit above N events, or first customer dispute. Debt with a trigger gets paid; debt with only a date does not.
  • Answer the second half honestly by naming what you would refuse under identical pressure: the sealing path, because a sealed row is frozen and a wrong number there stops being a bug and becomes an adjustment line, a dispute and an audit question.

Follow-ups

  • The detector fires on forty duplicate events for one tenant, and two of their invoices have already sealed. What happens next?
  • Whom did you tell that the billing numbers had a known hole, and in what words?
  • Finance asks you to cut storage by shortening partition retention. What do you say, and to whom?

Where candidates lose points

Reaching for built-in helpers when the reported utility question asks you to reimplement map, filter or reduce

The bank's versions of this question say to implement the logic without the language's collection helpers, so calling the built-in misses the point of the exercise. Write the loop yourself, decide how reduce behaves with and without an initial value and on an empty input, and say whether your versions are eager or lazy. Test with a callback that uses the index and with one that raises.

A sliding-window rate limiter that is wrong at the window boundary or grows without limit

State whether a request exactly one window old is inside or outside the window, and test that case. A per-user deque of timestamps gives amortised O(1) checks, but idle users keep their entries forever unless you evict them; say how you would bound memory, and how the design changes when several servers must share the limit (for example a shared store such as Redis).

Designing an API or platform layer with no pagination, rate limits, payload limits or versioning

Source notes explicitly warn against unconstrained endpoints in design rounds. For the reported Web API layer over legacy services, name the pagination scheme (keyset over offset for large tables), the rate limit, payload size limits, input validation, and how you version the API without breaking existing web and mobile clients. Add a cache only after saying what invalidates it.

Treating a queue-based pipeline as if every message arrives exactly once

For the reported ingestion and SQS/Lambda notification designs, assume at-least-once delivery. Make writes idempotent with a key per document or event, set a retry limit with a dead-letter queue, decide how failed items are replayed, and name the metrics you would watch (queue depth, age of oldest message, error rate) so a slowdown is visible before it becomes a backlog.

GRIT stories told in 'we' with no decision or result that belongs to you

Reported prompts ask about a mid-project scope change, balancing ideal architecture against a deadline, being challenged on a design, and owning an incident through post-mortem. For each, say what you decided, what evidence you used, and the measured outcome. If a teammate made the key call, say so and describe your part precisely.

Algorithms first, for a candidate whose problem-solving has gone rusty

For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.

Day 1 — Recruiter call and product groundwork

  • Create an account and use Scribd, Everand and SlideShare as a reader: open a document, search, save something, and note where subscription access changes what you can see
  • For each product flow you tried, sketch the backend it implies: a document viewer API, a search or recommendation call, an entitlement check
  • Write your recruiter-call summary: languages, datastores, queues and cloud services you have used in production, plus your location and timeline constraints
  • List the questions you will ask about team alignment (ML Data Engineering, Web API Platform, Signup & Activation) and which rounds lean toward APIs or pipelines

Deliverable: A one-page note with three product flows and their implied APIs, plus your recruiter-call summary and questions.

Day 2 — Coding: collection utilities and tree traversal

  • Reimplement map, filter and reduce without built-in helpers, handling an empty input and reduce with and without an initial value
  • Solve the bank's top K most frequently read books with a hash map plus a heap, and state when a full sort is simpler
  • Render a nested document outline with indentation both recursively and with an explicit stack, then test on a deeply nested input
  • Reverse a linked list iteratively and recursively and explain the space difference

Deliverable: Four solutions, each with its edge cases listed above the code and its time and space complexity stated.

Day 3 — Coding: parsing and sliding windows

  • Extract valid metadata tags from an unstructured string in one pass, defining what makes a tag valid before you code
  • Build a per-user sliding-window rate limiter and test the request that lands exactly on the window boundary
  • Solve a sliding-window problem over words or characters that tracks frequencies, and write the window invariant above the loop
  • Work the worked exercise 'Parse and verify a timestamped multi-signature webhook header' as practice for single-pass parsing with strict rejection rules

Deliverable: Three solutions narrated out loud from clarifying questions to complexity, plus the completed webhook-header exercise.

Day 4 — Database fundamentals: pagination, indexing, query optimisation

  • Compare offset and keyset pagination over a large table, write both queries, and name the index each one needs
  • Take a Rails or Django view with an N+1 query, fix it with eager loading or a join, and say how you would confirm the improvement
  • Design a many-to-many schema with a join table and composite primary key, then write the queries it has to serve
  • Work the worked exercise 'Enforce a concurrent-run quota that survives simultaneous requests' to practise isolation levels and race conditions

Deliverable: A written pagination comparison, one before-and-after query fix, a schema with its queries, and the completed quota exercise.

Day 5 — System design: ingestion and event-driven pipelines

  • Design a high-throughput document ingestion and metadata enrichment pipeline: upload, queue, workers, retries, dead-letter queue, deduplication and replay
  • Design the reported asynchronous content-notification system on SQS, Lambda and ElastiCache, stating what happens when a consumer fails mid-batch
  • Add observability to both designs: the metrics, logs and alerts you would set up and what each would tell you
  • Work the worked exercise 'An idempotent create endpoint that returns a one-time secret' to practise idempotency keys under retries

Deliverable: Two design diagrams with written failure handling and an alert list, plus the completed idempotency exercise.

Day 6 — System design: API layers, subscriptions and caching

  • Design a Web API platform layer between front-end clients and legacy microservices, covering contracts, pagination, rate limits and versioning
  • Design a subscription entitlements API that supports access across Scribd and Everand, including how an upgrade or cancellation propagates
  • Write a caching strategy for a high-traffic endpoint: what is cached, the key, the TTL, what invalidates it and what happens on a cache miss storm
  • Prepare a short comparison of REST, GraphQL and gRPC for web and mobile clients, and a Python or Ruby answer on memory and concurrency in long-running jobs

Deliverable: Two API designs with explicit constraints, a caching plan, and a one-page notes sheet for the language and API-style questions.

Day 7 — Behavioral and collaborative rounds

  • Write GRIT-mapped stories for a mid-project scope change, an architecture-versus-deadline trade-off, a design challenge from a teammate, and an incident you owned through post-mortem
  • For each story, mark the decision you personally made, the evidence you used and the measured result, and remove sentences that only say 'we'
  • Rehearse a collaboration story with product partners (scope agreed, what was cut, how it was communicated) and a cross-team technical conflict
  • Run a paired mock where a friend plays a teammate who challenges one of your design choices, and respond with evidence rather than repetition

Deliverable: Five stories rehearsed at a short and a long version, plus notes from one paired mock session.