cswork / libraryOpen practice workspace

Interview guides · American Enterprise Institute

American Enterprise Institute Software Engineer Interview Guide

American Enterprise Institute Software Engineer interview guide: reported coding and CS questions, the screen-to-Zoom loop, and a 7-day plan.

The 60-second brief

The American Enterprise Institute is a research and policy organization. Candidates describe the engineering role as the team that builds the software behind that research: internal data platforms, public-facing web applications and digital tools that present policy research in accessible, interactive forms. Reported examples include dashboards that let economists visualise large datasets and web portals where the public reads publications. Reported work also includes moving legacy databases to the cloud, tuning existing codebases and connecting third-party APIs to enrich data models.

This guide is for candidates applying to the American Enterprise Institute as a Software Engineer, including university-pipeline applicants who meet the company through a career fair. It covers the reported coding and computer-science questions, how to talk through a resume project, the screen-then-Zoom process candidates describe, and a one-week schedule. Languages named in the requirements candidates describe are Python, Java, JavaScript and C#, so pick the one you can write fastest and use it throughout.

Candidates report a first screen, either a phone call with an HR manager or an online assessment with self-introduction questions and basic coding, followed by a Zoom panel of two to three team members that runs roughly one hour. The whole process is reported to take about a month.

Focus areas

  • Coding and Data Structures
  • Computer Science and System Fundamentals
  • Software Engineer fundamentals

Overview

The reported questions are classic fundamentals: a palindrome check, two numbers that sum to a target, binary search, the middle node of a linked list, a basic expression evaluator, and the difference between a process and a thread. Candidates describe the coding level as easy-to-medium, so the preparation goal is not volume but fluency: write each one correctly without a framework, state its time and space complexity, and test it on edge cases before anyone asks.

The role description candidates see lists relational databases and efficient SQL among the must-have skills, and it names cloud platforms (AWS, Azure or GCP), front-end frameworks (React, Vue or Angular) and data pipelines as nice-to-haves. None of those appear among the reported questions, so treat them as material for your resume discussion rather than for drilling: be ready to say what you built, which trade-off you chose and what changed as a result.

Candidates describe the final round as conversational, with a panel that spends time on your resume, behavioral questions and core computer science. That makes spoken clarity part of the preparation. Practise narrating while you code, naming the invariant of a loop, and explaining a technical choice to someone who is not an engineer, because the role is described as working with researchers, policy experts and communications staff.

The reported material is narrow, which means depth on each item is what separates answers. For every coding question, know the brute-force version, the improved version, the edge cases and one natural extension. For the process-versus-thread question, go beyond the textbook definition to memory, communication, failure isolation and a concrete example of a race.

Where to focus your preparation

Candidates describe one early screen and one final video stage; the single entry below covers how to prepare across both, since no further stage order is reported.

Preparation focus

Candidates report that the entry point depends on how you apply. Through a career fair, direct application or recruiter outreach, the first step is either a brief phone call with an HR manager or an online assessment that mixes self-introduction questions with foundational coding. The palindrome, two-sum, binary search, linked-list middle and expression questions are reported for the assessment or the early technical screen. Passing it leads to a Zoom panel of two to three team members, reported at roughly one hour, covering your resume, behavioral questions and core computer science such as the process-versus-thread question. Candidates describe the tone as relaxed and conversational and note that steps can vary by location and team, so ask your recruiter whether you will write code on screen. Prepare in two phases: code fluency first, then resume and explanation.

What to demonstrate

  • Working, readable code for easy-to-medium problems on arrays, strings, hash maps, linked lists and simple parsing
  • Clear statements of time and space complexity for the code you write
  • Ability to explain the reasons behind decisions in past projects, not only what you built
  • Core computer-science understanding, shown by the process-versus-thread question

How to prepare

  • Write the five coding questions from scratch in your chosen language, then say aloud the complexity and two edge cases for each
  • Prepare a two-minute self-introduction and a deeper walkthrough of two resume projects, covering the problem, your decision, the alternative you rejected and the result
  • Write a one-page answer on processes versus threads covering memory, communication, failure isolation and a race example
  • Ask the recruiter which format applies to you, whether code is written on screen, and what to expect in the panel

Where candidates lose points

Writing the first thing that works on the palindrome or two-sum question and stopping, with no complexity statement

After every solution say the time and space complexity aloud and name the alternative you rejected, such as the reverse-and-compare copy or the nested loops. Candidates describe the coding bar as easy-to-medium, so the difference is how clearly you reason about it.

Off-by-one errors in binary search or the linked-list middle, such as lo = mid that never terminates or a fast pointer that dereferences null

State the loop invariant before coding and trace a one-element and a two-element input by hand. For the linked list, check the empty, single-node and even-length cases and decide which middle you return.

Giving a one-line definition of process versus thread and nothing more

Cover memory sharing, creation and switching cost, communication, failure isolation and one concrete race, then relate it to the language you use. A panel that discusses core computer science is easier to hold with a worked example than with a definition.

Describing resume projects by what you built but not why you chose that design

For each project, prepare the problem, the options you compared, the trade-off you accepted and a result you can state. Candidates report the panel spends time on your past projects, so the reasoning behind each decision matters as much as the outcome.

Coding silently, or pretending to know an answer when stuck

Narrate your plan while you write, and when you hit a gap say what you do not know and how you would find out. Candidates describe the final round as conversational, and spoken reasoning is part of the answer.

Questions & practice

Write a function to check if a given string is a palindrome.

Medium · Coding and Data Structures

Approach

  1. Clarify the definition first: is the check case-sensitive, and should spaces and punctuation be ignored ("A man, a plan, a canal: Panama")? Note that an empty string and a single character both count as palindromes.
  2. Use two pointers: i at index 0, j at len(s) - 1. Compare s[i] and s[j], return False on the first mismatch, otherwise move both inward until they meet. This takes O(n) time and O(1) extra space.
  3. If non-alphanumeric characters are ignored, skip them inside the loop (while i < j and not s[i].isalnum(): i += 1, and the same for j) and compare lowercased characters. This avoids building a cleaned copy of the string.
  4. Mention the one-liner s == s[::-1] as the baseline. It is also O(n) time but allocates an O(n) reversed copy, and with cleaning it allocates two copies. That is why the two-pointer version is the better answer.
  5. Test aloud: "", "a", "ab", "aba", "abba", a string made only of punctuation, and mixed case. Even and odd lengths both stop cleanly because the loop condition is i < j.

Follow-up

  • Can the string become a palindrome after deleting at most one character? (At the first mismatch, check whether s[i+1..j] or s[i..j-1] is a palindrome. The whole check stays O(n).)
  • How would you find the longest palindromic substring? (Expand around each of the 2n - 1 centres for O(n^2) time and O(1) space, or use Manacher's algorithm for O(n).)
  • What changes for Unicode input, such as case folding with str.casefold() or characters made of combining code points?

Given an array of integers, return the indices of the two numbers that add up to a specific target.

Medium · Coding and Data Structures

Approach

  1. State the brute force first: check every pair (i, j) with i < j, which is O(n^2) time and O(1) space. Then ask whether exactly one answer exists, whether the same element can be used twice, and whether the array is sorted.
  2. Go to one pass with a hash map from value to index. For each i, compute need = target - nums[i]. If need is already in the map, return [map[need], i]. Otherwise store nums[i] -> i. Expected time is O(n) and space is O(n).
  3. Check the map before inserting the current element. This stops an element from pairing with itself (nums = [3], target 6) and still handles duplicates correctly (nums = [3, 3], target 6 returns [0, 1]).
  4. Compare this with sorting plus two pointers. Sorting costs O(n log n) and loses the original positions, so you would have to sort (value, index) pairs. It only wins when the input is already sorted, where it needs O(1) extra space.
  5. Cover the edge cases: no valid pair (return an empty result or raise, and say which), negative numbers and zero, and in Java or C# the chance that target - nums[i] overflows int, which a long avoids.

Follow-up

  • The array is now sorted. Can you do it in O(1) extra space? (Move a left and right pointer inward based on whether the sum is too small or too large.)
  • Return every unique pair that sums to the target, without duplicate pairs.
  • Extend it to three numbers that sum to zero. (Sort, then run a two-pointer scan for each anchor: O(n^2).)

Implement a binary search algorithm on a sorted array.

Medium · Coding and Data Structures

Approach

  1. Set lo = 0 and hi = len(a) - 1, and loop while lo <= hi. Compute mid = lo + (hi - lo) // 2, return mid on a match, set lo = mid + 1 if a[mid] < target, else hi = mid - 1. Return -1 when the range is empty.
  2. State the invariant aloud: if the target exists, it is always inside a[lo..hi]. Each branch drops mid itself, so the range shrinks every iteration and the loop always ends. O(log n) time and O(1) space iteratively; a recursive version uses O(log n) stack.
  3. Explain why lo + (hi - lo) / 2 is used: in fixed-width integer languages (Java, C#, C++), (lo + hi) / 2 can overflow for large indices. Python ints do not overflow, but the habit is worth showing.
  4. Know the half-open lower-bound form: set lo = 0 and hi = n, loop while lo < hi, set hi = mid when a[mid] >= target, else lo = mid + 1. It returns the first index where the target could be inserted, which is what Python's bisect_left does.
  5. Test the empty array, a single element, a target below the first or above the last element, and duplicates. Say that the classic version returns any matching index, not necessarily the first one.

Follow-up

  • Return the first and last position of a target that appears several times. (Use two lower-bound searches, for target and target + 1, or bisect_left and bisect_right.)
  • Search a sorted array that has been rotated at an unknown pivot. (At each step one half is sorted. Check whether the target lies in that half.)
  • Use binary search on an answer space, for example the integer square root of n or the minimum capacity that finishes a job within a deadline.

How do you find the middle element of a linked list in a single pass?

Medium · Coding and Data Structures

Approach

  1. Use slow and fast pointers. Both start at head; slow moves one node and fast moves two, while fast and fast.next are not null. When fast reaches the end, slow is at the middle. This is one pass, O(n) time and O(1) space.
  2. Settle the even-length case before coding. The loop above returns the second of the two middle nodes (node 3 in 1-2-3-4). To get the first middle, loop while fast.next and fast.next.next are not null instead.
  3. Give the two-pass baseline too: count the nodes, then walk n // 2 steps. It has the same O(n) complexity but reads the list twice, and that is why the question asks for a single pass.
  4. Cover the edge cases: an empty list (return null), a single node (return head), and two nodes, where the loop condition decides which middle you get. Trace a 1-node, 2-node and 5-node list aloud.

Follow-up

  • Delete the middle node. (Track the node before slow, or start fast one step ahead, then relink around the middle.)
  • Check whether a linked list is a palindrome in O(1) extra space. (Find the middle, reverse the second half, compare the halves, and optionally restore the list.)
  • How would the same two-pointer idea detect a cycle and find where it starts? (Floyd's algorithm: after the pointers meet, reset one to head and advance both one step at a time.)

Write a program to evaluate a basic mathematical expression given as a string.

Medium · Coding and Data Structures

Approach

  1. Fix the grammar before coding. Which operators (+ - * /)? Are there parentheses, spaces, unary minus or multi-digit numbers? Should division truncate toward zero or floor? Rule out eval() and say why: it runs arbitrary code from untrusted input.
  2. Without parentheses, use one pass and a stack. Parse each number digit by digit and remember the last operator. When you reach an operator or the end, push num for +, push -num for -, replace the top with top * num for *, or with top / num truncated toward zero for /. Return the sum of the stack: O(n) time, O(n) space. Keeping only the last term instead gets this to O(1).
  3. Watch division semantics in Python: // floors toward negative infinity (-7 // 2 == -4), but most specs want truncation (-3). Use int(a / b) for moderate values, or compute abs(a) // abs(b) and apply the sign so that large integers lose no precision.
  4. With parentheses, use recursive descent. expr := term (('+' | '-') term), term := factor (('' | '/') factor)*, factor := number | '(' expr ')' | '-' factor. Precedence and left associativity come from the grammar itself. The alternative is Dijkstra's shunting-yard algorithm into postfix, then a stack evaluation of the postfix. Both run in O(n).
  5. Test multi-digit numbers, " 3 + 5 / 2 ", "2-3*4", nested parentheses, unary minus such as "-(2+3)", and invalid input such as a trailing operator, unbalanced parentheses or division by zero. Decide whether each one raises an error or returns a value.

Follow-up

  • Add a right-associative exponent operator. (2^3^2 must be 2^9; put it in its own grammar level and recurse on the right side.)
  • Support variables such as "x * 2 + y" from a dictionary, and report the position of a syntax error.
  • Why parse into a tree first instead of evaluating directly, and what would that tree let you do later?

What is the difference between a process and a thread?

Medium · Computer Science and System Fundamentals

Approach

  1. Start with ownership. A process is a running program with its own virtual address space and OS resources, such as open file descriptors. A thread is a unit of execution inside a process. Threads in the same process share the heap, globals and open files, but each has its own stack, registers, program counter and thread-local storage.
  2. Cover the cost trade-off. Creating a thread and switching between threads of the same process is cheaper than doing it with processes, because there is no address-space switch. Processes give isolation: a segfault or unhandled crash in one thread can take down the whole process, while a separate process fails alone.
  3. Explain how they communicate. Threads talk through shared memory, which is fast but needs synchronisation (mutexes, locks, atomics) to avoid race conditions, and careful lock ordering to avoid deadlocks. Processes use IPC: pipes, sockets, message queues or explicitly mapped shared memory.
  4. Tie it to the language you will code in. In standard CPython the GIL lets only one thread run Python bytecode at a time, so CPU-bound work uses multiprocessing and I/O-bound work uses threads or asyncio. Java and C# threads run truly in parallel on multiple cores. In Node.js, JavaScript code runs on a single event loop unless you use worker threads.
  5. Close with a concrete example of when to choose each: a thread pool for many concurrent network or database calls, and separate worker processes for CPU-heavy data processing or for work that must be isolated from crashes.

Follow-up

  • What is a race condition? Show one with two threads incrementing a shared counter, and fix it.
  • What happens on fork() in a multithreaded process? (The child gets a copy-on-write copy of the address space, but only the calling thread exists in the child, so locks held by other threads can stay locked forever.)
  • What is the difference between concurrency and parallelism, and where does async I/O fit?

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?

Design the bulk write endpoint a migration script retries blindly

Medium · batch api · partial failure · idempotency

A customer's migration script pushes 2 million resources through POST /v1/resources:batch, up to 500 items per call, and retries any call that errors or times out. Within a call some items fail validation, some collide with rows that already exist, and some succeed. Specify the request and response shape, whether a batch is atomic or per-item, how idempotency works for the call and for each item, the status code for a mixed outcome, the size and item-count limits with their error codes, and exactly what the script does after a timeout mid-batch.

Approach

  1. Choose atomicity deliberately and price it. All-or-nothing means one transaction holding locks for the whole batch on a primary already absorbing about 1.2k writes/second, which bounds batch size by lock duration, and it turns one bad row into 499 rejections the script must resend. Per-item partial success is the right default for a migration, and the contract's job is then to make a partial outcome impossible to miss.
  2. Require a client-supplied id on every item and echo it in every result. Deriving a per-item key from the array index breaks the first time the script resends a batch with the failures removed: the indices shift, previously-succeeded items acquire new keys, and they are created a second time.
  3. Key the effects at two levels. The call's Idempotency-Key covers an exact resend of the same bytes; per-item keys of (tenant_id, client_item_id) make a partially-applied batch safe to resend whole. Resending an identical batch must reproduce the same per-item results, not 500 conflicts the script has to interpret.
  4. Answer a mixed outcome with one status plus per-item detail: 200, or 207 borrowed from WebDAV if you prefer it - document whichever you pick - carrying an array of client_item_id, per-item status, and either resource_id or an error code from the same taxonomy the single-item endpoint uses. Reserve 4xx for the request as a whole: unparseable body, too many items, payload over the limit (413). Put a failed count at the top level so that even a script checking only the cheapest thing cannot conclude success while rows were dropped.

Follow-up

  • At 500 items per call, what is the wall-clock time for 2 million rows, and what pacing do you publish so the migration does not become an incident?
  • One item in every batch fails with the same code. How does the script discover that without a human reading logs?

Make the resource write endpoint safe for retrying clients

Easy · idempotency · unique constraints · retries

The edge API accepts POST /v1/resources at 1.2k writes/second peak under a 400 ms p99 budget, and clients retry on timeout. idempotency_key has PRIMARY KEY (tenant_id, idempotency_key) plus request_fingerprint CHAR(64), state in ('in_flight','succeeded','failed'), response_status, response_body, resource_id, locked_until and expires_at. Specify the exact sequence the handler runs: what the second request does when it arrives while the first is still executing, what a retry carrying a different body receives, what happens when a process dies mid-request, and how the table is kept from growing without bound.

Approach

  1. Start from the race rather than the happy path. Insert the key row first, in its own short transaction, with state 'in_flight' and locked_until set to now() plus the request deadline. The unique constraint on (tenant_id, idempotency_key) is the arbitration mechanism: exactly one caller commits the insert, and every other caller takes a unique violation (SQLSTATE 23505) and becomes a follower. Reading the table and then inserting is a check-then-act race that both callers pass, and they pass it most often under the load that generates the retries.
  2. Give the follower a branch for every state it can observe, with no fall-through to doing the work: fingerprint mismatch means the key is being reused for a different request, so reject with 422 and never replay the stored response; 'succeeded' replays response_status and response_body verbatim; 'failed' allows a fresh attempt; 'in_flight' with locked_until in the future is answered with 409 and a retry hint, or a short bounded wait, because the only alternative is executing the effect twice.
  3. Commit the effect and the record of the effect together. The resource insert, its resource_revision row, its outbox_event row and the transition of the key row to 'succeeded' all happen in one transaction, so no crash can leave the work done and the key still 'in_flight'. The earlier insert is deliberately a separate transaction: it has to be visible to a concurrent caller before the work starts, which an uncommitted row is not.
  4. Close the wedged-key path. A process that dies after the insert leaves a row that would otherwise block that key forever, which is what locked_until exists for. Reclaim with a single conditional statement, UPDATE ... SET state='in_flight', locked_until=now()+interval WHERE ... AND state='in_flight' AND locked_until < now(), and treat a zero rowcount as losing the reclaim. Two statements that read and then update reintroduce the original race one layer down.

Follow-up

  • The stored response body averages 200 KB and this table is now the largest in the database. What do you store instead, and what does a replay return once the body has been pruned?
  • A client library generates a fresh idempotency key on every attempt. What breaks, which layer should have caught it, and does the server have any defence?

p99 jumped on one listing filter while p50 stayed flat

Medium · query planning · composite index · percentiles

After a release that added an owner_user_id filter to the resource listing, p99 rose from 90 ms to 1.9 s while p50 stayed at 40 ms. Traffic and row counts are unchanged. resource carries the index (tenant_id, status, updated_at DESC, resource_id DESC). The new query filters tenant_id and owner_user_id, orders by updated_at DESC, resource_id DESC, and takes 20 rows. On PostgreSQL, explain the shape of the regression, prove it from a query plan, and give the index you would add.

Approach

  1. Start from the shape. A flat p50 with a moved p99 means a subset of requests changed cost, not all of them, so the first job is naming the subset. Bucket the endpoint's latency by the tenant's row count; the natural hypothesis is that large tenants are a small share of requests and all of the tail.
  2. Get the plan for the new query on a large tenant with EXPLAIN (ANALYZE, BUFFERS). Expect an index scan over the tenant's range, a filter discarding most of it, then a Sort feeding the Limit, possibly reporting Sort Method: external merge Disk. Read actual rows on the scan node, not estimated.
  3. Explain why the existing index cannot serve it. A composite B-tree is seekable only as a left prefix, and with no equality predicate on status the scan cannot treat updated_at as an ordering, because rows in the tenant's range are ordered by status first. Everything matching must be read and sorted before LIMIT 20 can apply, so a tenant with 400,000 rows pays 400,000 rows to return 20.
  4. Add (tenant_id, owner_user_id, updated_at DESC, resource_id DESC). Equality on the first two columns leaves the index ordered by updated_at within that pair, so the plan becomes an index scan that stops after 20 rows with no Sort node. PostgreSQL can scan a B-tree backwards, so the DESC markers matter only if the two sort columns ever disagree in direction; keeping them explicit documents the order the keyset cursor depends on.

Follow-up

  • The endpoint paginates with OFFSET. What does page 500 cost with your index, and what does the keyset version cost?
  • How would you have caught this before release, given that a 10,000-row seed database produces the same plan shape at an unnoticeable cost?

Suggested practice checklist

The week front-loads the five reported coding questions, adds the process-versus-thread question and SQL, then turns to resume stories and a spoken mock. Days follow the order candidates describe: assessment fundamentals first, then the Zoom conversation.

Day 1 — Confirm the format and set up your toolkit

  • Pick one language from Python, Java, JavaScript or C# and write a short sheet of its standard operations on arrays, strings, dictionaries or maps, and linked nodes.
  • Email your recruiter to ask whether you face an online assessment or a phone screen first, and whether code is written on screen in the Zoom round.
  • Write a two-minute self-introduction, since the assessment is reported to include self-introduction questions.

Deliverable: A language cheat sheet, a sent recruiter email with your format questions, and a written self-introduction.

Day 2 — Strings, pointers and hash maps

  • Solve the palindrome check with two pointers, then add the variant that ignores punctuation and case, without building a cleaned copy.
  • Solve two-sum with brute force first, then the one-pass hash map; test with [3, 3] and target 6, negatives, and no valid pair.
  • Say the complexity and one extension for each aloud, for example longest palindromic substring and three-sum.

Deliverable: Two working solutions in your language, each with a test list and a spoken complexity statement.

Day 3 — Binary search and linked lists

  • Write binary search with an inclusive range and the safe midpoint; test the empty, one-element, first, last and missing cases.
  • Extend it to find the first occurrence of a duplicated value.
  • Build a linked-list node class, then find the middle in one pass with slow and fast pointers, and decide aloud which middle you return for even lengths.

Deliverable: A binary search with a lower-bound variant and a middle-node function, plus test cases for each edge listed.

Day 4 — Expression evaluator

  • Write the evaluator for + - * / with spaces and multi-digit numbers, using the stack of terms approach.
  • Add parentheses by recursion, then add unary minus.
  • Write ten test expressions including precedence, negative division, trailing numbers and division by zero, and run them.

Deliverable: An evaluator that passes your ten-expression test list, with a note on how you handle integer division.

Day 5 — Process versus thread, and SQL

  • Write a one-page answer on processes versus threads covering memory, switching cost, communication, failure isolation, and a lost-update race with its fix.
  • Work through the SQL practice question on why a filter ignores an index, since the role lists relational databases and efficient SQL.
  • Work through the keyset-paging practice question, and write a sentence on when you would choose it over offset paging.

Deliverable: A one-page process-versus-thread answer, plus a written explanation of the two SQL exercises in your own words.

Day 6 — Resume deep-dive and plain-language explanation

  • Pick two projects and write for each the problem, options you compared, the decision, the trade-off and the result.
  • Explain one technical decision from a project in two minutes as if to a researcher with no engineering background.
  • Rehearse the practice prompts on arguing against a design and narrating an outage you owned, using your own real examples.

Deliverable: Two written project walkthroughs and one recorded plain-language explanation.

Day 7 — Spoken mock for the Zoom panel

  • Share your screen and solve two-sum and the linked-list middle while narrating; have a friend or a recording play the panel.
  • Answer the process-versus-thread question out loud, then walk through one resume project.
  • Write two questions to ask the panel about the team's current technical work and how engineering supports the research.

Deliverable: A recorded mock covering two coding questions, the process-versus-thread answer and a project walkthrough, plus two questions to ask.

Resume deep-dive and communication

Candidates describe the final panel as a discussion of your resume, behavioral questions and core computer science, and the first screen as including self-introduction questions. For this role that means explaining past decisions and trade-offs clearly, working with non-technical colleagues, and being honest about gaps. No specific behavioral question titles are reported, so the prompts below are rehearsal topics built from those themes.

  1. Introduce yourself and walk through the project on your resume you are proudest of.
  2. Walk me through a technical decision on one of your projects: what options you compared, why you chose one, and what you would change.
  3. Describe a time you explained a technical concept or constraint to someone who was not an engineer, and how you knew they understood.
  4. Tell me about a time you did not know the answer to a technical problem. What did you say, and how did you find a way forward?
  5. Describe a time you had to take ownership of a problem and see it through, for example a production issue or a database change.

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?

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

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

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?