Mobile Tech Lead and Architecture
This sheet is the canonical reference for mobile leadership and architecture. Interview routes should point here instead of restating these explanations.
Senior engineer, Tech Lead, Engineering Manager, and Staff Engineer
| Role | Primary accountability | Hands-on expectation | Typical evidence |
|---|---|---|---|
| Senior engineer | Deliver a complex slice safely and improve nearby systems | Regular implementation, testing, review | Makes good local tradeoffs; follows behavior into production |
| Tech Lead | Keep a team technically aligned and shipping a coherent product | Still codes, investigates, reviews, and prototypes; coding share varies with team risk | Clear interfaces, decisions, sequencing, quality bar, unblocked engineers |
| Engineering Manager | Team health, staffing, performance, and organizational delivery | Usually less production coding; may review technical context | Coaching, hiring, sustainable delivery, team outcomes |
| Staff / Principal engineer | Improve outcomes across multiple teams or a technical domain | Hands-on through design, prototypes, incidents, or critical code | Resolves cross-team constraints and enables durable technical direction |
These titles vary by company. Ask what decisions the role owns, who manages people, and how much implementation is expected. A Tech Lead is not automatically the manager, architect, or person who approves every line.
🟩 Practical split: Stay hands-on where code or production evidence is the fastest way to reduce uncertainty. Delegate bounded implementation and review; keep ownership of system coherence, sequencing, risks, and feedback loops.
Technical ownership and decision-making
Ownership means making an outcome explicit: scope, constraints, decision-maker, success measure, failure plan, and follow-up owner. It does not mean unilateral control. For consequential choices, write an Architecture Decision Record (ADR): context; options; decision; consequences; risks; revisit trigger. Keep it short enough that a future team can discover why the choice was made.
Use a decision loop:
- Clarify product goals, user impact, constraints, and non-functional requirements.
- Identify reversible choices versus expensive-to-reverse commitments.
- Compare a small number of credible options against agreed criteria.
- Prototype or measure the highest-uncertainty assumption.
- Record the decision, owner, rollout, metrics, and rollback trigger.
- Revisit when evidence or constraints change, not because a pattern became fashionable.
For example, do not choose micro-frontends or a new mobile module system because a large app “needs scale.” Identify the coordination or build bottleneck, measure it, then compare the least complex remedy with the expected cost of migration.
Technical leadership in day-to-day work
| Responsibility | Useful behavior | Avoid |
|---|---|---|
| Engineering standards | State a small set of enforceable defaults; automate checks | Style rules that require constant manual policing |
| Code review | Prioritize correctness, privacy, compatibility, testing, and clarity; explain the risk | Rewriting code to match personal taste |
| Mentoring | Set a shared goal, offer a stretch task, give timely specific feedback | Taking over the task when it becomes uncomfortable |
| Delegation | Define contract, owner, decision boundary, check-in, and integration point | Assigning a vague outcome with no authority or support |
| Technical debt | Link debt to user or delivery risk; prioritize reduction with feature work | Treating all old code as equally urgent |
| Estimation and planning | Decompose unknowns, surface dependencies, use ranges, update from evidence | Promising an exact date before discovery |
| Stakeholder communication | Describe impact, options, confidence, and the next decision needed | Reporting only implementation detail or optimism |
| Incidents and releases | Stabilize first, communicate, preserve evidence, learn without blame | Hiding uncertainty or making an unmeasured risky change |
Planning, governance, and production ownership
A Tech Lead usually partners with product and delivery peers on sprint planning; the lead does not promise an estimate alone. Break work into reviewable slices, identify platform/backend/release dependencies, set confidence ranges, and make technical risks visible before they become schedule surprises. Maintain a rolling technical roadmap tied to product capabilities, reliability/build health, staffing, and migration windows; distinguish committed work from options and discovery. Coordinate cross-team API and release contracts early. An ADR records a decision; architecture governance should make exceptions, ownership, and review triggers clear without requiring committee approval for routine implementation.
During an incident, reduce user impact first: establish incident owner and communication cadence, use safe rollback/feature disablement if available, preserve diagnostics, and coordinate the right specialists. Afterward, examine contributing system conditions and convert findings to owned reliability work. Release responsibility includes staged rollout, compatibility, crash/performance signals, a stop/rollback threshold, and a support path—not just pressing a publish button.
Mobile architecture patterns
Choose boundaries from change ownership, testability, and team topology. Patterns are tools, not maturity levels.
| Pattern | Typical shape | Main responsibility / dependencies | Strength | Cost / common failure | Fit |
|---|---|---|---|---|---|
| MVC | View ↔ Controller ↔ Model | Controller coordinates view/model | Natural UIKit entry point | Fat view controller when logic has no seams | Small screens, legacy UIKit |
| MVVM | View → ViewModel → UseCase/Model | View renders state; view model coordinates screen behavior | Testable presentation logic | View model becomes a second application layer | UI with meaningful state/transitions |
| MVVM-C | Coordinator → MVVM screen → domain | View model emits route intent; coordinator owns navigation | Separates flow from screen state | Coordinator graph can become a global router | Multi-step UIKit or hybrid flows |
| VIPER | View → Presenter → Interactor → Entity; Router → next screen | View, interactor, presenter, entity, router split | Strong ownership boundaries | Too many files and ceremonial pass-throughs | Large teams with stable, independently owned features |
| Clean Architecture | UI / DB / API adapters → domain policy | Domain policy inside; adapters outside; code dependencies inward | Framework and transport changes are contained | Over-abstraction without real volatility | Product/domain rules outlive UI and API details |
| Hexagonal | Adapter ↔ Port ↔ Domain ↔ Port ↔ Adapter | Core defines ports; adapters implement them | External systems replaceable in tests | Ports mirror every implementation detail | Integrations, persistence, and transport have real variation |
| Modular architecture | Feature A → Shared contract ← Feature B | Build and ownership boundaries; no sibling internals | Parallel work and selective reuse | Excessive targets, cycles, slow builds, shared-module dumping ground | Many features/teams or measured build/ownership problems |
| Repository | UseCase → Repo contract ← API / Store | Hides data source selection and mapping | Central cache/offline policy | “Repository” becomes a generic god object | Multiple sources or meaningful persistence policy |
| Dependency injection | Composition root → object graph → consumers | Construct dependencies at a composition boundary | Replaceable dependencies and visible ownership | Service locator/global container hides graph | Test seams and cross-module composition |
| Coordinator | Flow owner → route intent → feature | Owns navigation transitions | Flow testability and deep-link mapping | Overbuilt routing framework for simple navigation | UIKit/hybrid navigation with complex flows |
| Factory | Consumer → Factory → configured product | Encapsulates creation/configuration | Creation policy stays out of callers | Factory per type with no varying policy | Complex setup, variants, or lifecycle rules |
| Observer / reactive | Publisher → subscribers | Propagates state/events | Decouples event producer and consumer | Hidden ordering, retain cycles, unbounded event streams | UI state or event fan-out with explicit cancellation |
| Unidirectional data flow | Action → Reducer → State → View | State plus actions/reducer | Traceable state changes | Global store and giant reducers for local screens | Complex interactive state and reproducibility |
| State machine | State + Event → State / Effect | Explicit states and legal transitions | Invalid transitions become visible | State explosion if every detail becomes a state | Auth, payment, upload, onboarding workflows |
| Domain-driven design | Context A ↔ Contract ↔ Context B | Domain model/language reflects business rules | Shared understanding for complex domains | Tactical DDD ceremony for simple CRUD | Rich, evolving business rules |
The pattern table is a selection summary. The existing architecture sheet has concrete canonical walkthroughs for MVVM-C, VIPER, Factory and Observer, protocol-oriented design, and architecture testing. Prefer those explanations over cloning an implementation into every interview route.
Architecture boundary example
SwiftUI / UIKit feature
│ user intent + render state
▼
Presentation model ──→ Use case / domain policy
│ defined port
▼
Repository contract
▲
┌───────────────┴───────────────┐
│ │
API adapter Local-store adapter
│ │
URLSession / DTO SwiftData / SQLiteThe diagram shows source-code dependencies, not necessarily runtime call direction for every operation. UI frameworks and transport types should not leak into domain rules without a deliberate reason. Keep a boundary only when it clarifies a real change, test, ownership, or security seam.
Swift implementation: port, adapter, composition root
struct Account: Equatable, Sendable {
let id: String
let displayName: String
}
protocol AccountRepository: Sendable {
func account(id: String) async throws -> Account
}
struct LoadAccount {
let repository: any AccountRepository
func callAsFunction(id: String) async throws -> Account {
try await repository.account(id: id)
}
}
struct AccountScreenModel {
let loadAccount: LoadAccount
}
// Composition root chooses concrete adapters once.
let repository: any AccountRepository = LiveAccountRepository(client: apiClient)
let screenModel = AccountScreenModel(loadAccount: LoadAccount(repository: repository))LiveAccountRepository and apiClient are app-specific implementation names. The example demonstrates the boundary; it is not a complete networking client. For the authoritative dependency inversion and DI explanation, see Dependency Inversion in Swift.
The view owns only presentation of an input value; a parent owns the shared expansion state and passes a binding. For the full state-ownership explanation, see the @Binding question path.
import SwiftUI
struct AccountDisclosure: View {
@Binding var isExpanded: Bool
var body: some View {
Toggle("Show account details", isOn: $isExpanded)
}
}Modular architecture and enterprise mobile platforms
Start with feature ownership and stable contracts, not a target-count goal. A possible banking app tree is:
AppShell
├── Features/Accounts ───────┐
├── Features/Payments ───────┼──> DesignSystem, DomainContracts
├── Features/Cards ──────────┘ │
├── Core/Authentication ────────────────┤
├── Core/Networking ────────────────────┤
├── Core/Observability ─────────────────┤
└── PlatformAdapters <──────────────────┘Enforce acyclic dependencies, narrow exported APIs, explicit resource ownership, and one direction of dependency. Shared frameworks should contain capabilities used by multiple owners—not code that merely lacks a team. Track target build time, incremental rebuilds, binary size, API breakage, and ownership friction before/after modularization.
For multiple apps/teams, version shared packages, document deprecation windows, publish compatibility checks, separate internal and public APIs, and make release trains explicit. Use remote configuration and feature flags for controlled rollout, but define an expiry owner and kill switch; flags are not authorization. API compatibility is a client/server contract: support older app versions during adoption windows, validate schemas, and make additive changes before removals.
Cross-platform engineering decision matrix
| Option | Sharing leverage | Native fit | Main risks | Team / product conditions that favor it |
|---|---|---|---|---|
| Native Swift + Kotlin | Lowest shared implementation | Highest per-platform access and idiom | Duplicate domain/UI work; coordination drift | Distinct platform experiences, deep OS integration, platform-specialized teams |
| Flutter | High shared UI and application code | Plugin/platform-channel boundary | Plugin maturity, framework expertise, platform-specific UX gaps | Consistent UI and a team willing to invest in Dart/Flutter |
| React Native | Shared UI/app logic with JS ecosystem | Native modules bridge to platform APIs | JS/native boundary, dependency and upgrade management | Existing TypeScript/React strength and meaningful code reuse |
| Kotlin Multiplatform | Share selected domain/data logic | Native UI remains an option | Interop/toolchain complexity and ownership ambiguity | Teams want shared business/networking logic but native presentation |
Compare total lifecycle cost, accessibility, offline behavior, performance on target devices, testing, hiring, release ownership, platform API lag, code-sharing percentage, and migration exit cost. A “shared code” percentage alone is not a decision metric.
data class AccountSummary(val id: String, val balanceMinorUnits: Long)
interface AccountRepository {
suspend fun load(id: String): AccountSummary
}This small Kotlin sketch illustrates a platform-native suspend API and typed model. Kotlin Multiplatform can share such domain/data code where it is valuable while the Android and iOS UI layers remain native; the interop/build ownership cost still belongs in the decision.
Mobile security, delivery, and reliability cross-references
Keep server authorization authoritative, protect credentials with platform storage, minimize sensitive analytics, and threat-model deep links, local data, and network boundaries. The shared threat-model and agent controls are in AI Security and Governance; mobile OAuth, TLS, token, and API controls are in Backend Engineering. For the iOS CI/CD rollout pattern, see release-process interview answer; for crash/startup/performance investigation, see mobile production diagnostics.
Quick Recap · Architecture interview hit points
🧠 Say: “I begin with product constraints, team ownership, and measured pain. I choose the smallest boundary that isolates a real change, assign an owner, define a migration path, and verify the outcome with build time, defect rate, lead time, or user metrics.”
🟨 Gotcha: Clean Architecture is not synonymous with more layers. A protocol for every type, a repository over a single immutable array, or a shared module owned by nobody can make an app harder to change.
