Choosing a Platform
Native or cross-platform, Kotlin or Swift, and the lifecycle every mobile app has to survive.
The Decision That Shapes Everything Else
The first question is native or cross-platform, and it is more organisational than technical. Native means two codebases, two languages, two teams — and full access to every platform feature the day it ships. Cross-platform means one codebase and a bridge that is usually close enough.
| Approach | Buys you | Costs you |
|---|---|---|
| Native (Kotlin + Swift) | Best performance, every API on day one, platform-correct feel | Two codebases, two skill sets |
| React Native / Flutter | One codebase, shared logic, fast iteration | A bridge to maintain, lag behind new OS features |
| Web wrapped in a shell | Cheapest by far | Feels like a website, and users notice |
The honest heuristic: if the app is the product and its feel is the differentiator, go native. If it is a client onto a service you already run, cross-platform will save you a year.
Kotlin & Android
Kotlin is the language Android ships with, and it fixed the parts of Java that hurt most in app code: null safety in the type system, data classes instead of fifty lines of boilerplate, extension functions, and coroutines for asynchronous work that reads like sequential code.
data class User(val id: String, val name: String, val email: String?)
// The type says whether null is possible, and the compiler enforces it
fun greeting(user: User): String =
user.email?.let { "Signed in as $it" } ?: "Signed in as ${user.name}"
// Coroutines: asynchronous, but written top to bottom
suspend fun loadProfile(id: String): User = withContext(Dispatchers.IO) {
api.fetchUser(id)
}
Android itself is the harder half. An Activity is a screen, a Fragment is a reusable piece of one, an Intent is how you ask the system or another app to do something, and the manifest declares what your app is and what it may touch. Modern apps lean on Jetpack — the libraries that make lifecycle, navigation, storage and background work survivable.
Swift & iOS
Swift is fast, statically typed and expressive, with optionals doing the same job Kotlin's nullable types do. The ecosystem is smaller and more consistent — a handful of device sizes, an OS version curve where most users update within months, and one vendor deciding how everything works.
struct User: Codable {
let id: String
let name: String
let email: String?
}
// async/await, the same shape as Kotlin's coroutines
func loadProfile(id: String) async throws -> User {
try await api.fetchUser(id: id)
}
A UIViewController is roughly an Activity, Combine or async sequences handle streams of values, and Swift Package Manager handles dependencies. The platform is opinionated: doing things the Apple way is easy and everything else is uphill.
The App Lifecycle
This is the part with no desktop equivalent, and the part that produces the bugs nobody can reproduce. Your app does not own its process. The system starts it, backgrounds it, freezes it, and — under memory pressure — kills it without warning, then expects it to reappear exactly where the user left off.
| Transition | What you must do |
|---|---|
| Launched | Restore saved state, not just show a fresh screen |
| Backgrounded | Persist anything unsaved, release expensive resources |
| Resumed | Refresh anything that may be stale, reacquire the camera or location |
| Killed while backgrounded | Nothing — you are not running. This is why you saved on the way out |
| Rotated or resized | On Android the screen is rebuilt; survive it, do not reload |
Test it deliberately. Enable "don't keep activities" on Android, or background the app and let iOS reclaim it, then reopen. If the user loses their place, their draft, or their scroll position, the lifecycle is not handled.
Interview Questions
When would you choose native over cross-platform?
When the app is the product and its feel is the differentiator, when it needs new OS features on release day, or when performance-critical work like camera, graphics or audio is central. Otherwise a shared codebase usually wins on cost.
Why does Kotlin put nullability in the type system?
It moves an entire class of runtime crashes to compile time. The type states whether a value may be absent, and the compiler will not let you dereference it without handling that case.
What is the Android app lifecycle problem?
The system can stop and kill your process at any point and expects a seamless restore. State kept in the screen is lost on rotation or process death, so it belongs in a lifecycle-aware holder that outlives the view.
How do coroutines and async/await compare?
They solve the same problem the same way — suspend at an await point, free the thread, resume later — so asynchronous code reads sequentially instead of nesting callbacks.
Why is Android testing harder than iOS testing?
Fragmentation. Thousands of device models, screen sizes, OEM skins and OS versions still in use, versus a handful of Apple devices on a version curve where most users upgrade quickly.
How do you verify lifecycle handling?
Force the failure: turn on "don't keep activities", rotate the device, or background the app until the OS reclaims it. If the user loses their place or their unsaved input, it is not handled.