Interview Paths: Mobile, Backend, and AI Systems
This is a quick interview index, not a duplicate encyclopedia. Start with the short hit point, then follow the linked concept sheet for the detailed explanation, implementation, failure modes, security/performance implications, and alternatives. Add a specific story from your own work; do not claim experience you do not have.
Beginner — Name the contract
What is the difference between a protocol and a value you can instantiate?
🧠 Hit point: A protocol describes requirements; a concrete type conforms to it and is what you construct.
Example: Define protocol Store {} then create struct MemoryStore: Store {} and instantiate MemoryStore(). Avoid: writing Store() as if the protocol were a concrete type. Tradeoff/detail: Protocol construction and existential values.
What is an HTTP API request, and where should authorization happen?
🧠 Hit point: A client sends a request to a server endpoint, but the server must authenticate the principal and authorize access to each requested resource.
Example: The iOS app can hide an account button, but the API still checks account ownership for every request. Avoid: treating client-side validation as a security control. Tradeoff/detail: Request lifecycle and mobile API auth.
What is the difference between an AI agent and a tool?
🧠 Hit point: A tool performs one bounded capability; an agent uses a model-driven loop to pursue a goal by choosing among permitted observations/actions.
Example: A search tool returns matches; a read-only coding agent can use search, inspect results, and report a diagnosis. Avoid: assuming an agent's goal or prompt authorizes a tool call. Tradeoff/detail: Agent, tool, skill, workflow, and orchestrator.
Intermediate — Explain a boundary clearly
How do dependency injection and dependency inversion differ?
🧠 Hit point: Dependency inversion is a direction-of-dependency principle; dependency injection is one way to provide an implementation at a composition boundary.
Example: A payment use case depends on an any PaymentRepository, and the app root supplies live or test implementation. Avoid: creating protocols/containers for every type. Tradeoff and detail: Canonical Swift dependency inversion.
How do you choose between REST, GraphQL, and gRPC for a mobile product?
🧠 Hit point: Start with client data needs, contract evolution, caching, streaming, tooling, and the team's operational ability—not fashion.
Example: REST may suit a stable resource API; GraphQL can help diverse clients compose reads; gRPC can suit internal typed service calls. Avoid: assuming GraphQL or gRPC removes authorization/versioning work. Detail: API styles and tradeoffs.
Senior — Reason about correctness and production behavior
A mobile write times out. How do you prevent accidental duplicate effects?
🧠 Hit point: A timeout leaves the outcome unknown; retry with the same idempotency key and let the server durably return the same logical result.
Example: Keep the payment UI in a pending/unknown state until a status query confirms the result. Avoid: generating a new operation ID on each retry. Detail: Distributed reliability.
What does a modular monolith buy you before microservices?
🧠 Hit point: It creates enforceable ownership and dependency boundaries without immediately paying the network, deployment, and on-call cost of distributed services.
Example: Separate payment and account modules with explicit contracts and a shared database transaction while their consistency requirements remain coupled. Avoid: equating many packages with good architecture. Detail: Backend patterns.
How do you design a backend migration while old app versions are active?
🧠 Hit point: Expand compatibly, observe adoption, migrate data/clients, then contract only after the supported-version window.
Example: Add an optional response field first; ship clients that tolerate its absence; later remove a legacy endpoint only after usage and release policy allow it. Avoid: assuming every mobile user upgrades immediately. Detail: Enterprise mobile architecture.
Tech Lead — Make a decision and align owners
How do you choose MVC, MVVM-C, VIPER, or Clean Architecture for a new feature?
🧠 Hit point: Match the boundary to state complexity, navigation, business rules, testing needs, and team ownership; use the smallest pattern that addresses observed change.
Example: A small static settings screen can remain simple; a multi-step payment flow merits explicit state and navigation ownership. Avoid: pitching a pattern as universally superior. Detail: Mobile architecture pattern comparison.
When do you modularize a growing iOS app?
🧠 Hit point: When measurable build, parallel ownership, API stability, or change-isolation pain outweighs module and integration overhead.
Example: Pilot a high-conflict feature with an acyclic API and track build time and cross-team changes. Avoid: creating one shared “Core” module for every unrelated utility. Detail: Enterprise mobile architecture.
How do you resolve disagreement about a technical design?
🧠 Hit point: Agree on user goals and decision criteria, compare options against evidence, record a reversible decision and revisit trigger, then commit as a team.
Example: Prototype build-time impact before choosing a modularization strategy. Avoid: voting by seniority or prolonging debate without new evidence. Detail: Decision-making and ADRs.
How do you lead multiple developers without becoming the bottleneck?
🧠 Hit point: Define interfaces, owners, sequencing, quality gates, and escalation paths; delegate decisions at the lowest safe level.
Example: One engineer owns API contract, another feature UI, and a third tests a stable boundary, with a named integrator. Avoid: becoming the only reviewer/architect for every decision. Detail: Technical leadership responsibilities.
Backend architecture questions
How do you choose a database and scale it?
🧠 Hit point: Model consistency and query patterns first; index and profile real workloads before replicas, partitioning, or sharding.
Example: PostgreSQL transaction for a balance invariant and a cache only for explicitly stale-tolerant reads. Avoid: putting all reads in Redis without an invalidation/freshness contract. Detail: Database choices.
When would you add a queue or event bus?
🧠 Hit point: Use one when asynchronous decoupling, burst absorption, or fan-out justifies eventual consistency and operational burden.
Example: Transactional outbox publishes a notification after a payment commit. Avoid: a dual write where database commit succeeds but event publish silently fails. Detail: Distributed reliability and case patterns.
How should an iOS app authenticate to a backend?
🧠 Hit point: Use a native authorization flow with PKCE, short-lived access, carefully handled refresh, and server-side resource authorization; the app is a public client.
Example: Use the system browser for identity, store refresh material using platform protection, and recheck object permissions on each API action. Avoid: shipping a client secret or trusting a signed JWT as universal authorization. Detail: Mobile API authentication.
AI engineering and orchestration questions
When is RAG better than fine-tuning?
🧠 Hit point: Prefer retrieval when answers depend on changing/private/citable knowledge; fine-tuning changes model behavior rather than supplying a live, access-filtered source of truth.
Example: Search a versioned policy library and cite the authorized passage. Avoid: assuming embeddings enforce access. Detail: RAG architecture.
When should a workflow use an agent or multiple agents?
🧠 Hit point: Keep known transitions deterministic; add a model-driven loop only for observation-dependent judgment, and delegate only independent bounded tasks.
Example: Parallelize a read-only security review and API contract review; serialize implementation on the agreed contract. Avoid: spawning agents for each small step. Detail: Orchestration patterns.
How do you secure tools against prompt injection?
🧠 Hit point: Treat model/tool output as untrusted proposals; enforce identity, resource authorization, allowlists, sandboxing, budgets, and approval outside the model.
Example: A read-only search worker cannot invoke shell or network tools, even if a repository file instructs it to. Avoid: relying on a “do not follow injection” prompt alone. Detail: Agent security and trust boundaries.
How do you reduce AI system token cost without losing quality?
🧠 Hit point: Retrieve targeted source, avoid duplicate context, bound output/tool loops, and measure cost per accepted result alongside correctness.
Example: Search Swift symbols then read five relevant files and tests rather than sending the full repository. Avoid: claiming fixed savings without tokenizer and task assumptions. Detail: Token budgets and estimates.
How do you select and route models?
🧠 Hit point: Route by task complexity, latency, tool reliability, privacy, context need, and evaluated end-to-end success—not model hype.
Example: Use a fast extraction route only when schema/accuracy checks pass; escalate complex security review within budget. Avoid: assuming the cheapest model is cheapest after retries. Detail: Model selection and routing.
How do you know an AI feature is ready for production?
🧠 Hit point: Use versioned evaluation cases, retrieval/tool/safety checks, traces and usage budgets, privacy review, rollout, and rollback criteria.
Example: Canary a prompt change on an adversarial eval set and monitor groundedness, tool correctness, latency, and repair rate. Avoid: validating only with a hand-picked demo. Detail: AI system quality.
Staff / Principal system design routes
Use the corresponding complete case study rather than memorizing a single architecture:
- Design a secure banking mobile application → Banking case
- Design an enterprise platform for several apps/teams → Enterprise platform case
- Design an AI chat backend → AI chat case
- Design an AI coding orchestrator → Coding orchestrator case
- Design a secure knowledge assistant → RAG case
- Design a scalable mobile API → Distributed backend case
Coding exercises — prompts only
Try these before looking at the separate solution section. State assumptions and edge cases before coding.
Exercise 1: Idempotent API command
Design a TypeScript handler contract for POST /payments. Two requests with the same principal and idempotency key must produce one logical payment result. Explain persistence, concurrent duplicates, validation, and timeout recovery.
Exercise 2: Bounded DAG scheduling
Given tasks with dependency IDs and a maximum worker count, describe how to run ready independent tasks, stop on cancellation, and prevent a dependent task from running after a prerequisite fails.
Exercise 3: Swift feature boundary
Define a Swift protocol for loading an account asynchronously, a domain model, and one test double. Explain which layer owns DTO mapping and how cancellation/errors reach presentation state.
Exercise 4: Code review challenge — unsafe debit endpoint
Review this sketch. Identify authorization, consistency, idempotency, validation, logging, and failure-handling risks. Give findings by severity before proposing a patch.
app.post("/accounts/:id/debit", async (request, reply) => {
const amount = Number(request.body.amount);
await db.query("UPDATE accounts SET balance = balance - " + amount
+ " WHERE id = '" + request.params.id + "'");
request.log.info({ request }, "debit completed");
return reply.send({ ok: true });
});Exercise 5: Architecture tradeoff exercise — three mobile teams
A single iOS application has three feature teams, a slow full build, frequent merge conflicts, and one shared API module. A proposal recommends immediate microservices and a new cross-platform UI. State the evidence and constraints you would gather, the smallest experiment, success measures, and the conditions that would justify either proposal.
Coding exercise solutions
Solution 1: Idempotent API command
This sketch is framework-neutral and omits payment-domain and PCI/regulatory concerns. Use a unique database constraint on (principal_id, idempotency_key) and store a request fingerprint plus durable status/result. Reject reuse of the same key with a different payload. Wrap the idempotency record and domain transaction in a consistency boundary; publish side effects with an outbox.
type PaymentInput = { accountId: string; amountCents: number; currency: string };
type PaymentResult = { paymentId: string; status: "accepted" | "rejected" };
async function createPayment(
principalId: string,
key: string,
input: PaymentInput,
): Promise<PaymentResult> {
validatePayment(input);
const fingerprint = hashCanonical(input);
return database.transaction(async tx => {
const existing = await tx.idempotency.findForUpdate(principalId, key);
if (existing) {
if (existing.fingerprint !== fingerprint) throw new Error("Key reused with different request");
return existing.result;
}
await authorizeAccount(tx, principalId, input.accountId);
const result = await ledger.acceptPayment(tx, input);
await tx.idempotency.insert({ principalId, key, fingerprint, result });
await tx.outbox.insert({ type: "PaymentAccepted", payload: { paymentId: result.paymentId } });
return result;
});
}Concurrency requires a unique constraint and a defined conflict/re-read path; application-level “check then insert” alone races. Retain records long enough for the retry window. Add status lookup, response schema validation, audit, rate limit, and tests for simultaneous duplicate requests and mismatched payloads.
Solution 2: Bounded DAG scheduling
Validate the graph first: unique IDs, known dependencies, no cycles. Keep states pending/running/succeeded/failed/cancelled, admit only nodes whose dependencies all succeeded, cap active work, and cancel/skip dependents after failure. The actual implementation needs durable state and race-safe transitions for multiple scheduler processes.
type WorkItem = { id: string; dependsOn: string[]; run: (signal: AbortSignal) => Promise<void> };
async function executeDag(items: WorkItem[], maxWorkers: number, signal: AbortSignal) {
type State = "pending" | "running" | "succeeded" | "failed" | "cancelled";
const state = new Map<string, State>(items.map(item => [item.id, "pending"]));
const byId = new Map(items.map(item => [item.id, item]));
validateAcyclicGraph(items, byId);
while ([...state.values()].some(value => value === "pending" || value === "running")) {
if (signal.aborted) throw new Error("Cancelled");
const active = [...state.values()].filter(value => value === "running").length;
const ready = items.filter(item => state.get(item.id) === "pending"
&& item.dependsOn.every(id => state.get(id) === "succeeded"));
const slots = Math.max(0, maxWorkers - active);
if (ready.length === 0 && active === 0) throw new Error("Blocked or cyclic graph");
await Promise.all(ready.slice(0, slots).map(async item => {
state.set(item.id, "running");
try { await item.run(signal); state.set(item.id, "succeeded"); }
catch (error) { state.set(item.id, "failed"); throw error; }
}));
}
}For production, capture individual failures instead of losing the state map when Promise.all rejects; mark descendants blocked, propagate cancellation to running work, persist state/audit, enforce per-task timeouts, and ensure every task is idempotent or can be reconciled before retry.
Solution 3: Swift feature boundary
struct Account: Equatable, Sendable {
let id: String
let balanceCents: Int
}
protocol AccountLoading: Sendable {
func load(id: String) async throws -> Account
}
struct AccountScreenModel {
let loader: any AccountLoading
func refresh(id: String) async throws -> Account {
try Task.checkCancellation()
return try await loader.load(id: id)
}
}
struct StubAccountLoader: AccountLoading {
let result: Account
func load(id: String) async throws -> Account { result }
}The API adapter owns transport decoding and maps its DTO into the domain Account; presentation maps loading/success/failure into view state. Propagate cancellation instead of converting it to an ordinary user error. Sendable constraints should be checked against the app's selected Swift concurrency mode and SDK declarations.
Solution 4: Code review challenge — findings
- Critical: no authentication or object-level authorization is visible. Verify the principal and that it may debit this specific account.
- Critical: string concatenation allows SQL injection. Use a parameterized query and schema-validated amount/currency.
- High: the client-supplied account ID and amount do not prove authority or sufficient funds. Enforce business invariants in one transaction and use integer minor units/explicit currency rules.
- High: retries may debit twice. Require a principal-scoped idempotency key with a unique durable record and defined duplicate behavior.
- High: the log serializes the request object, potentially recording credentials and personal data. Log a stable event name and non-sensitive trace ID only.
- High: the handler returns success without checking affected rows, committed transaction result, or downstream state. Map domain outcomes into explicit response states.
Architecture tradeoff: Keep this logic in the service/domain boundary with a repository transaction; do not move all logic into generic middleware. See API security and distributed reliability.
Solution 5: Architecture tradeoff — decision path
First measure clean/incremental build time, target graph, CI queue, ownership conflicts, API churn, release cadence, and runtime bottlenecks. Identify whether the issue is compile dependency fan-out, shared module changes, or service deployment coupling. Pilot an acyclic feature module with a narrow public API and one team owner; record build time, cross-team edits, defects, and integration delay. Keep the backend modular monolith unless independent deploy/scale or domain ownership demonstrably justifies service distribution. Compare native Swift UI, Kotlin Multiplatform domain sharing, Flutter, and React Native against UX/accessibility needs, platform API use, team expertise, release ownership, and exit/migration cost. Reverse the proposal only when measured value exceeds operational and migration cost; no framework is inherently correct.
Short answer framework
- Define the problem in one sentence.
- State ownership and main tradeoff.
- Give one concrete example.
- Name a failure mode and how you verify it.
- Say what evidence would change your decision.
Keep the first answer under a minute, then expand when the interviewer asks. For hands-on interview prep and existing Senior iOS questions, continue to Interview Paths.
