Lesson 71 of 83 advanced

Full Architecture + System Design Mock Pack

Complete architecture and system design mock round with real-world scenarios and self-scoring rubric

Open interactive version (quiz + challenge)

Real-world analogy

Architecture interviews are like being handed a city map, a budget, and a growth forecast and asked: design the transit system. There is no single correct answer — the interviewer is watching how you think, what trade-offs you articulate, and whether your decisions are driven by real-world constraints or textbook theory.

What is it?

A full architecture and system design mock pack for senior Android engineers. Covers Clean Architecture deep-dives, offline-first system design scenarios, multi-module architecture, and testing strategy design. Includes model answers with real-world nuance, explicit trade-off articulation, and a 1–4 self-scoring rubric calibrated to the senior bar at product companies and scale-ups.

Real-world relevance

At Klarna, Wise, or Grab, a senior Android architecture round typically begins: 'Design the notification delivery system for our mobile app — users must receive payment confirmations reliably, even with intermittent connectivity.' A senior candidate will immediately ask: What is our current DAU? Is the notification user-initiated or server-pushed? Do we need deduplication? What is the acceptable delivery latency? This scoping behavior alone signals senior-level thinking before the design has started.

Key points

Code example

// ARCHITECTURE MOCK — SYSTEM DESIGN SCENARIOS

// ============================================================
// SCENARIO 1: OFFLINE-FIRST PAYMENT HISTORY
// "Design an offline-first payment history feature for 1M users"
// Score yourself /4 before reading the model answer
// ============================================================

/*
STEP 1 — CLARIFY (always first):
- How many transactions per user on average? (1–50/day)
- What is the freshness requirement? (near-real-time vs daily sync)
- Conflict resolution: who wins — client or server?
- What happens on deletion? Soft delete or hard delete?

STEP 2 — COMPONENTS:
UI layer: TransactionHistoryScreen (Compose)
         -> collects from ViewModel.transactionsFlow
ViewModel: TransactionViewModel
         -> exposes StateFlow<List<Transaction>> from UseCase
Domain:   GetTransactionsUseCase
         -> calls TransactionRepository.observeTransactions()
Data:     TransactionRepository
         -> reads from Room (SSOT)
         -> WorkManager triggers SyncWorker on connectivity change
SyncWorker -> calls API -> writes to Room -> Room emits to Flow -> UI updates

STEP 3 — CONFLICT RESOLUTION:
Strategy: server-wins (simpler, consistent, appropriate for financial data)
Each record has: id, serverId, lastModifiedAt (server timestamp)
SyncWorker compares local lastModifiedAt vs server payload
Server version replaces local if server is newer

STEP 4 — DELETION:
Soft delete: isDeleted = true, deletedAt timestamp
SyncWorker processes deletions after upserts to avoid missed deletes
Hard delete from Room only after server confirms deletion

STEP 5 — TRADE-OFFS STATED:
"I chose server-wins over merge because financial data must be authoritative.
 Client-wins would risk showing stale balances. The trade-off is that
 optimistic UI updates for new transactions must be rolled back on sync conflict
 — I handle this with a 'pending' state on local inserts."
*/

// ============================================================
// SCENARIO 2: MULTI-MODULE BUILD ARCHITECTURE
// "How would you structure a 20-engineer Android app into modules?"
// ============================================================

/*
Module hierarchy (dependency flows downward):
:app (assembles, no business logic)
  |
  :feature:payments
  :feature:profile
  :feature:notifications
  |
  :domain (use cases, entities — pure Kotlin, no Android)
  |
  :data (repositories, API, Room — implements domain interfaces)
  |
  :core:ui (design system, shared Compose components)
  :core:network (OkHttp, Retrofit setup)
  :core:testing (shared fakes, test utilities)

Rules:
- :feature modules CANNOT depend on each other
- :feature modules depend on :domain, NOT :data
- :data depends on :domain (implements interfaces)
- Navigation between features: use a navigation contract in :domain

DI across modules: each module exposes a Hilt @Module
  that binds its implementations. :app installs all modules.

Build time impact:
- Modules enable parallel compilation
- Modules enable build cache — unchanged modules reuse cached output
- Trade-off: more Gradle config complexity, need strict dependency rules
*/

// SELF-SCORE EACH SCENARIO /4 then compare with model answers above

Line-by-line walkthrough

  1. 1. Step 1 of every system design question: Clarify scope and scale. This is the most visible senior signal in an architecture interview.
  2. 2. Offline-first pattern: Room is SSOT. Network data flows API -> Room -> Flow -> UI. UI never calls the API directly.
  3. 3. Conflict resolution for financial data: server-wins. Document this choice and the trade-off (optimistic UI requires rollback on conflict).
  4. 4. Soft delete: isDeleted flag + deletedAt timestamp prevents sync races when hard-deleting records before sync propagates.
  5. 5. Multi-module hierarchy: app -> feature -> domain <- data. Feature modules must not depend on each other.
  6. 6. Domain layer purity: no Android imports in domain. This enables fast JVM unit tests without Robolectric or device.
  7. 7. DI across modules: each module exposes a Hilt @Module. :app installs all modules as the composition root.
  8. 8. Build time improvement: parallel compilation of independent modules + build cache for unchanged modules.
  9. 9. Trade-off articulation: every architectural choice must include explicit trade-offs. This is the senior signal interviewers score highest.
  10. 10. Real project references: 'On [your project], I used X because Y' is more credible than theoretical answers.
  11. 11. Scalability signals: state what changes at 10x scale. Cursor-based pagination vs offset, database sharding, CDN for assets.
  12. 12. Synthesis question: prepare your answer to 'what architectural decision are you most proud of and what would you change?' — this is a common final-round question.

Spot the bug

// ARCHITECTURE DESIGN BUG — FIND THE VIOLATIONS:

// Feature module: feature-payments
class PaymentViewModel @Inject constructor(
    private val userRepository: UserRepository,
    private val paymentRepository: PaymentRepository,
    private val analyticsTracker: FirebaseAnalyticsTracker
) : ViewModel() {

    fun processPayment(amount: Double) {
        viewModelScope.launch {
            val user = userRepository.getCurrentUser()
            val result = paymentRepository.pay(amount, user.id)
            analyticsTracker.logEvent("payment_complete", mapOf("amount" to amount))
            _uiState.value = UiState.Success(result)
        }
    }
}

// domain/UserRepository.kt
interface UserRepository {
    suspend fun getCurrentUser(): User
}

// data/UserRepositoryImpl.kt
class UserRepositoryImpl @Inject constructor(
    private val db: AppDatabase,      // Room database
    private val context: Context      // Android Context
) : UserRepository { ... }
Need a hint?
Check: Does the ViewModel depend on the correct layers? Does the domain layer stay Android-independent? Is there a Clean Architecture layer violation? Is there a testability problem?
Show answer
Violation 1 (ViewModel depends on concrete analytics): PaymentViewModel depends directly on FirebaseAnalyticsTracker — a concrete class. This breaks Dependency Inversion. The ViewModel should depend on an AnalyticsTracker interface in the domain layer. This also makes the ViewModel untestable without Firebase. Violation 2 (Domain-to-data dependency via UserRepository location): If UserRepository interface is in the domain module but UserRepositoryImpl takes AppDatabase (a Room class) and Context — that is correct for the data module. However, verify the domain module itself has no Room or Context imports. Violation 3 (Business logic in ViewModel): processPayment() mixes orchestration (get user, pay) with analytics. This should be a ProcessPaymentUseCase in the domain layer — the ViewModel should call the use case, not orchestrate the steps. This makes the business logic untestable at the ViewModel level. Fix: Create ProcessPaymentUseCase(userRepository, paymentRepository, analyticsTracker: AnalyticsTracker). ViewModel calls use case, domain defines AnalyticsTracker interface, FirebaseAnalyticsTracker is the data-layer implementation.

Explain like I'm 5

Designing an architecture is like designing a restaurant kitchen. Someone has to take the order (UI), someone has to cook (business logic), someone has to get ingredients from the fridge or order from the supplier (data layer). Good kitchen design means the chef never has to go to the supplier themselves — they just tell someone to get what they need. And if the supplier is closed (no internet), there are ingredients in the fridge to keep cooking (offline-first).

Fun fact

System design interviews have the highest variance in scoring at senior Android roles. A Google study of their own hiring found that candidates who explicitly stated trade-offs were scored 35% higher than candidates who gave the same architectural choice without explaining the why. The interviewer already knows the common patterns — they are evaluating your reasoning, not your knowledge of patterns.

Hands-on challenge

Run the full architecture mock: (1) Set a 25-minute timer. Without notes, design an offline-first messaging feature for a mobile app (users can send and read messages when offline, sync when reconnected). Include: data flow, conflict resolution, sync strategy, failure handling, and testing approach. Score yourself /4. (2) Answer in writing or aloud: 'What is the most important architectural decision you have made in your career and what would you do differently?' This is your synthesis answer — refine it until it sounds natural and confident.

More resources

Open interactive version (quiz + challenge) ← Back to course: Android Interview Mastery