cswork / libraryOpen practice workspace

Interview guides · Citizen (NY)

Citizen (NY) Software Engineer Interview Guide

Citizen (NY) Software Engineer interview guide: reported rounds, API design, algorithm and React build questions, and a 7-day prep plan.

The 60-second brief

Citizen (NY) is described in candidate reports as a real-time public safety alert product with a consumer app. The engineering work reported for this role covers systems that take in live location data, incident reports submitted by users and live video streams, plus internal tools that let an operations team verify and dispatch alerts, and the mobile and web clients that receive those updates. Software Engineers are reported to work alongside product managers, operations staff, data scientists and mobile engineers.

This guide is for candidates interviewing for a Software Engineer position at Citizen (NY). It covers the three reported stages (Recruiter Call, Technical Screening, Onsite Interviews), the ten questions candidates report across algorithms, API and system design and web application implementation, the stack the role lists (JavaScript or TypeScript, React, Node.js, REST and real-time APIs, caching and data modelling), and a plan for the week before the loop.

Candidates report three stages over roughly 3 to 5 weeks: a Recruiter Call, a Technical Screening, then Onsite Interviews built around hands-on development, with API design and building a small web application featuring prominently.

Focus areas

  • Coding interviews
  • Problem solving
  • API design

Overview

The product is built around time-sensitive, location-aware data: where an incident is, who is near it, how its status changes, and how quickly a phone or browser learns about it. Candidates describe a role that touches intake of user-submitted reports, delivery of alerts to clients, and tooling for the operations staff who verify and dispatch them. That shape is visible in the reported questions: a rate limiter for bursts of traffic, an expiring cache for alerts, nearest-responder search on a grid, merging overlapping emergency zones, and a push system keyed to location.

The reported questions fall into three groups. Algorithmic questions cover a rate limiter, shortest path or nearest responder on a coordinate grid, an in-memory cache with expiration, and interval merging. API and system design questions cover an incident reporting and tracking API (REST or GraphQL), a geolocation-based push notification dispatcher, and a comments and reactions API for live incident video. Web application questions ask you to build a filterable real-time incident dashboard, a polling map with incident markers, and an incident report form with location tagging and validation.

Candidates report that the loop leans toward building working things rather than whiteboard-only discussion. Recruiter conversations may mention a conventional system design round, but reports say the practical emphasis falls on API design and a rapid web build, so have working code ready as well as diagrams. Prepare in your strongest language for the algorithm questions, and prepare React with TypeScript or modern JavaScript, plus a small Node.js API, for the build questions, since those are the technologies the role lists.

Reports also mention that prompts can be open-ended and that interviewers may suggest simplifying or switching approach part-way through. Practise stating assumptions in one or two sentences, building the smallest end-to-end version first, and adjusting without restarting. The sections below tie each round, question group and day of the plan to that style of preparation.

The Citizen (NY) interview process

Candidates report these three stages, in this order, over roughly 3 to 5 weeks. Formats can vary between loops, so confirm the exact structure with your recruiter.

Recruiter Call

Candidates describe this as a short call to line up your background and expectations with the role. No technical questions are reported for it. Use it to learn the shape of the rest of the loop: recruiter conversations are reported to mention a standard system design round, while the loop itself is reported to centre on API design and building a small web application, so ask what format each later stage takes and which language or framework you may use.

What to demonstrate

  • How your background lines up with a role that lists JavaScript or TypeScript, React and Node.js
  • Your expectations for the position and the process
  • Whether you can describe your own projects clearly and concisely

How to prepare

  • Write a short walkthrough of your career that highlights any real-time, location, notification or mobile work, since the product revolves around those.
  • Prepare questions about the format of the technical screen and of each onsite session, including whether you will code in your own editor or a shared one, and whether you may use a framework.
  • Have your timing, location and compensation expectations ready to state plainly.

Technical Screening

Candidates describe an initial technical assessment of foundational skills. The reported algorithm questions are the closest practice material for it: a rate limiter, shortest path or nearest responder on a coordinate grid, an in-memory cache with expiration, and merging overlapping intervals. Prepare each as working code with tests rather than as a spoken outline, and be ready to state time and space complexity and the edge cases.

What to demonstrate

  • Foundational programming skills and data structure choice
  • Reasoning about time and space complexity
  • Handling of edge cases such as empty input, touching intervals or expired entries
  • Explaining your approach while you write code

How to prepare

  • Implement a sliding window rate limiter and a token bucket, each with an injectable clock so you can test bursts at window boundaries.
  • Build a TTL cache with lazy expiry on read plus a sweep, and add a maximum size with an eviction rule.
  • Write breadth-first search on a grid with blocked cells and a multi-source version that starts from every responder at once, then interval merging that handles touching and nested ranges.

Onsite Interviews

Candidates describe a series of hands-on development rounds in which you build working interfaces and design real-world APIs. The reported question groups here are API and system design (an incident API in REST or GraphQL, a geolocation push dispatcher, a comments and reactions API for live video) and web application implementation (a filterable incident dashboard, a polling map, a validated report form). Candidates also report being nudged to simplify or pivot mid-problem, so keep your first version small enough to change.

What to demonstrate

  • Working code produced within a limited timeframe
  • API and schema design: payloads, status codes, data modelling
  • Choosing between WebSockets, Server-Sent Events and polling for live data
  • Asking clarifying questions on open-ended prompts and adapting to feedback

How to prepare

  • Keep a project scaffold you can start in minutes: a React app with TypeScript, a fetch layer and a small Node.js endpoint serving mock incidents.
  • Write an endpoint list for the incident API with status codes, an idempotency key for report creation, pagination, and the reported, verified and resolved state transitions.
  • Sketch the push dispatcher end to end: location-bucketed subscriber index, queue, worker fan-out, push provider limits, retries and deduplication.
  • Build the three reported web tasks from scratch: filterable dashboard, polling map with markers, and a form with client-side validation and location capture.

Where candidates lose points

A rate limiter that uses a fixed window counter and lets a double burst through at the boundary

Use a sliding window log or a token bucket, and say which one you chose and why. Keep state per key, remove idle keys so memory does not grow without limit, and inject the clock so you can test the boundary case instead of asserting it.

A cache whose expiry is only checked when an entry is read

Combine lazy expiry on read with a periodic sweep, or keep a min-heap of expiry times, so dead alerts leave memory. State what happens at capacity and name the eviction rule.

Grid, interval and geometry code that fails on its edge cases

Sort intervals by start before merging and test touching ends, nested ranges and a single interval. Mark cells visited when you enqueue them in breadth-first search. Use a distance formula suited to the coordinates in the prompt, and ask whether the grid has obstacles or diagonal moves.

An API design that stops at a list of endpoints

For each endpoint give the request and response payload, status codes, pagination and the behaviour of a retried create. Model incident status transitions explicitly, and say how a mobile client on a weak network gets smaller payloads and live updates.

A web build that polishes styling while the data flow is half done, or polls without cleanup

Get fetch, state, render, filter and submit working end to end first. Cancel in-flight requests and clear timers on unmount, ignore stale responses, show loading and error states, and add styling last.

Questions & practice

Merge overlapping intervals representing active emergency zones.

Medium · Algorithmic Problem Solving

Approach

  1. Clarify the interval semantics before coding: are endpoints inclusive, so that [1,3] and [3,5] merge? Is the input already sorted? Can start ever exceed end?
  2. Sort by start, O(n log n). Then walk the list once: if the current start <= last merged end, set last end = max(last end, current end). Otherwise, append the current interval.
  3. The max matters because an interval can be fully contained in the previous one. [1,10] followed by [2,3] must stay [1,10], and a bare assignment would shrink it.
  4. Complexity: O(n log n) time, dominated by the sort, and O(n) space for the output, plus whatever the sort uses. If the input is already sorted, the merge pass is O(n).
  5. Test the edge cases: an empty list, a single interval, all intervals overlapping into one, none overlapping, duplicates, and unsorted input.

Follow-up

  • How would you insert one new zone into an already merged, sorted list, and what does that cost?
  • How would you handle intervals arriving as a stream, with the merged set queryable at any time?
  • How would you compute the total time covered by at least one active zone, or the maximum number of overlapping zones?

Collapse a redelivered event batch into per-aggregate high-water marks

Easy · hashing · at-least-once · aggregation

You drain a batch of up to 5,000,000 events, each (aggregate_id BIGINT, aggregate_version INT, event_type, payload). The log guarantees order within one aggregate only; the batch merges 64 partitions, and a relay failover has redelivered a range, so an older version for an aggregate can appear after a newer one. Given a map of last_applied_version per aggregate, produce the events worth applying, at most one per (aggregate_id, version), plus the count discarded. Target O(n) time. State the memory for 2,000,000 distinct aggregates and what you do when it does not fit.

Approach

  1. One pass, one hash map from aggregate_id to the highest version kept, and a discard counter. An event whose version is at or below last_applied_version for its aggregate is dropped without further work, which is the whole reason the event carries its version rather than a delta. O(n) expected time, O(d) space in distinct aggregates.
  2. Keep the maximum, never the last occurrence. The redelivered range means the final appearance of an aggregate in the batch can be an older version than one seen earlier in the same batch, so last-wins applies stale state over newer state and the projection regresses with no error anywhere.
  3. Cost the memory instead of calling it large: an 8-byte key plus a 4-byte version is 12 bytes of payload, and an open-addressed table held at a 0.7 load factor costs roughly 17 bytes per entry before per-slot metadata, so 2,000,000 aggregates is tens of megabytes in a native layout and several times that in a runtime that boxes both key and value.
  4. If the distinct set exceeds memory, partition on hash(aggregate_id) mod P and reduce each partition independently. Every event for one aggregate hashes to the same partition, so the per-partition result is exact and the merge is concatenation rather than a second reduction.

Follow-up

  • The payload is a patch rather than a snapshot, so applying only the highest version loses the intermediate changes. What changes in your reduction?
  • How do you detect that version 7 arrived while version 6 was never delivered, and what should the consumer do about the gap?

Track a rolling failure rate per destination for circuit decisions

Easy · sliding window · ring buffer · circuit breaker

The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.

Approach

  1. Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
  2. Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
  3. State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
  4. Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.

Follow-up

  • The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
  • A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?

Explain why the owner filter ignores the listing index

Medium · composite index · query plans · sort elimination

The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.

Approach

  1. Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
  2. For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
  3. Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
  4. Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.

Follow-up

  • 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
  • A dashboard runs this for 40 owners in one page load. What changes about the design?

Replace offset paging on the resource feed with keyset

Easy · keyset pagination · composite index · sort order

resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.

Approach

  1. Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
  2. Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
  3. Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
  4. Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.

Follow-up

  • The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
  • Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?

Implement a rate limiter to prevent API abuse during sudden traffic spikes.

Medium · Algorithmic Problem Solving

Approach

  1. Start by pinning the limit: per API key, per user or per IP, how many requests per window, and whether short bursts are allowed. That choice decides which algorithm fits.
  2. Compare the algorithms. A fixed-window counter is cheap, but up to twice the limit can get through around a window boundary. A sliding-window log (a deque of timestamps per key) is exact but costs O(limit) memory per key. A sliding-window counter weights the previous window's count to approximate the log in O(1) memory.
  3. Token bucket is the usual pick when bursts are allowed: keep tokens and last_refill per key, refill with tokens = min(capacity, tokens + elapsed * rate), and allow the request only if tokens >= 1, then decrement. Each check is O(1) time and O(1) space per key.
  4. When several servers share the limit, put the state in Redis and make the read-modify-write atomic with a Lua script (or INCR plus EXPIRE for a fixed window), so two servers cannot both spend the last token. Use a monotonic clock for elapsed time in a single process.
  5. On rejection, return HTTP 429 with a Retry-After header. Evict idle keys with a TTL. Decide in advance whether the limiter fails open or fails closed when its store is unreachable.

Follow-up

  • How would you let legitimate traffic surge during a major incident while still blocking one abusive client?
  • What happens to your limiter if Redis is down or slow, and would you fail open or closed?
  • How do clock differences between application servers affect a sliding-window implementation?

Find the shortest path or nearest incident responder on a coordinate grid.

Medium · Algorithmic Problem Solving

Approach

  1. Clarify the model: 4- or 8-directional moves, whether there are blocked cells, whether moves have unit cost, and whether the answer is a path length, the path itself, or the nearest responder's identity.
  2. On an unweighted grid, use BFS from the start cell. Mark a cell visited when you enqueue it, not when you dequeue it. That costs O(R*C) time and space and gives shortest distances. Keep a parent map if the path itself is needed.
  3. For the nearest responder, run a multi-source BFS: seed the queue with every responder at distance 0, and the first time BFS reaches a cell, that is its nearest responder. One pass of O(R*C) answers every cell, instead of one BFS per responder.
  4. If moves have different costs, switch to Dijkstra with a min-heap, O(E log V). A* with a Manhattan-distance heuristic is admissible for unit-cost 4-directional moves and usually expands fewer cells. With no obstacles, Manhattan distance is the answer directly, and a linear scan over N responders is O(N).
  5. Cover the edge cases out loud: the target is unreachable (return -1 or null), the start or target is a blocked cell, the start already is the target, an empty grid, and ties between responders at equal distance.

Follow-up

  • How would you answer many nearest-responder queries per second when responders keep moving?
  • What changes when positions are real latitude and longitude instead of grid cells?
  • How would you return the k nearest responders instead of one?

Design an in-memory cache with an expiration policy for real-time alerts.

Medium · Algorithmic Problem Solving

Approach

  1. Core structure: a hash map from key to (value, expiresAt) gives O(1) get and set. Compute expiresAt from a monotonic clock so a wall-clock change cannot expire or revive entries.
  2. Lazy expiry: on get, if now >= expiresAt, delete the entry and return a miss. That alone is correct, but keys nobody reads again stay in memory, so add active cleanup as well.
  3. For active cleanup, either keep a min-heap ordered by expiresAt and pop expired entries (O(log n) per insert; on pop, skip stale heap entries whose expiresAt no longer matches the map), or run a periodic sweep that samples keys, as Redis does.
  4. Bound memory with a capacity and an eviction policy. LRU is the standard one: the hash map points into a doubly linked list, which gives O(1) get, set and evict. Say whether an expired entry or the least-recently-used live entry is evicted first.
  5. Concurrency: guard the map and list with a lock, or with striped locks per key range. Mention the thundering herd when a hot alert key expires, and prevent it with single-flight loading or jittered TTLs.

Follow-up

  • How would you keep several application instances consistent when an alert is updated or retracted?
  • What happens under memory pressure if many keys share the same TTL and expire at once?
  • Would you build this yourself or put Redis in front of the service, and why?

Design a RESTful or GraphQL API for reporting and tracking active public safety incidents.

Medium · API & System Design

Approach

  1. Model the resources first. An incident has id, status, severity, latitude and longitude, created_at, updated_at and version. Updates are an append-only child resource. Define an explicit status state machine (for example reported, verified, resolved) and reject illegal transitions.
  2. Write out the REST endpoints. POST /incidents returns 201 with a Location header and accepts an Idempotency-Key, so a retried mobile request cannot create the incident twice. GET /incidents/{id}. GET /incidents filters by bounding box or radius, status and severity. POST /incidents/{id}/updates. PATCH /incidents/{id} changes status.
  3. Pin down the status codes: 400 or 422 for validation errors with per-field details, 404 for an unknown id, 409 for an illegal status transition, and 412 when an If-Match ETag version check fails. That version check is how two operators editing the same incident avoid a lost update.
  4. For the list endpoint, use cursor (keyset) pagination on (created_at, id) rather than offset, because new incidents keep arriving. Back the location filter with a spatial index, such as a PostGIS GiST index or a geohash column with a B-tree. Keep payloads small for mobile clients by returning summary fields in lists.
  5. On REST versus GraphQL: GraphQL lets each client pick its fields and offers subscriptions for live tracking, but HTTP caching is harder and you need query depth or cost limits. REST plus SSE or WebSocket for live updates is simpler to cache and rate-limit. Name the trade-off and pick one.

Follow-up

  • How would you detect and merge duplicate reports of the same incident from many users?
  • How does a client keep an incident's status current without polling too often?
  • How would you version this API once mobile clients you cannot force-upgrade depend on it?

Architect a notification dispatch system that sends push alerts to millions of users based on their geolocatio

Medium · API & System Design

Architect a notification dispatch system that sends push alerts to millions of users based on their geolocation.

Approach

  1. Split the system into location ingestion and alert fan-out. Devices send location on significant movement rather than continuously. The service stores each user's last-known geo cell (geohash, S2 or H3) in a cell-to-users index, for example Redis sets.
  2. When an alert fires, compute the set of cells that covers the alert radius (the center cell plus its neighbours at the right precision). Read candidate users from those cells, then filter them by exact haversine distance, since cells only approximate a circle.
  3. Fan out through a durable queue such as Kafka, partitioned by cell or user, to a pool of workers that batch calls to APNs and FCM. Make delivery idempotent per (user, alert) pair so that redelivery from the queue does not double-notify.
  4. Handle provider behaviour: retries with exponential backoff and jitter, provider throttling, and removing device tokens the provider reports as unregistered. Give severe alerts a separate high-priority queue so a large low-severity fan-out cannot delay them.
  5. State the trade-offs: last-known location is stale for users who have moved, and a dense area can mean one alert reaches millions of devices. Measure end-to-end latency from alert creation to provider acceptance, and track per-user notification caps.

Follow-up

  • What happens when one alert in a dense city matches several million users, and where is the bottleneck?
  • How do you avoid notifying a user twice when their location update lands mid-fan-out?
  • How would you prove alerts reached users quickly, and which metrics would you alert on?

Design a real-time comments and reactions API for live incident video streams.

Medium · API & System Design

Approach

  1. Separate writes from delivery. POST /streams/{id}/comments and POST /streams/{id}/reactions are plain HTTP calls with per-user rate limits. Delivery to viewers goes over a WebSocket or SSE connection subscribed to that stream. SSE works when the client only receives and sends through HTTP.
  2. Store comments partitioned by stream_id, with a server-assigned, time-sortable id (snowflake-style or ULID). That gives a stable order within a stream and a cursor for GET /streams/{id}/comments?before=... when a viewer joins and loads history.
  3. Publish each accepted comment to a per-stream pub/sub channel (Redis pub/sub, Kafka or similar). Connection servers subscribe only to the streams their clients are watching and push to those sockets.
  4. Do not broadcast reactions one by one. Count them per stream in short intervals and broadcast aggregated counts. This keeps fan-out proportional to viewers rather than to viewers times reactions.
  5. Plan for reconnects and moderation. A client reconnects with its last-seen comment id and backfills the gap. Comments pass a moderation filter before broadcast, and a removal is broadcast as a tombstone event so clients delete it.

Follow-up

  • How does the design change for a stream with a very large number of concurrent viewers?
  • How do you guarantee a viewer who drops for ten seconds sees no gap and no duplicates?
  • Would you pick WebSockets or SSE here, and what does each cost behind a load balancer?

Build a lightweight dashboard that displays a real-time feed of incidents filtering by severity and distance.

Medium · Web Application Implementation

Approach

  1. Pick the state shape before writing JSX: incidents in a Map keyed by id, the filters (a set of severities and a max distance), the user's coordinates, and loading and error flags. Compute the filtered, sorted list with useMemo rather than storing it as a second piece of state.
  2. Fetch in a useEffect. Poll with a setTimeout chained after each response, or open an EventSource for SSE. Clean up the timer or connection on unmount, and use AbortController so a late response cannot update an unmounted component.
  3. Merge each new batch by id, so an updated incident replaces its old version instead of duplicating it. Sort by time or severity after filtering.
  4. Compute distance with the haversine formula: d = 2R * asin(sqrt(sin^2(dlat/2) + cos(lat1) * cos(lat2) * sin^2(dlon/2))), with R about 6371 km and angles in radians. If geolocation permission is denied, disable the distance filter or ask for a location.
  5. Get a working feed with filters end to end first, then add loading, empty and error states. Leave styling for last.

Follow-up

  • The feed now has thousands of incidents: what do you move to the server and what do you virtualize in the browser?
  • How do you stop the list from jumping while a user is reading it and new incidents arrive?
  • How would you test the filtering and the polling logic?

Implement an interactive map interface that polls an endpoint and renders incident markers dynamically.

Medium · Web Application Implementation

Approach

  1. Create the map once. Hold the Leaflet or Mapbox instance in a ref initialised in a useEffect with an empty dependency array, so React re-renders never re-create the map.
  2. Poll with a setTimeout scheduled after each response completes, not a fixed setInterval, so slow responses cannot overlap. Abort in-flight requests on unmount. Pause polling while document.visibilityState is hidden, and back off after errors.
  3. Send the current viewport (map.getBounds()) with each request so the endpoint returns only visible incidents. Re-fetch when the map's move-end event fires.
  4. Diff instead of redrawing. Keep a Map from incident id to marker. Add markers for new ids, update the position or icon of changed ones, and remove markers whose ids disappeared. Clearing and re-adding every marker on each poll causes flicker and loses open popups.
  5. Handle races and scale. Tag each request with a sequence number and ignore responses older than the newest applied one. Cluster markers when many overlap, using marker clustering in Leaflet or a clustered GeoJSON source in Mapbox.

Follow-up

  • How would you replace polling with server push, and what changes in the marker update code?
  • What breaks with ten thousand markers, and how do you fix it?
  • A user pans quickly and older responses arrive after newer ones: how do you stop stale markers from appearing?

Create a functional web form that allows users to submit incident reports with location tagging and input vali

Medium · Web Application Implementation

Create a functional web form that allows users to submit incident reports with location tagging and input validation.

Approach

  1. List the fields and their rules before building: description (required, with a length limit), category and severity (from a fixed list), and location (latitude in [-90, 90], longitude in [-180, 180]). Use controlled inputs, or a form library with a schema validator.
  2. Get location from navigator.geolocation.getCurrentPosition. Handle its error callback (permission denied, position unavailable, timeout) with a manual fallback, such as clicking a point on a map or entering an address.
  3. Validate on blur and again on submit. Show errors next to each field, tied to the input with aria-invalid and aria-describedby, and move focus to the first invalid field on submit.
  4. Prevent double submission by disabling the button while the request is pending and sending an idempotency key. Map server 400 or 422 field errors back onto the matching inputs, because client-side checks never replace server validation.
  5. Cover the states: submitting, success with a confirmation, and network failure with the user's input preserved so they can retry.

Follow-up

  • How would you let a user submit a report while offline or on a flaky connection?
  • How would you add photo or video attachments without blocking the form on a large upload?
  • Which tests would you write first for this form?

Edge instances grow 400 MB per hour until the nightly restart

Hard · memory leak · heap analysis · metric cardinality

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

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?

Suggested practice checklist

The plan starts with a reusable scaffold, then covers the reported algorithm, API design and web build questions in the order candidates meet them, and ends with a full mock.

Day 1 — Map the loop and set up a scaffold

  • Write the three reported stages and, for each, what you still need to learn about its format; send those questions to your recruiter.
  • Create a React and TypeScript project plus a small Node.js server that returns a list of mock incidents with severity, latitude and longitude.
  • List the technologies the role names and mark any you have not used in the last year.

Deliverable: A runnable scaffold that renders mock incidents from a local endpoint, and a written list of format questions for the recruiter.

Day 2 — Rate limiter and expiring cache

  • Implement a sliding window rate limiter and a token bucket with an injectable clock; test the boundary burst and a quiet key.
  • Implement a TTL cache with lazy expiry, a sweep and a maximum size; test expiry, overwrite and eviction.

Deliverable: Passing tests for a rate limiter and an expiring cache, with time and space complexity written under each.

Day 3 — Grids, intervals and geometry

  • Write breadth-first search for the shortest path on a grid with blocked cells, then a multi-source version that finds the nearest responder for each cell.
  • Write interval merging and test touching, nested and unsorted inputs.

Deliverable: Three tested functions (grid search, interval merge, point in polygon) with a list of edge cases for each.

Day 4 — Incident API and live video interactions

  • Write the incident API as a spec: resources, endpoints, status codes, idempotent report creation, pagination and the reported, verified and resolved states. Then outline the GraphQL version and name its trade-offs.
  • Design the comments and reactions API for live video: pick WebSockets, Server-Sent Events or polling and justify it, and handle reaction counts under heavy load.

Deliverable: One written API spec for incident reporting and one for live comments and reactions, each with a justified delivery protocol.

Day 5 — Geolocation push dispatch

  • Design the dispatcher: how users' locations are stored and bucketed, how an alert's target area becomes a recipient list, and how a queue and workers send it.
  • Add failure handling: push provider rate limits, retries with backoff, deduplication and what happens during a traffic spike.
  • Design the endpoint that accepts batched location updates and say what you would cache or drop to keep it cheap.

Deliverable: A component diagram with the data flow, the storage and queue choices, and a written list of the first three failure points.

Day 6 — Build the three web tasks

  • Build the incident dashboard with live refresh and filters for severity and distance from a chosen point.
  • Build the map view with Leaflet that polls the endpoint and adds, moves and removes markers without re-rendering everything.
  • Build the report form with client-side validation, location capture from the browser and a fallback when permission is denied.

Deliverable: Three working screens in the scaffold, each with loading and error states and request cleanup.

Day 7 — Full mock and stories

  • Run a timed mock: one API design question out loud, then one web build from an empty folder, narrating assumptions and trade-offs as you go.
  • Rehearse your recruiter call walkthrough and your questions about the loop format.

Deliverable: A recorded or written mock of one design and one build, a final list of gaps, and two written story outlines.

Stories for a fast, ambiguous loop

Candidates do not report specific behavioral questions for this loop, so the prompts below are rehearsal material rather than reported questions. They follow the themes candidates describe: open-ended prompts that need clarifying, feedback or a change of direction during a build, and collaboration with product, operations and mobile colleagues.

  1. Tell me about a time you were given an open-ended problem and had to define its boundaries yourself.
  2. Describe a moment when someone challenged your approach part-way through the work. What did you change, and what did you keep?
  3. Tell me about something you shipped quickly. What did you cut, and how did you decide?
  4. Describe an incident or traffic spike you helped handle. What was the first signal, how did you find the cause, and what changed afterwards?
  5. Tell me about working with a product manager, operations team or mobile engineer whose priorities differed from yours.

Argue against a design, lose, and commit anyway

Medium · disagreement · service boundaries · decision records

Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.

Approach

  1. State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
  2. Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
  3. Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
  4. Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.

Follow-up

  • What threshold on that alert would have proved you right, and did anyone ever look at it?
  • If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?

Narrate an outage you owned from page to postmortem

Hard · incident response · blast radius · postmortems

Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you cannot name what you measured, choose a different incident.

Approach

  1. Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
  2. Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
  3. Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
  4. Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.

Follow-up

  • What would you do differently in the first five minutes, given the same dashboard and no more information?
  • Which follow-up action did you deliberately not take, and why was dropping it the right call?

Turn a code review disagreement into a decision

Easy · code review · optimistic concurrency · communication

A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.

Approach

  1. Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
  2. Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
  3. Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
  4. Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.

Follow-up

  • Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
  • The author says clients cannot handle a 409. How do you check whether that is true?