Irbisa · Шпаргалка
System Design мобилки
Offline-first, синхронизация, пагинация, кэш, модуляризация, фича-флаги
Как распределить 45 минут на мобильном system design интервью, чтобы закрыть то, что оценивает интервьюер?
Вы ставите лайк на экране деталей, возвращаетесь назад, а в ленте пост по-прежнему без лайка. Что не так с архитектурой?
// Wrong: each screen owns a copy, so a write on one is invisible to the otherclass DetailViewModel(private val api: Api) : ViewModel() { val post = MutableStateFlow<Post?>(null) fun like(id: String) = viewModelScope.launch { post.update { it?.copy(liked = true) } // only this screen will ever know…Как спроектировать поиск по мере ввода, чтобы он был отзывчивым и не заваливал бэкенд?
private val query = MutableStateFlow("") val results: StateFlow<SearchUi> = query .map { it.trim() } .distinctUntilChanged() .debounce(250)…Продукт требует свежести на каждом экране, а приложение всё равно шлёт сорок запросов на холодном старте. Как решить, что кэшировать и насколько?
// One policy per resource class, not one number for the whole appenum class Freshness(val ttl: Duration) { NEAR_STATIC(24.hours), // serve from cache within the ttl USER_OWNED(5.minutes), // serve from cache within the ttl VOLATILE(Duration.ZERO) // never render silently from cache}…Открытие экрана профиля отправляет семь запросов, и экран остаётся пустым, пока не ответит самый медленный. Что менять на клиенте и на бэкенде?
// Wrong: awaitAll means the screen is blank until the slowest call finishesval state = coroutineScope { val header = async { api.header(id) } val stats = async { api.stats(id) } val recs = async { api.recommendations(id) } // 900 ms p95, blocks the name ProfileUi(header.await(), stats.await(), recs.await())…iOS показывает одну сумму заказа, Android показывает другую, а в письме с чеком третья. Что сломано в дизайне системы?
Ограниченная акция должна закончиться в полночь, но часть пользователей видит её ещё сутки. Каким часам приложение может доверять?
// Wrong: the countdown and the gate both trust a clock the user can changeval remaining = offer.endsAtMillis - System.currentTimeMillis() // Right: one anchor, refreshed on every response, used wherever "now" is neededclass ServerTime { private data class Anchor(val serverMs: Long, val elapsedMs: Long)…Через полгода после релиза люди всё ещё открывают ту старую сборку. Что это меняет в проектировании?
Как бы вы спроектировали экран ленты в приложении вроде Instagram, чтобы скролл ощущался непрерывным и переживал поворот экрана и смерть процесса?
class FeedViewModel(private val repo: FeedRepository) : ViewModel() { val items: StateFlow<List<FeedItem>> = repo.observeFeed() // rows from the local DB .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) private var nextCursor: String? = null // persist it in a real app private var endReached = false…Как вести feature-флаги и remote config в большом приложении, не накопив сотни протухших веток?
// One registry: every flag has an owner and a removal date, so deletion is scheduled workenum class Flag(val key: String, val owner: String, val removeBy: LocalDate?) { CHECKOUT_V2("checkout_v2", owner = "payments", removeBy = LocalDate.parse("2026-12-01")), NEW_SEARCH("new_search", owner = "discovery", removeBy = LocalDate.parse("2027-01-15")), PAYMENTS_ENABLED("payments_enabled", owner = "payments", removeBy = null), // permanent kill switch}…Спроектируйте кэш в приложении со слоями в памяти и на диске: как реализовать LRU-вытеснение, лимиты размера и потокобезопасность?
class LruMemoryCache<K : Any, V : Any>( private val maxBytes: Long, private val sizeOf: (V) -> Long,) { // accessOrder = true: every get() moves the entry to the tail (most recently used) private val map = LinkedHashMap<K, V>(16, 0.75f, true)…Как спроектировать offline-first приложение: локальный источник истины, синхронизация при нестабильной сети и разрешение конфликтов?
class NotesRepository( private val db: AppDatabase, private val work: WorkManager,) { fun observe(): Flow<List<Note>> = db.notes().observeAll() // the UI reads only this…Как спроектировать чат вроде WhatsApp: транспорт, статусы доставки, офлайн-очередь и история сообщений?
suspend fun send(chatId: String, text: String) { val clientId = UUID.randomUUID().toString() db.withTransaction { messages.insert( MessageEntity(clientId = clientId, chatId = chatId, text = text, serverSeq = null, state = State.SENDING) // shown at once, at the bottom…Как спроектировать библиотеку загрузки изображений вроде Coil или Glide?
class ImageLoader( private val memory: LruCache<String, Bitmap>, // sizeOf() returns byteCount private val disk: DiskCache, private val fetcher: Fetcher, private val decodeDispatcher: CoroutineDispatcher = Dispatchers.Default.limitedParallelism(2),) {…Спроектируйте вертикальную ленту коротких видео, как в TikTok или Reels: предзагрузка, пул плееров, кэш сегментов и мгновенный старт воспроизведения при свайпе.
@OptIn(UnstableApi::class)class ShortsPlayback(context: Context, cache: Cache, clips: List<Clip>) { private var current = 0 private val builder = DefaultPreloadManager.Builder(context) { index: Int -> when (abs(index - current)) { 1 -> PreloadStatus.specifiedRangeLoaded(2_000L) // neighbours: 2 s in memory…Спроектируйте менеджер загрузки и выгрузки больших файлов: очередь, повторы, докачка и прогресс, которые переживают убийство приложения.
class DownloadWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) { override suspend fun doWork(): Result { val job = dao.get(inputData.getString("id")!!) ?: return Result.failure() val part = File(job.targetPath + ".part") val done = if (part.exists()) part.length() else 0L…Спроектируйте SDK аналитики и логирования для приложения: батчи, офлайн-буфер, сэмплирование и смена вендора.
interface AnalyticsSink { suspend fun send(batch: List<StoredEvent>) } // vendor adapters class Analytics( private val queue: EventDao, private val scope: CoroutineScope, private val sampler: Sampler,…Когда server-driven UI оправдан и что ломается, если увлечься?
@Serializablesealed interface Block { @Serializable @SerialName("hero") data class Hero(val imageUrl: String, val title: String, val action: String) : Block @Serializable @SerialName("product_row")…Как спроектировать клиент такси или сервиса «друзья рядом», который показывает живые координаты и не сажает батарею?
Как структурировать суперприложение, в которое десяток команд поставляет фичи в одно Android-приложение?