AI Round
Build a frontend or backend feature with AI through small plans, critical review, focused implementation, and verified iterations.
An AI-assisted feature interview asks you to build a frontend or backend feature while keeping ownership of the result. You decide what the feature must do, inspect the repository, review the AI's plan, and verify each change. Generating a large patch says little about whether you understand it.
Use a repeated cycle: plan → review → implement → check → review again. The next prompt should respond to what you learned from the repository, a diff, or an actual test. The worked exercise below is a preparation example, not a particular employer's interview script.
Agree on one feature and its boundaries
The task is a support-ticket list with a status filter and pagination. Choose the frontend or backend scope with the interviewer. You do not need to build both to demonstrate the workflow.
| Backend implementation | Frontend implementation |
|---|---|
Add GET /tickets using existing authentication and ticket-read authorization. | Use the existing endpoint and shared request client. |
| Derive tenant identity from the verified session; validate filter and page. | Label the status selector; reset to page one when it changes. |
| Return at most 20 tickets, ordered by creation time and a unique ID. | Cover loading, empty, error, retry, and final-page states. |
| Preserve the application's error contract; never expose database exceptions. | Prevent stale responses replacing a newer selection; clean up or cancel obsolete requests. |
Agree on details before prompting: status is absent, open, or closed; page defaults to one; invalid input returns 400; an empty result is a successful empty list. Unauthenticated requests return 401, and authenticated users without read permission get the existing 403 response. For the frontend, keyboard access and an announced loading or error state belong in acceptance criteria.
Explore before proposing architecture. Read repository instructions, package scripts, a neighboring route or screen, authentication, data access, and one relevant test. Follow the request from entry point to storage or rendering. Find the actual commands and framework versions. Ask AI to locate candidate files, then open them yourself: a confident filename is not evidence that the file exists.
The example proceeds with the backend in a Node.js service using PostgreSQL. Authentication, authorization, and HTTP error mapping already exist. The query builder below uses plain JavaScript and Node's built-in test runner; it does not require a frontend, new framework, or database migration.
Review the plan before requesting code
Give the assistant a bounded request after inspecting the relevant files:
Add status filtering and pagination to the existing ticket-list route.
Use the current auth, repository, and error-handling patterns.
First propose a small plan: files, behavior, tests, assumptions.
Do not edit yet. Identify where tenant identity comes from.
Keep the first patch to one request-to-response slice.Suppose the proposed plan adds tenantId to the query string, builds a new authentication helper, and sorts only by created_at. Reject those assumptions explicitly:
The session already establishes the tenant and the route has a read-permission guard. Reuse them. A caller must not select another tenant through query input. Equal timestamps also need a deterministic tie breaker. Revise the plan before editing.
A revised, inspectable plan is short:
- Keep the existing guards; pass their trusted tenant ID into the repository.
- Validate status and page; parameterize the tenant and filter; order by
created_at DESC, id DESC. - Map validation errors through the existing
400handler. Keep database failures on the standard server-error path. - Test malformed input, tenant isolation, permission denial, empty results, and page boundaries.
Review scope too. Offset pagination is an acceptable agreed choice for this small list, with pages capped at 500. Unique ordering makes ties deterministic; it does not prevent concurrent inserts from shifting later pages. If the requirement is uninterrupted traversal while new tickets arrive, revise the plan toward cursor pagination before coding. Do not quietly promise snapshot consistency.
Build a slice, then challenge it
Ask for the route-to-repository slice and its focused tests. Read the diff before running it. Check that authorization still runs, tenant scoping reaches the SQL predicate, and the patch has not added unrelated refactors or weakened tests.
This is the query-building part, saved as ticket-query.mjs in the example. Assume status is a text column and id is unique; use the actual schema types in a real repository.
export function buildTicketQuery(tenantId, params) {
const status = params.get("status");
if (status !== null && !["open", "closed"].includes(status)) {
throw new RangeError("Invalid status");
}
const rawPage = params.get("page") ?? "1";
const page = Number(rawPage);
if (!/^[1-9]\d*$/.test(rawPage) ||
!Number.isSafeInteger(page) || page > 500) {
throw new RangeError("Invalid page");
}
return {
text: `SELECT id, subject, status, created_at
FROM tickets
WHERE tenant_id = $1
AND ($2::text IS NULL OR status = $2)
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET $3`,
values: [tenantId, status, (page - 1) * 20],
};
}The route must pass the verified tenant ID, never params.get("tenantId"). This helper does not authenticate callers or establish permissions. Its RangeErrors need explicit mapping to the agreed validation response; catching every exception and returning an empty list would hide outages as success.
Save these focused checks as ticket-query.test.mjs beside the helper:
import test from "node:test";
import assert from "node:assert/strict";
import { buildTicketQuery } from "./ticket-query.mjs";
const query = (input) =>
buildTicketQuery("tenant-a", new URLSearchParams(input));
test("defaults and last accepted page", () => {
assert.deepEqual(query("").values, ["tenant-a", null, 0]);
assert.equal(query("page=500").values[2], 9980);
});
test("rejects malformed and out-of-range input", () => {
for (const page of ["0", "-1", "2junk", "1.5", "501", ""]) {
assert.throws(() => query(`page=${page}`), RangeError);
}
assert.throws(() => query("status=deleted"), RangeError);
});
test("binds filter and retains the trusted tenant", () => {
const result = query("status=closed&page=2&tenantId=tenant-b");
assert.deepEqual(result.values, ["tenant-a", "closed", 20]);
assert.match(result.text, /ORDER BY created_at DESC, id DESC/);
});Run node --test ticket-query.test.mjs in that example directory. In the interview repository, also run its documented route tests and relevant lint or type checks. Inspect results yourself. These unit assertions establish validation and query construction; they do not establish database isolation or HTTP behavior.
For those boundaries, use route tests with two tenants containing matching tickets, a user lacking read permission, and rows sharing timestamps. Assert returned IDs and status codes. Request consecutive pages and verify the agreed order without duplicates on an unchanged fixture. Inject a repository failure and check that it produces the standard server error rather than 200 [] or a leaked SQL message.
Now repeat the cycle. If a first patch omitted the ID ordering, give evidence-grounded feedback:
Review: two fixture tickets share created_at, so timestamp-only
ordering does not define their relative position. Plan a minimal fix
and a two-page integration regression before editing. Keep auth intact.Review the revised plan, inspect the patch, rerun the failing check and affected tests, then reread the final diff. A SQL-string assertion alone cannot prove actual page membership. Likewise, on the frontend, test a slow first filter request followed by a fast second one: the old response must not overwrite the current selection, and cancellation must not become a visible error.
Hand off with evidence and limits
Explain one decision the AI got wrong, how you detected it, and what changed. Be ready to trace a request and modify the feature without asking the assistant to explain every line. An AI review can offer another hypothesis; its approval does not validate the implementation.
Finish with the behavior delivered, checks actually executed, and remaining limits. Report a blocked database test as blocked and name the missing fixture or service. Do not turn a suggested command into a passing result.
A useful handoff states the tenant source and authorization path, the cases verified, and the pagination tradeoff. If only helper tests ran, say that route authorization, real SQL execution, and failure mapping remain unverified. The interviewer can then assess your understanding, review decisions, and evidence alongside the working feature.