Interview guides · Committee For Children
Committee For Children Software Engineer Interview Guide
Committee For Children Software Engineer interview guide: HTTP, web security, system design and the three-hour project round, with a 7-day prep plan.
The 60-second brief
Committee For Children is an organization built around social-emotional learning and children's well-being. Candidates describe its engineering work as the software that delivers evidence-based programs to educators and students around the world, so the product is educational content and tooling used in classrooms and by school administrators. A Software Engineer there works alongside product managers, designers and educators, turns program goals into technical requirements, and takes features from idea through release and upkeep.
This guide is for candidates interviewing for a Software Engineer position at Committee For Children. It covers the six reported questions on HTTP, request methods, web security, concurrent traffic, database choice and long-term maintainability, the project-based coding session candidates describe, and the conversations about mission, collaboration and communication that candidates report alongside them. It does not cover other roles at the organization.
Candidates describe a multi-stage process that runs from an initial phone screen to an onsite with project-based sessions, including a three-hour project, collaborative coding with team members and a conversation with the leadership team. No round-by-round order has been reported, so confirm the sequence with your recruiter.
Focus areas
- Software Design
- HTTP
- Problem Solving
Overview
The reported questions fall into two groups. The first is web fundamentals: how HTTP works, what the request methods mean and when to use each, and which security risks matter on a web platform used by students and teachers. The second is design: serving curriculum materials to many users at once, choosing a database for the platform, and keeping a codebase maintainable as the product grows. Candidates also report topics labelled Software Design, HTTP, Problem Solving, System Design and API Fundamentals, which matches that split.
Because the fundamentals questions are short and open, depth is what separates answers. For HTTP, that means being able to follow one request from the address bar through name lookup, connection setup, TLS, the request line and headers, the status code, caching headers and connection reuse. For methods, it means safe versus idempotent versus cacheable, plus the status codes that go with each. For security, it means naming concrete attacks (cross-site scripting, cross-site request forgery, SQL injection, broken access control) and the specific control for each, applied to an educational product with teacher and student accounts.
Candidates report that the onsite includes a three-hour project in which you build a small feature, and collaborative sessions where you build small projects alongside current team members. Prepare by practising small, complete deliverables: a working endpoint or page, a few meaningful tests, readable structure, and a short explanation of the choices you made. Candidates also report that the language is not the focus, so use the one you can write fastest and be ready to justify your tool choices.
The mission matters in the conversations too. Candidates report that connecting your work to social-emotional learning and to educators and school administrators as users carries weight, as do accessibility (WCAG is listed as a nice-to-have) and your ability to explain technical ideas to product managers. Prepare two or three real project stories where a user need changed a technical decision, and rehearse explaining a concept such as caching or an API change in plain language.
Where to focus your preparation
Candidates describe the loop as a phone screen followed by deeper technical rounds and an onsite with project-based sessions. No stage-by-stage order has been reported, so the single entry below groups what to prepare.
Preparation focus
Candidates report a multi-stage process that begins with a phone screen and moves to an onsite. The onsite is described as project-based: you build a small feature in a three-hour project, and you may also code a small project alongside current team members. Candidates also mention a conversation with the leadership team and spoken technical questions on HTTP, request methods, web security, concurrent requests, database choice and maintainability. Which question appears in which stage has not been reported, so prepare every question as a spoken answer and prepare the build as a timed exercise. Ask your recruiter for the order of stages and whether you may choose the language.
What to demonstrate
- Working, readable, tested code written live in a small project
- Clear spoken explanations of HTTP, request methods and web security
- Design reasoning on concurrent load, database choice and maintainability, including the trade-offs
- Communication with collaborators while you work, including when you are stuck
How to prepare
- Practise the HTTP and request-method questions aloud until each answer follows a fixed structure, from URL to response and from method to status code
- Build one small feature end to end in a three-hour limit: an endpoint or page, tests, a short README and a short note on your choices
- Prepare a security answer that names each attack with its control, framed around teacher and student accounts
- Write two or three project stories that explain why you chose a design, and rehearse them for a non-engineer listener
Where candidates lose points
Explaining HTTP as "the client sends a request and the server answers" and stopping there
Walk through one real request in order: DNS lookup, TCP connection, TLS handshake, request line and headers, status code and response headers, then caching (Cache-Control, ETag, 304) and connection reuse. Mention what HTTP/2 changes (multiplexing, header compression) so the answer shows depth beyond HTTP/1.1.
Mixing up request methods: saying PUT and PATCH are the same, using GET for actions that change data, or being unable to say what idempotent means
Keep a table of GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS with safe, idempotent and cacheable columns. Give one example for each from a curriculum or classroom API, and explain how an idempotency key makes a retried POST safe.
Answering the security question with a list of acronyms and no link to the platform
Pair every risk with a concrete control and a platform example: output encoding and a content security policy for XSS, SameSite cookies and tokens for CSRF, parameterised queries for SQL injection, and a server-side ownership check so a teacher cannot read another school's students. Add storing as little student data as you can.
Jumping to microservices, sharding or a trendy database for the concurrent-requests and database-choice questions without sizing the problem
State assumptions first (read-heavy content, how many users at peak, how often content changes), then build up: CDN and object storage for static material, a stateless app tier behind a load balancer, a cache, a relational primary with replicas. Say what breaks first at ten times the load.
Treating the project session as a race: shipping features without tests, going silent, or staying stuck rather than asking
Write down the smallest working slice in the first minutes, build it, add tests as you go, and say your reasoning out loud. When a requirement is ambiguous, ask a clarifying question and state the assumption you chose. Finish with a short note on what you would do next.
Questions & practice
Merge partitioned event streams into one ordered feed with bounded lateness
Hard · k-way merge · watermarks · out-of-order streams
The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.
Approach
- Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
- Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
- Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
- Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.
Follow-up
- The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
- The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?
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
- 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.
- 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.
- 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.
- 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?
Archive a resource graph without breaking live references or recursing
Medium · graph traversal · topological order · tenant isolation
Resources reference other resources within a tenant; for the largest tenant the reference table holds up to 2,000,000 nodes and 8,000,000 edges. Archiving a resource must archive everything reachable from it that nothing outside the set still references, refuse when a live external referrer exists, and terminate when references form cycles, which they legitimately do. Produce the archive order and the refusal list, targeting O(V+E). Say what stops the traversal crossing a tenant boundary, and why recursion is the wrong control structure at this size.
Approach
- Load the subgraph with the tenant predicate on both endpoints of the edge, not only on the side you started from. Scoping the left table alone is the classic cross-tenant leak: one mis-entered edge then pulls another tenant's resources into the traversal and, worse, into the archive.
- Traverse iteratively with an explicit stack. A 2,000,000-node graph can hold a chain deep enough to exhaust a native stack in the low tens of thousands of frames, and that failure is a process crash rather than an error you can return.
- Treat cycles as data rather than corruption: compute strongly connected components with Tarjan in O(V+E) using its own explicit stack, then condense. The condensation is a DAG, so a topological order over it gives the archive order, and every member of a component archives in one transaction because no order within a cycle is valid.
- Decide refusals with reverse edges. A candidate is archivable only if every in-edge originates inside the candidate set, so build the transpose or count in-degrees restricted to the visited set, and emit each blocked resource with the id of the external referrer, which is the only part of the answer an operator can act on.
Follow-up
- The graph is read in one query and the archive writes a minute later. What can change in between, and how do you make the write safe?
- The candidate set is 400,000 resources. Is that one transaction, and if not, what does a half-finished archive look like to a reader?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
Stop tag and share joins from fanning out a page
Medium · join fan-out · semi-joins · many-to-many
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
How does the HTTP protocol work?
Medium · Technical Fundamentals and Domain Knowle
Approach
- Walk one request end to end: DNS resolves the host, the client opens a TCP connection (or QUIC over UDP for HTTP/3), TLS negotiates keys for HTTPS, then the client sends a request and reads the response.
- Describe the message shape: a request line (method, target, version), headers such as Host (mandatory in HTTP/1.1), Content-Type and Authorization, an optional body; the response carries a status line, headers and a body.
- Group status codes by class (2xx success, 3xx redirect, 4xx client error, 5xx server error) and give one real example of each, e.g. 201, 304, 404, 503.
- State that HTTP is stateless: each request stands alone, and sessions are rebuilt from cookies or bearer tokens. Caching rides on headers: Cache-Control, ETag with If-None-Match, which returns 304 Not Modified when nothing changed.
- Contrast versions: HTTP/1.1 keeps connections alive but serialises requests per connection; HTTP/2 multiplexes binary-framed streams on one TCP connection with HPACK header compression; HTTP/3 runs on QUIC so one lost packet no longer stalls every stream.
Follow-up
- If HTTP is stateless, how does a logged-in session survive across requests, and which cookie flags would you set?
- What does HTTP/2 multiplexing fix, and what head-of-line blocking does it still leave for HTTP/3 to remove?
- How would you make a lesson page cacheable by browsers and a CDN while still showing per-user progress?
Can you explain the difference between various request methods and their use cases?
Medium · Technical Fundamentals and Domain Knowle
Approach
- Define the two properties first: safe means no intended state change (GET, HEAD, OPTIONS); idempotent means repeating the identical request leaves the server in the same state (GET, HEAD, PUT, DELETE, OPTIONS). POST is neither, and PATCH is not guaranteed idempotent.
- Map methods to use cases: GET to read a resource, POST to create or trigger processing (return 201 with a Location header), PUT to replace a resource at a known URI, PATCH for a partial update, DELETE to remove (204 or 404 on a repeat, the state is the same either way).
- Explain why it matters: clients, proxies and crawlers retry or prefetch safe and idempotent requests, so a GET that changes data can be fired by a link prefetcher and a retried POST can create duplicates.
- Show how to make POST retry-safe: the client sends an idempotency key, the server stores the key with the first result and returns that stored result on a repeat.
- Mention the browser angle: GET, HEAD and POST with simple content types skip the CORS preflight, while PUT, PATCH, DELETE or custom headers trigger an OPTIONS preflight.
Follow-up
- When would you pick PUT over PATCH for updating a student roster, and what happens if two clients PUT at once?
- How would you stop a double-clicked submit button from creating two records?
- Why should sensitive values never go in a GET query string?
What are the common security considerations when developing web-based educational platforms?
Medium · Technical Fundamentals and Domain Knowle
Approach
- Start with access control, the most common real failure: every request checks that this user may see this object (a teacher reads only their own classes, an admin only their own school), enforced server-side by object, not by hiding links. Name IDOR as the bug this prevents.
- Cover injection and output handling: bind parameters for every SQL query, encode output by context to stop XSS, add a Content-Security-Policy, and validate uploads (type, size, storage outside the web root).
- Cover authentication and sessions: hash passwords with bcrypt or Argon2, support school single sign-on through SAML or OIDC, use MFA for admin accounts, set session cookies HttpOnly, Secure and SameSite, and add CSRF tokens for state-changing forms.
- Treat children's data as the core risk: collect the minimum, encrypt in transit (TLS with HSTS) and at rest, keep personal data out of logs, define retention and deletion, and check obligations such as COPPA and FERPA when the platform serves US schools.
- Close with operations: rate-limit login and password reset, scan dependencies for known vulnerabilities, keep audit logs of who viewed student records, and have a written incident response path.
Follow-up
- How would you test that a teacher cannot read another school's roster by changing an id in the URL?
- Where would you store a session token in the browser, and what does each cookie flag defend against?
- A library you depend on publishes a critical vulnerability on a Friday: what do you do first?
How would you design a system to handle concurrent user requests for curriculum materials?
Medium · Design and Architecture
Approach
- Clarify the load before designing: who requests materials (teachers, students), the peak shape (school-day starts, the first week of a term), file types and sizes, and whether access depends on a license or subscription check.
- Split static from dynamic: serve files from object storage through a CDN, gated by short-lived signed URLs when content is licensed; keep app servers stateless behind a load balancer so they scale horizontally.
- Put a cache in front of metadata reads (cache-aside in Redis with a TTL), use database read replicas for catalogue queries, and pool connections so a traffic spike does not exhaust the primary's connection limit.
- Handle the hot-key problem: when a popular lesson's cache entry expires, coalesce concurrent misses into one backend fetch (single-flight or a short lock) or serve stale-while-revalidate, so the database is not hit by every request at once.
- Keep writes off the read path: record progress or view events through a queue and batch them, rate-limit per client, and version asset URLs on publish so caches never serve a half-updated lesson.
Follow-up
- An author fixes a typo in a lesson that is cached at the CDN: how does the fix reach users, and how fast?
- What fails first if concurrent users grow ten times overnight, and how would you see it in metrics?
- How do you stop a shared signed URL from being passed around outside the licensed school?
What factors influence your choice of database architecture for this specific platform?
Medium · Design and Architecture
Approach
- Start from the data shape: if the platform models schools, classrooms, users, licenses and content, the entities are relational with foreign keys and transactional updates, which points to a relational database such as PostgreSQL as the default.
- Name the access patterns and consistency needs: licensing and account changes need strong consistency, while analytics and recommendations tolerate lag; read-heavy catalogue traffic scales with read replicas and caching long before sharding is needed.
- Pick a multi-tenancy model and its trade-off: a shared schema with a tenant id (cheapest, isolation depends on every query or on row-level security), schema per tenant, or database per tenant (strongest isolation, highest operational cost).
- Separate workloads by store: large files in object storage with only metadata in the database, full-text search in a search index if needed, and reporting in a warehouse so heavy queries do not slow the transactional database.
- Include privacy and operations: encryption at rest, data residency if districts require it, deletion and retention rules for student data, point-in-time recovery, and whether the team can run the system (a managed service often wins).
Follow-up
- When would a document store be a better choice than a relational database for curriculum content?
- How would you roll out a schema change on a busy table without downtime?
- How do you guarantee one school's data never appears in another school's query?
How do you ensure your code remains maintainable as the product scales?
Medium · Design and Architecture
Approach
- Anchor the answer in one real codebase you worked on: what made it hard to change, what you changed, and what got measurably easier (review time, defect rate, onboarding time).
- Describe structure: modules split by domain with narrow interfaces, dependencies pointing one way, and business logic kept apart from frameworks and I/O so it can be tested in isolation.
- Describe the safety net: a test pyramid (many fast unit tests, fewer integration tests, a handful of end-to-end tests), CI that runs tests, linting and type checks on every change, and small reviewed pull requests.
- Name the trade-offs: premature abstraction costs as much as duplication, so abstract once a pattern repeats (the rule of three); feature flags let unfinished work merge without long-lived branches; tech debt is tracked and paid down alongside features.
- Mention what keeps knowledge in the repo: short architecture decision records, a README that runs locally in one command, and logs and metrics that make behaviour visible without reading every line.
Follow-up
- How do you decide between refactoring a messy module now and shipping the feature first?
- How do you test code that calls an external service you do not control?
- Tell me about a change that broke something despite tests: what did you add afterwards?
Exports duplicate a row range about once a week
Hard · leases · clock skew · idempotency
Roughly once a week an export writes a file containing a duplicated range of rows. The affected job_run rows show attempt = 1, status = succeeded, one started_at, and a lease_owner naming a different host from the one whose logs show the job starting. Leases last 30 seconds and are heartbeated every 10 from inside the handler; lease_expires_at is computed on the worker and compared against the database's now(). Find the mechanism, and give a fix that holds even if you cannot fix the clocks.
Approach
- Start from the fact that eliminates the obvious answer. attempt = 1 means no retry was recorded, so this is not a re-run after failure; two workers ran the same row concurrently and the takeover path never touched the counter. lease_owner naming a host other than the one that started the job is the same statement from the other side.
- Enumerate the mechanisms that cause a premature takeover, then find the signal that separates them. Either the lease genuinely expired because the heartbeat did not fire, which is what happens when the heartbeat runs on the handler's own thread and the handler makes a long blocking call, or it only appeared expired because two clocks disagree, since lease_expires_at is written from the worker's clock and evaluated against the database's. The discriminator is the distribution: incidents clustered on the longest exports indict the heartbeat, incidents clustered on one host indict skew. Measure both, and measure each host's offset against the database directly.
- Read the reclaim query precisely. In PostgreSQL now() is transaction start time, not statement time, so a reclaimer holding a long transaction compares against an older timestamp than expected; clock_timestamp() is the statement-time function. This is worth ruling in or out before you redesign anything, because it changes which rows look expired.
- Remove the second clock rather than trying to synchronise it. Issue and extend the lease in the database, with lease_expires_at = now() + interval '30 seconds' in both the claim and the heartbeat, so exactly one clock is ever compared and worker skew stops mattering to this predicate.
Follow-up
- The displaced worker has already streamed half the file to object storage. What makes that side effect safe to repeat?
- You now count takeovers. What alert fires on that counter, and at what threshold?
Suggested practice checklist
Days 1 to 5 each take one reported topic (HTTP, request methods, security, concurrent load, database choice with maintainability), day 6 rehearses the project-based session, and day 7 is a full spoken run plus behavioral stories.
Day 1 — HTTP from URL to response
- Write out step by step what happens from typing a URL to seeing a page: DNS, TCP, TLS, request, response, caching, connection reuse.
- Run curl -v against a public site, and annotate the request headers, status line, and response headers such as Content-Type, Cache-Control and ETag.
- Write a short comparison of HTTP/1.1 and HTTP/2, covering multiplexing, header compression and head-of-line blocking.
Deliverable: A one-page request walkthrough and an annotated curl -v output, followed by a spoken answer to the HTTP question.
Day 2 — Request methods and API fundamentals
- Build a table of GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS with safe, idempotent and cacheable columns.
- Design the routes and status codes (200, 201, 204, 304, 400, 401, 403, 404, 409, 422, 429) for a small API that serves lessons and classroom assignments.
- Explain how an idempotency key lets a client retry a POST without creating a duplicate.
Deliverable: A method table, a route-and-status sheet for the lessons API, and a spoken answer to the request-methods question.
Day 3 — Security for a school-facing web platform
- Write a threat table for a platform with teacher and student accounts, listing XSS, CSRF, SQL injection, broken access control, session handling, TLS, CORS and rate limiting, each with its control.
- Write a vulnerable and a fixed version of two of them in your language, for example string-built SQL versus a bound parameter, and an unchecked object id versus an ownership check.
- Prepare a short answer on collecting and keeping as little student personal data as possible.
Deliverable: A threat table, two before-and-after code snippets and a spoken answer to the security question.
Day 4 — Serving curriculum to concurrent users
- Estimate peak requests from stated assumptions, then split the design into static content (object storage and CDN) and dynamic API calls.
- Draw the path of one request through load balancer, stateless app servers, cache and database, and say how a content update invalidates cached copies.
- List what breaks first at ten times the load and the fix for each, such as connection pool limits, replica lag and a cache stampede.
Deliverable: A labelled architecture diagram, a ten-times-load failure list and a spoken answer to the concurrent-requests question.
Day 5 — Database choice and maintainability
- Compare a relational schema with a document store for the same data (users, classes, lessons, progress) against query patterns, consistency needs and reporting.
- Write a maintainability answer built on a real example from your work: module boundaries, tests, code review, migrations, observability, and removing dead code.
Deliverable: A database comparison sheet, a written maintainability example and spoken answers to the database and maintainability questions.
Day 6 — Rehearse the project-based session
- Pick a small spec, such as a lessons list with filters, and build it in a single three-hour limit while narrating your choices aloud.
- Include a few tests, a README and keyboard-and-label checks against WCAG basics, and note what you would do next.
Deliverable: A working small feature with tests and a README, plus a written retrospective on what slowed you down.
Day 7 — Full spoken run and behavioral stories
- Answer all six reported questions aloud back to back, without notes, and re-record the weakest two.
- Rehearse two project stories that explain why you made a design choice, and one explanation of caching or an API change for a product manager.
Deliverable: Six recorded spoken answers, two written story outlines and a short list of questions for the recruiter.
Mission, collaboration and communication
Candidates report that the loop includes conversations about project decisions, collaboration with product managers, designers and educators, and the organization's focus on social-emotional learning, plus a session with the leadership team. No specific behavioral questions have been reported, so these prompts follow those themes. Build each answer around a real decision, why you made it, and what happened.
- Walk me through a past project and why you picked its architecture over the alternatives.
- Explain a technical idea, such as caching or a breaking API change, to a product manager with no engineering background.
- Why do you want to build software for educators and students, and what does social-emotional learning mean to you?
- Tell me about a time you were stuck during a build while someone watched. What did you say and do?
- Describe a time feedback from a designer, teacher or other non-engineer changed your technical approach, including any accessibility impact.
- Tell me about a time you explained a technical trade-off to senior leadership and how you framed it.
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
- 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.
- 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.
- 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.
- 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?
Estimate work you have never done and defend the range
Hard · estimation · backfills · expand-contract
You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.
Approach
- Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
- Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
- Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
- Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.
Follow-up
- How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
- Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?
Tell callers you do not own that their integration breaks
Medium · deprecation · compatibility · stakeholders
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?