Articles
Ashby Pair Programming Interview: Navigate an Existing Codebase and Explain Your Changes
Prepare for Ashby pair programming by tracing a behavior through an existing codebase, making a focused change, testing it, and explaining the diff.
An unfamiliar repository changes the shape of a coding interview. The useful first move is to find where a user action enters the code, where the state changes, and which test would show a repair. For Ashby pair programming, rehearse reading a path through an existing application while explaining the next check you will make. This is different from memorizing a solution you wrote at home.
This guide separates one current Ashby role description, an unverified candidate account, and original practice exercises. The role and invitation you receive govern the actual format. To warm up on existing-code changes, Debug and refactor a legacy module is adjacent practice, not an Ashby question prediction.
Confirm the role-specific pairing format
Official role description: The Staff Product Engineer – Americas posting, checked in September 2026, describes a one-hour live technical screen pairing in Ashby's actual codebase. It says there are no LeetCode or whiteboard exercises for that role and names other conversations on product thinking, technical design, and infrastructure. This is specific to that posting; it is not a universal Ashby engineering loop. Confirm that the position is still active, which role and level you are interviewing for, your screen length, environment, and any preparation material sent to you.
The phrase “actual codebase” should change your preparation. The bar is not merely writing a correct isolated function. You may need to find a relevant file, infer conventions from neighboring code, use a test or runtime symptom, and make a small change without breaking the surrounding behavior. Since the repository is unfamiliar, explain how you reduce uncertainty. Do not pretend to know Ashby's private implementation or claim that a particular React, GraphQL, or database task will appear.
An aggregated candidate account describes a Senior Product Engineer candidate struggling to navigate an unfamiliar repository while talking during pair coding. Its timing, provenance, and exact role match cannot be independently established here. Treat it as one person's account of the cognitive load, not as proof of a fixed assignment or typical candidate outcome. The useful preparation response is to practice navigation and concise communication together.
Before the call, ask about the practical setup: whether you will share a screen or use a hosted environment, which editor and runtime are available, whether pre-reading is allowed, and how tests can be run. Check the invitation's AI and external-resource policy rather than assuming one. If setup fails, describe the exact blocker and ask for the interviewer's preferred fallback; debugging a tool problem silently wastes the pairing time.
Trace one behavior through an unfamiliar codebase
Use a separate, original mini-repository for practice. Imagine a support-ticket application with a list page, an API route, a service function, and a data fixture. A ticket can be open, snoozed, or closed. The bug report says that after a user snoozes a ticket, the list shows it as open until the page refreshes. You are not given the cause. Your task is to locate the change path and prove where visible state diverges from stored state.
Start from the user action rather than searching every file for “snoozed.” Identify the button handler and the request it sends. Follow the route that receives the request, the service that updates the ticket, and the response shape sent back. Then inspect the client code that applies the response. A diagram in your notes can be as small as button → mutation → route → service → response → list state. If a file name differs, follow the behavior rather than forcing the diagram onto the project.
Use a navigation ledger:
| Observation | Working hypothesis | Next check |
|---|---|---|
| The click sends a mutation. | The UI handler exists. | Inspect payload and response in a test or network trace. |
Service saves snoozed. | Persistence path may be correct. | Inspect returned ticket shape. |
Response has snoozed, list stays open. | Client cache or state update may be stale. | Find reducer, query invalidation, or list update. |
Refresh displays snoozed. | Stored state likely changed. | Add a focused test for the immediate UI transition. |
This table is an editorial reasoning aid, not a disclosed Ashby prompt. The observations are hypothetical; do not announce the client as the cause before checking the response. The “refresh fixes it” clue narrows the search, but another possibility is that the list endpoint itself returns stale data briefly. Keep competing explanations alive until one observation separates them.
Search is useful once you know what you seek. Look for the visible label, request path, type name, or existing test. Read callers before rewriting a service. In a TypeScript application, types can reveal the intended state shape, but a type does not prove a runtime response is correct. In a React component, setState may update a local view while a query cache still holds an older record. Inspect the actual ownership path in your rehearsal instead of reciting framework trivia.
Make the smallest useful change
When you identify the defect, state the desired behavior in one sentence: “After the snooze mutation succeeds, the current list should show the returned ticket status without requiring a refresh.” Then choose the smallest correct owner for the update. It might be a normalized cache write, query invalidation, or a direct state update, depending on the existing architecture. Do not add a second source of truth merely because it is easy to patch the screen.
Before editing, inspect an adjacent feature that makes a similar mutation. Reusing its established pattern often reduces risk, but copy the reason, not the surface syntax. If the neighboring code invalidates a query after a successful response, ask whether the current task needs immediate feedback while refetching. If it uses an optimistic update, determine what happens on failure. These are concrete questions to discuss with a pair partner.
Keep your diff narrow enough to explain line by line. A broad refactor can create more uncertainty than the bug it fixes. If you see unrelated problems, note them and return to the requested behavior. Ask the interviewer whether they want a follow-up after the core change is proved. The objective is not to maximize code churn; it is to show a reliable decision under incomplete context.
For the ticket rehearsal, write one focused test that starts with an open ticket, performs the snooze action, resolves the mutation with a snoozed ticket, and checks the immediate list state. Add the failure case if the code attempts an optimistic update: a rejected mutation should not leave a false snoozed status. Test the behavior through the same entry path a user takes when practical. A service-only test would miss a stale client cache.
If you cannot run the full suite in the pairing environment, run the smallest relevant test and report exactly what it checked. A failure in an unrelated test should be distinguished from a failure introduced by your diff. Use git diff to inspect accidental formatting or generated-file changes before presenting the patch. A concise summary—“one state update changed; focused test passes; full suite not run”—is more credible than a claim of comprehensive verification.
Explain your reasoning while working together
Pairing is collaborative, but continuous narration can drown the useful signals. At each decision point, say what you know, what you suspect, and what check will decide. For example: “The mutation returns snoozed, but the list remains open. I will inspect how the list cache is updated after this response.” Then spend a moment reading. Silence while tracing code is normal; announce when your plan changes.
Ask questions that alter the implementation: “Should the UI update before the server confirms?” or “Does the list use the same query cache as the detail page?” Avoid asking for every next keystroke. When the interviewer offers a hint, restate the constraint it adds and test it against the code. A strong response to feedback is not just agreeing; it is adjusting the next check or patch accordingly.
Use the original ledger to avoid overconfidence. A fact is something you observed in the repo or test. A hypothesis is an explanation for that fact. The next check is the smallest action that could disprove it. This pattern helps when the codebase is large and you have to speak before you understand the whole system. It also makes the collaboration legible to someone who did not write the files.
If you get stuck, give a bounded status: “I found the route and service; I have not yet found where list state is replaced. I am looking at the query hook next.” That is more useful than silently opening unrelated files. If an interviewer proposes a different path, compare both against the desired behavior. The goal is shared progress and accurate reasoning, not defending your first guess.
Prepare a short diff walkthrough
At the end of the rehearsal, explain the patch in this order: symptom, reproduction, root cause with evidence, changed behavior, test, and remaining limit. Keep it anchored to files and output. For example, “The server returned a snoozed ticket, but the list retained its old cache entry. The change updates the owning cache from the confirmed response. A UI test checks the list before refresh; the rejection path leaves the old status unchanged.” That is an example of a defensible answer, not a report of an Ashby assignment.
A reviewer might ask why you did not refetch every list. Explain the cost and consistency tradeoff. A targeted update can feel immediate but risks missing another view of the same ticket; invalidation centralizes refresh but may introduce a brief stale state or extra request. Which one fits depends on the existing code and product contract. State the reason grounded in the repository, not a universal rule.
Rehearse once with another person who plays the interviewer and asks you to justify an assumption. If alone, record a short spoken walkthrough while you navigate an unfamiliar open-source repository, then check where you used phrases like “probably fine” without evidence. Choose a repository you can legally inspect and modify locally; do not seek private Ashby code or candidate task answers.
Here the evidence is the code path, test, and diff you build in the session.