The 60-second brief
Reddit is a platform of communities where users discover, discuss and share content. For a Software Engineer, the source notes place the engineering work in the core content feed, real-time chat, search, ad-bidding platforms and machine learning pipelines. They name team areas such as Core App, Ads Engineering, Infrastructure and Community Tools. Depending on the team, the work is backend microservices, data pipelines, or web and mobile clients.
Reddit candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
What a Software Engineer does at Reddit
The reported questions reflect that spread. The coding questions include classical algorithm problems: finding nodes with zero or one parent in a parent-child graph, the lowest common ancestor of two nodes in a binary tree, BFS or DFS over arrays, and processing a transaction stream with hashing. They also include practical domain problems: a tennis scoring tracker with deuce and advantage, parsing an administrator action log and checking whether one admin can remove another, merging fragmented chat messages, and an in-memory cache with eviction. The reported design questions cover a newsfeed that distributes posts to subscriber feeds, an ad-serving API with real-time click and impression aggregation, a caching layer for a read-heavy application, and an ML feature store or real-time signal pipeline.
The role notes list Python, Go, Java, C++, TypeScript and Swift or Kotlin as languages, and PostgreSQL, Redis and Cassandra as data stores. They list AdTech, auction systems and streaming systems such as Kafka as nice-to-haves. Use that list to choose your design vocabulary, but only claim tools you have actually run in production.
The practical coding category is where a purely puzzle-based preparation falls short. Practise modelling state and parsing input cleanly. Build a working baseline before handling every edge case, and say your assumptions out loud whenever a prompt leaves the input format open.
The Reddit interview process
Reddit candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Recruiter Conversation
Candidate reports describe this as a conversation with a recruiter about your background, your career direction and how you fit the open positions. Use it to find out which seat you are interviewing for. The role notes name team areas such as Core App, Ads Engineering, Infrastructure and Community Tools, and the work ranges from backend services and data pipelines to web and mobile clients. Also ask which format the technical screen takes, because reports describe both a live coding session and a hiring manager phone screen. Say which parts of the posting you have done and which you have not. If you claim all of it, the gaps come out in a later round.
What it evaluates
- Whether your background maps clearly onto one team area and one kind of work rather than onto the whole posting
- Whether you can explain your career direction, and why this role fits it, in a few concrete sentences
- Whether you are open about the parts of the posting you have not done
How to prepare
- Mark each line of the posting as done, adjacent or new, and for each adjacent line write one sentence naming the closest thing you built
- Ask which team the role is on, whether the technical screen is live coding or a hiring manager screen, and how the onsite sessions are split
- Prepare a short answer to why you want to join Reddit that names a community you actually use and one concrete thing you noticed about it
Technical Screening
Reports describe this stage as either live coding in a collaborative editor such as CodeSignal or CoderPad, or a hiring manager phone screen. Either way it focuses on core algorithms, coding fluency and problem-solving speed. Candidate notes say screens often use classical problems on arrays, trees and graph search. The reported coding questions include finding nodes with zero or one parent in a parent-child graph, the lowest common ancestor in a binary tree, BFS or DFS over arrays, and a transaction stream solved with hashing. Write a correct brute force first and state its cost. Then improve it while the working version is still on screen. Before you say you are finished, trace an empty input, a single element and duplicate keys.
What it evaluates
- Whether degenerate inputs get checked without prompting: an empty collection, one element, all elements equal, and a node that appears only on one side of a relationship
- Whether the complexity you state matches the code you actually wrote, including a sort or a copy inside a loop
- Whether you check the finished answer against the worked examples before calling it done
How to prepare
- Drill the reported coding categories: a parent-count map built from edge pairs, lowest common ancestor in a general binary tree and in a BST, BFS or DFS on a grid, and a single hash-map pass over a stream
- Practise the brute force as a separate skill: on ten problems, write only the obviously correct slow version and time how long it takes to pass
- Take five problems you have solved and, without running anything, write what each returns for empty input, one element and all duplicates; then run them and count how many you got wrong
- If you get a hiring manager screen, prepare a walkthrough of one system you built: its scale, the part you owned, and one trade-off you made
Virtual Onsite Loop
Reports describe a virtual onsite that can include multiple sessions and can be split across two days. It covers practical domain coding, system design, product and cross-functional collaboration, and behavioural conversations. Candidate notes say onsite coding leans on practical object-oriented modelling, string processing and domain design more than on puzzle-style algorithms. The reported questions in those categories include a tennis scoring tracker, an admin log parser with a seniority-based permission check, and chat message merging with caching. The reported design questions include a newsfeed, an ad-serving API and a caching layer. For the practical problems, build a working baseline and then extend it. Aim to leave each session with one concrete thing the interviewer can cite, such as a bug you caught yourself or a trade-off you named. Keep your project facts the same in every session.
What it evaluates
- Whether practical coding problems get clear state and classes, and whether you extend them in steps without breaking what already worked
- Whether a design states requirements, a data model and the scaling bottleneck before choosing components
- Whether the scale, team size and decisions you attach to a project stay the same when it comes up in a different session
- Whether you describe work with product managers and data scientists in terms of a specific decision and what it cost
How to prepare
- Build the tennis tracker and the admin log parser in stages: get one game or one record type tested first, then extend it
- Sketch the newsfeed with push, pull or hybrid fanout and the ad aggregation path with idempotent counting, then work the at-most-once push design exercise
- Write a one-page sheet per project with the figures you will quote, and repeat them aloud until they come out the same every time
- Prepare one cross-functional story: a decision you negotiated with a product manager or data scientist, what you gave up, and the result
Questions Reddit candidates report
These are the prompts PracHub has collected for this role and company. They are a record of what candidates describe being asked, so treat them as the shape of the conversation rather than a script Reddit works from.
Data structures and algorithms
Ask which resource the problem actually takes away, because it is not always time. If the input does not fit in memory, the set you were going to build is gone and the bound that matters is space. If the data arrives once and cannot be revisited, anything needing a second pass is out whatever its complexity, and a constraint banning modification of the input removes the in-place trick that would otherwise be free. Naming the scarce resource first cuts the candidates down to a handful, and it stops you optimising a dimension the problem was never short of.
- Implement a solution to find nodes with 0 parents and 1 parent in a parent-child graph relationship.
- Process a transaction stream using optimal hashing and array lookup techniques.
- Solve array-based data manipulation problems utilizing depth-first search (DFS) or breadth-first search (BFS).
- Given a binary tree structure, find the lowest common ancestor of two specified nodes.
System design and architecture
A design is a set of promises about what happens when something is slow, down, or delivered twice, and that is the part usually left unsaid. For each dependency, say what the system does when it is unavailable: serve stale data, reject the write, or accept it and settle later. For each relaxation of consistency, describe the anomaly a user would actually notice, in the product's own words, rather than naming a consistency model and moving on. The separator is volunteering the failure behaviour as part of the design instead of producing it only when pushed.
- Architect an ad-serving platform API capable of handling high concurrent bid requests and real-time click/impression aggregation.
- Design a high-throughput, low-latency newsfeed system capable of distributing posts across millions of active subscriber feeds.
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 work with a difficult colleague. How did you resolve the situation and maintain team momentum?
- Tell me about a time you received harsh or critical technical feedback. How did you process it and what changes did you make?
Other topics
A large share of these are really questions about evidence: why something is slow, or how much of something you would need. Convert the prompt into a number or an observation. For diagnosis, name the measurement that would tell two candidate explanations apart and say what each predicts before you collect it. For sizing, carry the units through and stop at one significant figure. Listing everything that could plausibly be true is what the weak version looks like. What separates the strong one is a hypothesis, the observation that would falsify it, and what the arithmetic already rules out before any new data arrives.
- Admin Log Processing: Parse a long string of administrator action logs to generate an ordered list of administrators by last addition time, and implement a permission check method determining if one administrator can remove another based on seniority.
Practice exercises with worked solutions
The prompts above tell you what gets asked. These are PracHub exercises written to drill the same skills, with the reasoning spelled out so you can check your own answer instead of guessing whether it was good enough.
Take a viral item's like counter off one hot row
One item is taking 8,000 likes a second and the counter is a single row: UPDATE content_counter SET like_count = like_count + 1 WHERE content_id = $1. Describe precisely what that statement does under READ COMMITTED and under REPEATABLE READ, including whether a count is ever lost. Then give the sharded schema, the read query, the rule for choosing a shard, and the reconciliation job that rebuilds the number from engagement. State what is approximate, by how much, and for how long.
How a strong candidate works it
- Under READ COMMITTED that statement does not lose an update. A blocked writer waits on the row lock, then re-reads the newly committed version and re-evaluates like_count + 1 against it. The failure is throughput: every writer serialises behind one row lock, so the ceiling is roughly one increment per lock hold, and the hold spans commit and anything else still inside the transaction.
- Under REPEATABLE READ the same statement raises could not serialize access due to concurrent update (SQLSTATE 40001) rather than waiting the conflict out, so the hottest item produces the retry storm. The anomaly moved; it did not disappear, and prescribing stricter isolation here makes the incident worse.
- Distinguish the genuine lost update: SELECT the value, add one in application code, UPDATE with a literal. Under READ COMMITTED both sessions read 500 and both write 501, and one increment is gone with no error anywhere. Being able to tell this interleaving apart from the first one is the substance of the question.
- Shard: content_counter_shard(content_id BIGINT, shard_no SMALLINT, delta BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (content_id, shard_no)). Pick the shard by hashing actor_id modulo N so a redelivered apply lands on the same row and any dedup stays local. Read is SELECT sum(delta) FROM content_counter_shard WHERE content_id = $1, one index range scan of N rows - the trade is read cost for write concurrency, so shard only above a popularity threshold and leave cold items on one row, which means the reader must know which mode an item is in, the same shape as the push/pull threshold.
- Reconcile from the ledger: SELECT content_id, count(*) FROM engagement WHERE action_kind = 'like' AND undone_at_utc IS NULL AND content_id BETWEEN $lo AND $hi GROUP BY content_id, batched by id range and compared against the shard sum. This only works because undo is recorded in place instead of deleted, and it is why the denormalised count is never the input to an authorization test.
- State the contract out loud: the number is approximate between runs, the drift is bounded by the deltas not yet reconciled, the displayed value can be stale by one reconciliation period, and nothing that must be exact - payouts, quotas, rate limits - reads it.
Expected result (35 min): The single-row UPDATE ends at exactly the number of committed increments under both isolation levels - nothing lost - with throughput bounded by the row lock and a burst of 40001 retries under REPEATABLE READ. The SELECT-then-UPDATE variant ends short of the transaction count, which is the lost update. Sixteen shards raise throughput until some other bottleneck dominates, and the shard sum equals the reconciliation count.
Check yourself
- final like_count equals the committed transaction count for the single-row UPDATE; anything less means the harness lost commits, not the database.
- Under REPEATABLE READ, errors plus successes equal attempts.
- SUM(delta) across shards equals count(*) of live engagement rows for that item.
- Throughput on the sharded version stops improving at some N - record it, because that is where the read-side scan costs more than the write contention it saved.
Follow-ups
- Two follows commit concurrently while follower_count is 9,999 and the push/pull threshold is 10,000. Each transaction reads 9,999 and neither flips fanout_mode. Name the anomaly, say which isolation level prevents it, and say what the database does when it detects it.
- How do you choose N, and what measurement tells you it is too high?
- Would you move the increment to an in-memory counter instead? State the loss window you are accepting and what makes it acceptable.
Reconstruct an item's like curve from an undo-in-place table
engagement records one row per (actor_id, content_id, action_kind) with created_at_utc and undone_at_utc, where an unlike sets undone_at_utc in place rather than inserting a second row. For one content_id, return the net live like count over time and the earliest instant it reached 500. Write the query. The obvious one-pass window over created_at_utc produces a curve that never dips - explain why that is wrong, state which window frame you rely on and why, and name the index each leg of your query needs.
How a strong candidate works it
- Say what the table is: current state, not an event log. SUM(CASE WHEN undone_at_utc IS NULL THEN 1 ELSE 0 END) OVER (ORDER BY created_at_utc) counts, at each past instant, the likes placed by then that are still live today, so every undo is applied retroactively to a moment before it happened and an item that reached 800 and fell to 300 never appears to have crossed 500.
- Unpivot into events first: one +1 at created_at_utc for every like row, one -1 at undone_at_utc for the rows where it is not null, combined with UNION ALL. Order the window over that unioned instant rather than over created_at_utc, because an undo can fall between two later likes.
- Run the running total with an explicit frame and choose RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW deliberately: the question asks for the count at an instant, so every event sharing a timestamp must resolve to the same value. ROWS would expose intermediate values inside one instant and can report a crossing that existed only between two simultaneous events. ROWS is right for a per-row ledger, RANGE for a level at a time.
- Take the crossing as MIN(at) FILTER (WHERE net_likes >= 500) over the running result rather than with LAG, because the question is about a level and not a change, and LAG also misreads ties.
- Index each leg separately: (content_id, action_kind, created_at_utc) for the like leg, and a partial (content_id, action_kind, undone_at_utc) WHERE undone_at_utc IS NOT NULL for the undo leg, since undos are a minority and the partial index keeps that leg off the full like history.
- State the modelling limit precisely: because the row is updated in place, a like then unlike then re-like leaves one row with undone_at_utc NULL, so that actor's dip is unrecoverable. The curve is exact for actions that are currently undone and approximate otherwise; an exact curve needs an append-only engagement_event ledger, which is the real deliverable this question is circling.
Follow-ups
- Give the DDL for that append-only ledger and say what it costs at this write rate, and what you would stop storing to pay for it.
- Now return the curve bucketed per minute for a chart. What changes about the frame and about the index?
- Answer the same question for 10,000 items at once without running the query 10,000 times.
Suppress duplicate notifications under bounded memory and redelivery
The notification path receives envelopes as (recipient_id, dedup_key, ts_ms) at 300,000 per second, and at-least-once redelivery means the same (recipient_id, dedup_key) can reappear at any point within 24 hours. Deliver each pair at most once per 24-hour window using memory that does not grow with the stream. Exact deduplication over 24 hours at this rate spans roughly 26 billion events, which does not fit in memory. Give the design, state the memory it consumes, and state precisely which direction it can be wrong in and how often.
How a strong candidate works it
- Separate events from distinct pairs before sizing anything. The gate only has to remember distinct pairs, not deliveries, so measure the redelivery ratio first: at 26 billion events and a 12x ratio the distinct set is about 2 billion, which at 16 to 24 bytes an entry is still 32 to 48 GB. That does not by itself solve the problem, but it changes the constant by an order of magnitude and it is the first number to establish.
- Make expiry O(1) per bucket rather than per entry by time-bucketing the state: 24 rotating hourly sets, a lookup probes all 24, an insert goes to the current one, and expiry frees the oldest set wholesale. Per-entry TTLs cost a delete per entry and fragment the allocator. Two prices come with that bucket count and both are quantitative: the effective window runs from 24 to 25 hours depending on where in a bucket the first delivery landed, and a lookup gets 24 independent chances to return a false positive rather than one. State the slop instead of claiming exactly 24 hours, and carry the 24 into the sizing rather than the footnotes.
- Choose the error direction deliberately, which is the real content of the question. A Bloom filter has no false negatives and false positives at rate p, so a false positive suppresses a real notification: a silent, user-visible loss with no recovery path. If the product prefers a rare duplicate to a rare silent drop, demote the filter to a negative cache where a miss is definitive and a hit is confirmed against a durable store, so a false positive costs one lookup instead of a notification.
- Size from the rate a caller sees, not the per-filter rate, or the headline is wrong by the bucket count. The 24 filters hold disjoint sets, so their false positives are independent and the delivered rate is P = 1 - (1 - p)^24, about 24p. Invert it: p = 1 - (1 - P)^(1/24), about P/24. For a target P = 10^-4 that is p = 4.2 * 10^-6, and the optimal geometry is bits per element = 1.44 * log2(1/p) = 25.8 with hashes = 0.693 times that, about 18 per filter, so 2 billion distinct pairs is roughly 6.4 GB across all buckets against 32 GB or more for exact. Sizing each bucket at p = 10^-4 instead looks cheaper at 19.2 bits and 4.8 GB, but its delivered rate is 2.4 * 10^-3.
- Treat the bucket count as the tuning knob it is, with no free corner: B buckets give window slop up to 24/B hours, bits per element proportional to log2(B/P), and B filters probed per lookup. B = 4 costs 22.1 bits and 5.5 GB but up to 6 hours of slop; B = 24 holds the slop under an hour and costs 25.8 bits and about 430 random bit probes per lookup, which at 300,000 lookups per second is roughly 130 million random memory reads per second and is the part that bites on real hardware, not the gigabytes.
- Convert P into a number a product owner can accept or reject, noting that only a first delivery can be wrongly suppressed: about 2 billion first-deliveries a day times P = 10^-4 is roughly 200,000 notifications silently suppressed per day, and at the mis-sized 4.8 GB geometry the same sentence reads about 4.8 million. That sentence is the deliverable; the byte count alone is not. Then shard on a hash of the pair so each worker owns a disjoint key space and needs no coordination, or shard on recipient_id if coalescing decisions for one recipient must be co-located, and state the restart behaviour plainly: losing a worker's in-memory sets produces duplicates, not drops, which is the safer direction, and persisting only the current bucket bounds how many. Exact at-most-once needs memory proportional to the distinct pairs in the window; any design using materially less trades one error direction for the other, and the answer is naming which.
Expected result (40 min): A sharded, bucket-rotating dedup gate sized from the delivered rate rather than the per-filter rate: with 24 hourly buckets at P = 10^-4 the per-bucket p is 4.2 * 10^-6, which is 25.8 bits per element and about 18 hashes, roughly 6.4 GB for 2 billion distinct pairs against 32 GB or more for exact, about 430 bit probes per lookup, a window slop of one bucket, an explicit error direction, and about 200,000 notifications silently suppressed per day.
Check yourself
- Recomputing 1 - (1 - p)^24 from your per-bucket p reproduces the P you quoted to the caller, and recomputing bits per element as 1.44 * log2(1/p) reproduces the byte total.
- The same pair submitted twice one second apart is suppressed; submitted twice 25 hours apart it is delivered both times.
- Killing a worker mid-window produces a duplicate notification, never a missing one.
- Sizing every bucket at p = 10^-4 and then quoting 10^-4 as the delivered rate is the specific error the union bound catches: that geometry is 19.2 bits, 4.8 GB and a delivered rate of 2.4 * 10^-3, so its real cost is about 4.8 million suppressed notifications a day, 24 times the headline. Tightening the target to P = 10^-6 moves per-bucket p to 4.2 * 10^-8, bits per element to 35.4, the total to about 8.8 GB and the daily count to about 2,000, and all of those should fall out of bits = 1.44 * log2(24/P) rather than being re-derived by hand.
Follow-ups
- Add coalescing: 40 likes on one item within five minutes should become one notification. Does that change the key, the structure, or both?
- You are told a silent drop is unacceptable but a duplicate push is merely bad. Redesign the gate and state the new memory bill.
- The service does a rolling restart every 30 minutes. What does that do to your guarantee, and what is the cheapest durable state that restores it?
At-most-once push when the dedup store fails over
Notification envelopes are generated at 250,000 per second from likes, replies and mentions. Each carries a coalescing key and a dedup key, and delivery goes through a device push gateway that is itself at-least-once. A duplicate push is user-visible; a missing push for a direct reply is a product failure. Dedup keys currently live in an in-memory store that can lose a partition on failover. Specify the delivery path, then state what you do during a dedup-store failure, per notification class, and the bound you commit to for each: deliver and risk duplicates, or suppress and risk losses.
How a strong candidate works it
- Split the two keys, because they solve different problems and need different lifetimes. The coalescing key (recipient, target, kind) collapses many actors into one envelope inside a window and is what turns 250,000 envelopes per second into a much smaller push rate; the dedup key stops a redelivered fanout event from creating a second envelope and must outlive the consumer's retry horizon.
- Move the invariant off the cache: a unique constraint on (recipient_id, dedup_key) on the durable envelope row makes at-most-once a property of the write, demoting the in-memory store to a negative-lookup filter that only saves latency. With that in place a failover is a latency and capacity event rather than a correctness event, which is the strongest available answer and should be stated before any fallback policy.
- Where the durable write is not affordable at this rate, make the choice per class and say it in product terms. High-salience direct notifications (reply, mention, direct message) fail open: deliver, accept a rare duplicate, because a lost direct reply is the worse outcome. Low-salience aggregate notifications fail closed: suppress, because a repeated 'someone liked your post' is noise and the miss costs almost nothing.
- Close the end-to-end gap honestly. The gateway is at-least-once and a device can receive a retransmit after a lost acknowledgement, so server-side dedup alone cannot give at-most-once at the device. Attach a stable notification identifier or collapse key so the client suppresses the repeat; without that the guarantee as stated is untrue regardless of the server design.
- Add backpressure and publish the bounds: cap pushes per recipient per window, shed the lowest class first, and commit to numbers, for example p99 under ten seconds from action to push, and during a failover at most one duplicate per envelope for the failover window for fail-open classes and full suppression for fail-closed classes.
Expected result (40 min): Coalescing key windows many actors into one envelope; a unique constraint on (recipient_id, dedup_key) carries the at-most-once property; the cache is a latency optimisation only. Direct classes fail open during failover with at most one duplicate per envelope for that window; aggregate classes fail closed and are suppressed. A stable client-side notification id suppresses gateway retransmits.
Check yourself
- The correctness property survives losing the entire in-memory store.
- Every notification class is assigned a fail direction with a stated reason.
- The end-to-end guarantee accounts for the gateway being at-least-once, not just the server path.
- Both bounds are numbers, and they are different numbers.
Follow-ups
- An account with 60 million followers publishes. Do you create 60 million envelopes? If not, what does the follower see instead?
- How long do dedup keys live, what does that retention cost at 250,000 per second, and what expires them?
- Show the interleaving where two workers both pass the negative filter. Name the exact mechanism that stops the second write.
Feed page latency scales with page size after a card redesign
A feed page of 40 items takes 1.4 s at p99; a page of 10 takes 380 ms. Feed service request rate is flat, but reads against the content and account stores are up 40x since last week's release, which added author badges and a per-item saved state to each card. No entry appears in the slow query log. You have distributed traces, per-request span counts, and both stores' statement statistics. Give the ordered list of what you inspect, the defect, and the fix with its cost.
How a strong candidate works it
- Start from the ratio, not the slow query log. The log is empty because no individual statement is slow; the defect is the number of statements, which that log cannot show. Divide store reads per second by feed requests per second and you get lookups per request. A per-request count that rises with page size is a per-item pattern by definition, and that single number localises the fault before you read any code.
- Confirm linearity deliberately: latency at page size 10 versus 40 is 380 ms versus 1.4 s, close to a 4x ratio on a 4x page size, so the cost is per item rather than per request. A fixed per-request cost would have shown a much flatter curve.
- Read one trace and count spans by name. Expect roughly four lookups per card after the release — author record, saved state from engagement, the block check, and the media rendition — so 40 items issue about 160 round trips. At 6 ms each, issued sequentially, that is around 960 ms of pure round-trip time and accounts for the whole regression without any query being slow.
- Replace per-item lookups with one batched statement per entity type: collect the ids for the whole page first, then issue WHERE content_id = ANY($1) and WHERE account_id = ANY($1). Two preconditions to state out loud: the application must re-associate results by key rather than by array position, because the database returns rows in an unspecified order, and it must handle ids that return no row, because a deleted or suspended entity silently shrinks the result set. Four batched queries replace roughly 160, which is O(1) round trips per entity type instead of O(k).
- Add a request-scoped memo keyed by entity id on top of the batch. A page of 40 items usually contains far fewer distinct authors than items, so deduplicating ids before the batch cuts the author fetch further, and the memo prevents the ranking and rendering passes from re-fetching what hydration already loaded.
- Make the defect detectable next time: export lookups per feed request as a metric, and add a test that renders a fixed page and asserts the query count stays under a constant. A latency threshold will not catch this again, because the per-item pattern is cheap at the page sizes used in tests.
Follow-ups
- The block check is per viewer, not per item. Where does it belong in the request, and what changes about its cost once you move it?
- One of the batched ids returns no row because the item was deleted between the timeline read and hydration. What does the page render, and what does the timeline store do about that slot?
- Ranking needs an engagement count per item. Do you batch that from the counter store or accept a stale value, and what is the staleness bound you commit to?
Unblock an engineer seeing duplicate items on page two
An engineer on your team reports that users see the same items twice when they load page two of the feed. Their query is ORDER BY sort_key DESC LIMIT 40 OFFSET 40, and their proposed fix is to dedupe by content_id in the client. They are blocked, frustrated, and have been on it a day. Unblock them without taking the keyboard: get them to a reproduction, explain why the duplicates appear, why client-side dedup is the wrong layer, and what you would have them build instead.
How a strong candidate works it
- Get them to a deterministic reproduction before explaining anything. Insert k items at the head between the two requests and watch exactly k already-seen items reappear on page two. A reproduction they ran themselves teaches the mechanism; an explanation teaches them that you were right.
- Describe the cause as counting rather than as a bug. OFFSET defines a window by counting from the start of a result set that is mutating at the head, so k insertions above the window push k rows down into page two. Then show the half they have not seen: deletions shift the other way and skip rows entirely, with no signal to the client that anything was missed.
- Use the skip case to show why client dedup is the wrong layer. It suppresses the visible repeat and can do nothing about the invisible omission, because nothing ever told the client an item existed. Add the performance argument second: the database still produces and discards OFFSET rows, so page n costs more than page one even when no duplicate appears.
- Hand over the replacement with its precondition attached, because the precondition is where this bug comes back. Seek on the previous page's last key: WHERE owner_id = :owner AND sort_key < :cursor ORDER BY sort_key DESC LIMIT 40, served by the index on (owner_id, sort_key DESC). The key must be unique within the owner, or the boundary row is either repeated or dropped, which is why the sort key is the time-sortable content id rather than a timestamp that collides at high insert rates.
- Leave them the work and the proof: they write the query, the test that inserts at the head between page requests, and the plan check confirming an index scan rather than a sort. Then ask them to explain the skip case back to you, which is the only cheap way to find out whether the mentoring landed.
Follow-ups
- What must the cursor encode once a page merges pushed timeline entries with items pulled from above-threshold authors?
- The product wants a 'new items above' indicator. How do you show what arrived above the cursor without breaking the page sequence?
- What test would have caught this before release, and why did the existing tests pass?
Where candidates lose points
Counting only nodes that appear as children in the parent-child graph question
Build the parent count from every pair, and give every node that appears anywhere a count of zero first, including nodes that only ever appear as a parent. Nodes that appear only as parents are exactly the zero-parent answer, and a dictionary that records only children silently drops them. Ask whether duplicate pairs can occur. Before you finish, trace a node with two parents and a repeated pair.
Writing the tennis scoring tracker as one growing chain of if-statements
Name the states before writing code: point scores within a game, deuce, advantage, game won, and then set and match counters. Write the transition for a won point from each state, including advantage back to deuce, and reject points recorded after the match is decided. Get one game correct and tested before adding sets and the match. Extending a working game is cheaper than debugging a whole match of nested conditions.
Parsing the admin log before settling its format and what seniority means
The reported prompt asks for admins ordered by last addition time and a check of whether one admin can remove another based on seniority. Before parsing, state the record separator and timestamp format, and say what happens when an admin is added twice or removed and re-added. Also settle whether seniority means an earlier addition or a position in an ancestor chain (the related bank question uses ancestor relationships and creation timestamps). Write those assumptions down, parse into a map keyed by admin, and test the re-added case.
Aggregating ad clicks and impressions from an at-least-once stream with no event id
In the ad-serving and ad event pipeline design questions, the aggregates feed reporting and billing, so an event counted twice after redelivery is a billing error. Give each event a unique id and make the aggregation step idempotent, either by deduplicating on event id within the redelivery window or by upserting keyed rows. State that a partitioned log guarantees order only within a partition. Explain how late events are handled and how a batch reconciliation corrects the real-time count.
Answering 'why Reddit' and 'how would your manager rate you' with generic lines
The reported behavioural questions include why you want to join Reddit and which community is your favourite, and how your current or previous manager would rate you from 1 to 10. Use the product before the loop and name a community you actually read, with one concrete observation about how it works for its members. For the rating, give a number below 10, cite the feedback it is based on, and name the growth area and what you have done about it. A perfect score with no evidence leaves the interviewer nothing to write down.
Design first, for a senior candidate
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Day 1 — Recruiter call and role mapping
- Mark each line of the posting as done, adjacent or new, and match your experience to one team area the source notes name: Core App, Ads Engineering, Infrastructure, Community Tools, or web and mobile client work.
- Write three questions for the recruiter: which team, which technical screen format (live coding in a tool such as CodeSignal or CoderPad, or a hiring manager screen), and how the onsite sessions are split.
- Write your why-Reddit answer around a community you actually use and one concrete thing you noticed about how it works.
Deliverable: A one-page role map, three recruiter questions, and a why-Reddit answer that names a real community.
Day 2 — Technical screen: trees and graphs
- Solve the reported parent-child graph question (nodes with zero parents and exactly one parent) with a count map. Trace it on a node that appears only as a parent and on a repeated pair.
- Solve lowest common ancestor twice: once for a general binary tree with a recursive search, and once for a BST using the ordering (the bank's Lowest Common Ancestor in BST). State the complexity of each.
- Solve two BFS or DFS problems of your choice over an array or grid, for example counting connected regions with BFS and a flood fill with DFS. Write the brute force first and time how long it takes to pass.
Deliverable: Five solved problems (parent count, LCA in a binary tree, LCA in a BST, and two BFS or DFS problems), each with its complexity stated and a written trace for empty, single-element and duplicate inputs.
Day 3 — Coding: hashing, intervals and streams
- Solve the reported transaction-stream question with a hash map. State what the map is keyed by and how large it grows.
- Solve the bank's Merge Overlapping Chat Messages: sort by stream and start time, then merge overlapping or consecutive intervals in O(n log n). Test touching intervals, one message, and messages from different streams.
- Implement the bank's sliding-window rate limiter and say whether your version is exact per request or approximate.
- Work the duplicate-notification worked exercise (Suppress duplicate notifications under bounded memory and redelivery) and recompute its sizing figures yourself.
Deliverable: Three working implementations with tests (transaction stream, chat message merge, rate limiter), plus your own recomputed numbers for the dedup exercise.
Day 4 — Onsite: practical object-oriented coding
- Build the tennis scoring tracker in stages: one game with deuce and advantage, tested; then sets and match; then rejecting input once the match is over.
- Solve the admin log question. Write down the log format assumptions, parse into a map, return admins by last addition time, then add the permission check (compare the bank's Permission Deletion Logic).
- Design an in-memory cache with a memory bound, an eviction policy and an invalidation rule, and say why that policy fits the access pattern.
- For extra repetitions, time yourself on the bank's Implement a Stateful Chatter Message Store or Design Add, Overwrite, Undo, and Redo for Billing State.
Deliverable: The tennis tracker and admin log solutions built in stages, each with its assumptions and test list written out, plus a written cache design.
Day 5 — Onsite: system design for feed and ads
- Newsfeed: choose push, pull or hybrid fanout and state the follower threshold where you switch. Name the partition key and the query it makes expensive.
- Ad-serving API: separate the bid request path from the click and impression aggregation path. Design idempotent aggregation and a reconciliation job.
- Work the design exercise (At-most-once push when the dedup store fails over) and compare your fail-open and fail-closed classes with the worked answer.
- Sketch the bank's Rate Limiter for Multiple APIs with per-user quotas enforced across several nodes.
Deliverable: Two designs taken to API, data model and failure handling, each with the trade-off you chose against written beside it.
Day 6 — Storage, caching and incidents
- Work the SQL exercise (Take a viral item's like counter off one hot row), then explain the difference between lock contention and a genuine lost update.
- Work the debugging drill (Feed page latency scales with page size) and write the ordered list of what you inspect before reading the approach.
- Design the reported caching layer for a read-heavy application: cache-aside or write-through, the staleness window, and how you prevent a stampede.
- Write short answers to the bank's Database Down Scenario, CSRF Mitigation and XSS Mitigation. If the team is ads- or ML-adjacent, also sketch the ML feature store question.
Deliverable: The hot-counter exercise completed, a written N+1 diagnosis, a caching design, and short incident and web-security answers.
Day 7 — Behavioural stories and onsite consistency
- Write STAR stories for the reported behavioural questions about a difficult colleague and harsh technical feedback, and one for the bank's Your Strengths and Weaknesses.
- Prepare the manager-rating answer: a number, the feedback behind it, the growth area, and what you changed.
- Prepare one story each for the bank's Collaborate Across Product and Data Science and Manage Technical Debt Under Delivery Pressure.
- Write a one-page fact sheet per project. Have someone ask you the same project question twice, an hour apart, and compare the two answers for numbers that changed.
Deliverable: Five behavioural stories, a manager-rating answer, and a project fact sheet that held steady across two tellings.
Frequently asked questions
Is this an official Reddit interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Reddit. Rounds and questions reflect what candidates have reported, not a process Reddit has published, and they change over time. Confirm the current format and scope with your recruiter.
What does the Reddit Software Engineer technical screen look like?
Candidate reports describe it as either a live coding session in a collaborative editor such as CodeSignal or CoderPad, or a hiring manager phone screen. Both focus on core algorithms and coding fluency. Ask your recruiter which one you have. To cover both, do timed algorithm practice on arrays, trees and graph search, and prepare a short walkthrough of a system you built.
Can I choose my programming language for the coding rounds?
Candidate reports say you can generally choose a standard modern language such as Python, Go, Java or JavaScript/TypeScript. Confirm with your recruiter. Practise in the language you will use, including its standard collections for maps, heaps and sorting, so you are not looking up syntax during the round.
How much algorithmic coding versus practical coding should I expect?
Both. Candidate notes say screens often use classical algorithm problems (arrays, trees, graph search), while onsite coding leans on practical object-oriented modelling, string processing and domain design. The reported questions include a tennis scoring tracker and an admin log parser. Split your practice between the two, and for practical problems, practise building a working baseline and then extending it.
Which system design topics should I prepare?
The reported design questions cover a newsfeed that distributes posts to subscriber feeds, an ad-serving API with real-time click and impression aggregation, a caching layer for a read-heavy application, and an ML feature store or real-time signal pipeline. The bank adds a real-time ad event pipeline, a rate limiter for multiple APIs, a game leaderboard and scaling a small forum app. Prepare fanout strategy, cache invalidation, idempotent stream aggregation and rate limiting.
Do I need ads or machine learning experience?
The role notes list AdTech, auction systems and streaming systems such as Kafka as nice-to-have skills, not requirements. Several reported design questions touch ads and ML pipelines, so learn the basics of event aggregation and feature freshness even if your background is elsewhere. Be clear about what you have and have not built.
How soon do candidates hear back after the onsite?
Reports vary. Candidate notes say most people hear back within three to five business days after the final loop, usually through a call with the recruiter. At the end of the onsite, ask your recruiter what timeline to expect.
Keep practising
- Reddit questions on PracHub — 38 Software Engineer questions in the bank.
- Software Engineer question bank — cross-company practice.
- Interview preparation framework
- All interview guides