Lifecycles and state • Coroutines and Flow • Jetpack Compose • Room, Retrofit and Hilt • ANRs and releases • 2026

Android Developer Interview Questions

32 questions What each one tests, an answer frame, a spoken answer 35 min read

This page is for anyone interviewing for an Android role, from a first job to a senior engineer. Most Android rounds check that you understand lifecycles and configuration changes, then move to coroutines and Flow, Compose or RecyclerView, and the data layer with Room, Retrofit and Hilt. Stronger rounds add memory leaks, ANRs, background work, testing and shipping to the store, plus stories from real apps and a few judgement calls. Each question shows what the interviewer is checking, the shape of a good answer and a short answer you can say out loud. Replace the stories with your own.

Search all questions by round, difficulty and level, or save the ones you want to practise.

Motivation and Teamwork 4 questions

Easy Screening round Fresher, Mid-level Practice question

1. Walk me through how you got into Android development and the apps you've actually shipped.

What the interviewer is really testing:
Whether you have built and shipped real things, and can describe your own part in them clearly instead of listing technologies.
Answer frame:

Start: the short story of how you began, one or two lines.

Shipped work: one or two apps, who used them and what you owned.

Now: what you want to go deeper on in this role.

Sample spoken answer:

"I started in my second year of college when I wanted a simple app to split bills with friends. I built it in Kotlin with a single activity and a Room database, and about forty friends ended up using it, which taught me more than any course. After graduating I joined a small team building a delivery tracking app. I owned the order history and notifications screens, moved them from XML to Compose, and set up the WorkManager job that syncs orders when the network comes back. Now I want to work on an app with a bigger user base, where performance, crash rates and careful releases matter more, because that's the part of the job I've enjoyed most."

Red flag to avoid:

A list of libraries with no app, no users and no clear statement of what you personally built.

They may ask next:
  • What would you do differently if you rebuilt that first app today?
  • Which part of the delivery app are you least proud of?
Say it in 60 seconds
Medium Screening round Fresher, Mid-level, Senior Practice question

2. Why do you want to build native Android apps rather than work in a cross-platform framework? Would you ever choose one?

What the interviewer is really testing:
Whether you can give a balanced view of native versus cross-platform instead of treating it as a loyalty question.
Answer frame:

What native gives: new platform features on day one, deep system integration, full control of performance.

Where cross-platform wins: two platforms, a small team, mostly standard screens.

Your stance: why native suits you, while respecting the trade-off.

Sample spoken answer:

"I like native Android because I get new platform features the day they ship, I can integrate deeply with things like background work, widgets and notifications, and when something is slow I can profile it all the way down. That said, I don't think native is always right. If a small team has to ship the same mostly standard screens on two platforms, a cross-platform framework can be the smarter business choice. A middle path I find interesting is sharing the business logic in Kotlin Multiplatform while keeping native UI on each side. So for me it's about the product. I want to go deep on Android because that's where I think I add the most value, not because the other options are bad."

Red flag to avoid:

Dismissing every cross-platform option as bad without being able to name a single case where it makes sense.

They may ask next:
  • What would make you recommend a cross-platform framework to a team?
  • What would you share between platforms, and what would you keep native?
Say it in 60 seconds
Medium Behavioral round Fresher, Mid-level, Senior Practice question

3. Tell me about a time you pushed back on a designer or backend developer because a request didn't work well on Android.

What the interviewer is really testing:
Whether you can explain platform constraints to non-Android colleagues and reach a better answer together instead of just refusing or silently complying.
Answer frame:

The ask: what was requested and why it was a problem on Android.

How you raised it: evidence, a demo or a prototype, not just an opinion.

Outcome: the agreed solution and what the user got.

Sample spoken answer:

"On a shopping app, the backend team planned an endpoint that returned the whole product catalogue in one response, so the app could filter locally. On mid-range phones that meant a large download and a parse that froze the screen. Instead of just saying no, I built a quick test with a real sample file and showed the load time and memory use on a cheap test phone during our sync. That made the problem concrete. We agreed on a paged endpoint with server-side filtering and a small summary call for the first screen. It meant a bit more backend work, but the first screen loaded much faster and we stopped seeing memory crashes on low-end devices. I learned to bring numbers from a real device, not arguments."

Red flag to avoid:

A story where you simply built what was asked despite knowing it would fail, or where you won by being stubborn rather than showing evidence.

They may ask next:
  • What would you have done if the backend team still said no?
  • How do you push back on a designer's animation that would stutter on older phones?
Say it in 60 seconds
Easy Culture fit round Fresher, Mid-level, Senior Practice question

4. Android changes every year. How do you keep up without rewriting the app every time a new library comes out?

What the interviewer is really testing:
Whether you learn steadily and bring new tools in with judgement, especially platform changes that are actually required, like new target SDK rules.
Answer frame:

Sources: official release notes, behaviour changes for each version, a few trusted engineers.

Must-do versus nice-to-have: target SDK and policy changes come first.

Trying things: side projects or one small screen before team adoption.

Sharing: short write-ups or demos for the team.

Sample spoken answer:

"I split it into what we must do and what's nice to try. The must-do list is the behaviour changes page for each new Android version and the store's target SDK requirements, because those can break us or block updates if we ignore them. I read those every year and turn them into tickets early. For new libraries, I follow the official Android developer blog and release notes and a handful of engineers whose judgement I trust. When something looks useful, I try it in a side project first, then on one small, low-risk screen at work. If it earns its place, I write a short note or give a quick demo so the team can decide together. I'm happy to be a few months behind the newest thing if it means the app stays stable."

Red flag to avoid:

Either not knowing what changed in recent Android versions, or wanting to adopt every new library as soon as it is announced.

They may ask next:
  • What's a recent Android change you had to handle for a target SDK update?
  • What's a library you tried and decided not to adopt?
Say it in 60 seconds

Lifecycle and State 4 questions

Easy Technical round Fresher, Mid-level Practice question

5. A user opens your app, presses Home, then comes back a minute later. Which activity lifecycle callbacks run, in order?

What the interviewer is really testing:
Whether you know the lifecycle well enough to decide where to start and stop work, not just recite method names.
Answer frame:

Opening: onCreate, onStart, onResume.

Home: onPause, then onStop; state is saved for later.

Return: onRestart, onStart, onResume, or a fresh onCreate if the process was killed.

Why it matters: what you start and stop in each pair.

Sample spoken answer:

"When the app opens, the activity gets onCreate, where I set up the UI and ViewModel, then onStart when it becomes visible, then onResume when it's in the foreground and taking input. When the user presses Home, it gets onPause and then onStop, and the system also calls onSaveInstanceState so it can restore small UI state later. When they come back, it's onRestart, onStart and onResume. The catch is that while the app sat in the background, the system may have killed the process to free memory. In that case, coming back runs a fresh onCreate with the saved bundle. That's why I tie things like location updates or camera use to start and stop, and never assume the activity simply resumes."

Red flag to avoid:

Saying onDestroy runs when the user presses Home, or never mentioning that the process can be killed while in the background.

They may ask next:
  • What changes if a transparent dialog-style activity opens on top instead?
  • How would you test that your screen survives the process being killed in the background?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

6. What actually happens when the user rotates the screen, and how do you make sure they don't lose what they were doing?

What the interviewer is really testing:
Whether you know the three layers of state survival: ViewModel for configuration changes, saved state for process death, and disk for anything lasting.
Answer frame:

Default: the activity is destroyed and recreated with the new configuration.

ViewModel: survives rotation, but not process death.

Saved state: SavedStateHandle or rememberSaveable for small UI state that must survive process death.

Disk: real data lives in a database or DataStore.

Sample spoken answer:

"By default, a rotation is a configuration change, so the system destroys the activity and creates a new one with the new resources. Anything held only in the activity is gone. I handle it in layers. Screen data like a loaded list lives in a ViewModel, which survives rotation because it's kept outside the activity instance. But a ViewModel dies if the system kills the process in the background, so small things the user typed or selected, like a search query or a tab, go into SavedStateHandle, or rememberSaveable in Compose. Those are written into the saved instance state bundle. Anything that must last, like a draft message, I write to Room or DataStore. I avoid opting out of recreation in the manifest, because it just hides the problem."

Red flag to avoid:

Saying the ViewModel survives everything, including the process being killed, or fixing rotation by locking the screen orientation.

They may ask next:
  • Why shouldn't you put a large list into the saved instance state bundle?
  • How do you simulate process death to test this?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

7. Why does a fragment have two lifecycles, and why should you observe data with viewLifecycleOwner instead of the fragment itself?

What the interviewer is really testing:
Whether you know a fragment can outlive its view on the back stack, and the bugs that causes: duplicate observers, leaked bindings and updates to views that no longer exist.
Answer frame:

Two lifecycles: the fragment itself, and its view from onCreateView to onDestroyView.

Back stack: a replaced fragment on the back stack loses its view but stays alive.

Observers: observe with viewLifecycleOwner so each new view gets exactly one observer.

Binding: clear the view binding in onDestroyView.

Sample spoken answer:

"A fragment and its view don't live for the same length of time. When I replace a fragment and add that transaction to the back stack, the old fragment gets onDestroyView but not onDestroy, so the fragment object stays in memory with no view. When the user presses Back, onCreateView runs again and builds a brand new view. If I observe LiveData in onViewCreated with the fragment as the owner, the old observer is never removed, because the fragment never died. So after coming back I have two observers, and the old one may touch views that are gone. Using viewLifecycleOwner ties the observer to the current view, so it's removed in onDestroyView. Flows work the same way: I collect them with the view's lifecycle and repeatOnLifecycle. And I set the binding to null in onDestroyView so the old view tree can be freed."

Code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.items.observe(viewLifecycleOwner) { adapter.submitList(it) }
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}
Red flag to avoid:

Observing with the fragment as the owner inside onViewCreated, or not knowing that onDestroyView can run without onDestroy.

They may ask next:
  • What happens to the fragment's ViewModel while the fragment sits on the back stack?
  • How would you share data between two fragments on the same screen?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

8. LiveData or StateFlow: how do they behave differently, and which would you expose from a new ViewModel?

What the interviewer is really testing:
Whether you understand lifecycle awareness, initial values and duplicate handling, and know how to collect a flow safely from the UI.
Answer frame:

LiveData: Android-only, lifecycle-aware, only delivers to active observers.

StateFlow: plain Kotlin, needs an initial value, skips values equal to the current one.

Safe collection: repeatOnLifecycle or collectAsStateWithLifecycle.

Choice: StateFlow for new code, with a reason.

Sample spoken answer:

"LiveData is an Android class that knows about the lifecycle, so it only pushes updates to observers that are started or resumed, and it cleans up when the owner is destroyed. StateFlow comes from Kotlin coroutines. It always has a value, so you must give it an initial one, and it skips an update if the new value equals the current one. It isn't lifecycle-aware by itself, so in views I collect it inside repeatOnLifecycle with the started state, and in Compose I use collectAsStateWithLifecycle. For a new ViewModel I'd expose StateFlow, because it works with the rest of the flow operators, it's easy to test without Android, and it can live in modules that don't depend on Android. I'd keep LiveData where an older screen already uses it."

Code:
// ViewModel
private val _state = MutableStateFlow<UiState>(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow()

// Fragment
viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.state.collect { render(it) }
    }
}
Red flag to avoid:

Collecting a flow in a fragment with no lifecycle handling, so it keeps working while the screen is in the background.

They may ask next:
  • What goes wrong if you collect a flow with lifecycleScope.launch alone, without repeatOnLifecycle?
  • How would you send a one-time event, like a snackbar, from a ViewModel?
Say it in 60 seconds

Coroutines 3 questions

Medium Technical round Fresher, Mid-level, Senior Practice question

9. When do you use viewModelScope, lifecycleScope or GlobalScope, and which dispatcher does each kind of work belong on?

What the interviewer is really testing:
Whether you understand structured concurrency, meaning work is tied to something that owns it and gets cancelled with it.
Answer frame:

viewModelScope: work for a screen's data, cancelled when the ViewModel is cleared.

lifecycleScope: UI work tied to an activity or fragment.

GlobalScope: almost never; use an injected app scope or WorkManager instead.

Dispatchers: Main for UI, IO for blocking calls, Default for CPU work.

Sample spoken answer:

"A scope decides how long a coroutine lives. viewModelScope is my default for loading screen data, because it's cancelled when the ViewModel is cleared, so a user leaving the screen stops the work. lifecycleScope belongs to an activity or fragment, and I use it for UI things like collecting state. GlobalScope isn't tied to anything, so nothing cancels it and it can leak or run after it's useful. If work really must outlive a screen, I inject an application-level scope, and if it must survive the process dying, it goes to WorkManager. For dispatchers, Main is for touching the UI, IO is for blocking work like file access, and Default is for CPU-heavy work like parsing a big list. Suspend functions from Room and Retrofit already switch threads, so I don't wrap them again."

Red flag to avoid:

Reaching for GlobalScope to make a request 'keep going', or saying coroutines are threads.

They may ask next:
  • What does it mean for a suspend function to be main-safe?
  • Why is launching work from a ViewModel better than from a fragment for a network call?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

10. How does cancellation work in coroutines, what happens when a child coroutine throws, and where does SupervisorJob come in?

What the interviewer is really testing:
Whether you know that cancellation is cooperative, that a failing child cancels its parent by default, and how to stop one failure taking down unrelated work.
Answer frame:

Cooperative cancellation: suspend points check it; tight loops must check it themselves.

Don't swallow it: CancellationException must be rethrown.

Failure spreads: under a normal Job, a failing child cancels the parent and siblings.

SupervisorJob: children fail on their own; viewModelScope already uses one.

Sample spoken answer:

"Cancellation is cooperative. Library suspend functions like delay or withContext check for it, but a loop doing CPU work won't stop unless I call ensureActive or check isActive. Cancelling throws a CancellationException inside the coroutine, so if I write a catch-all for Exception, I have to rethrow that one or I break cancellation. For failures, under a normal Job, a child that throws cancels its parent, and the parent cancels all the other children. That's usually right for related work: if one of two parallel calls for a screen fails, the other is pointless. When the children are independent, I use a SupervisorJob or supervisorScope, so one failing doesn't cancel the rest. viewModelScope already uses a SupervisorJob, which is why one failed launch doesn't kill the others there."

Code:
suspend fun loadAll() = supervisorScope {
    val news = async { api.news() }
    val weather = async { api.weather() }
    val items = try { news.await() } catch (e: IOException) { emptyList() }
    val forecast = try { weather.await() } catch (e: IOException) { null }
    Dashboard(items, forecast)
}
Red flag to avoid:

Wrapping everything in runCatching or catch Exception without rethrowing cancellation, then being surprised that work never stops.

They may ask next:
  • How does an exception from async differ from one thrown in launch?
  • Where would you put a CoroutineExceptionHandler, and when does it actually run?
  • How do you run cleanup code that must finish even after cancellation?
Say it in 60 seconds
Hard Coding round Mid-level, Senior Practice question

11. Write a search ViewModel that waits for the user to stop typing and cancels the old request when a new query arrives.

What the interviewer is really testing:
Whether you can use Flow operators to express debouncing and cancellation cleanly, and handle errors without killing the stream.
Answer frame:

Input: a MutableStateFlow holding the query.

Debounce: wait for a pause, trim, drop repeats.

Latest only: mapLatest cancels the previous search when a new query arrives.

Expose: stateIn with viewModelScope; catch errors inside each search.

Sample spoken answer:

"I keep the query in a MutableStateFlow that the text field updates. From that I build the results flow. debounce waits until typing pauses for a short time, then I trim the text and use distinctUntilChanged so the same query doesn't search twice. The key operator is mapLatest: when a new query comes in while the previous search is still running, it cancels the old one, so a slow response can't overwrite newer results. I catch network errors inside the mapLatest block, not after it, because a catch at the end of the chain would complete the whole flow after the first failure and search would stop working. Finally stateIn turns it into a StateFlow tied to viewModelScope, and WhileSubscribed stops the work shortly after the screen stops collecting."

Code:
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
class SearchViewModel(private val repo: SearchRepository) : ViewModel() {
    private val query = MutableStateFlow("")

    val results: StateFlow<List<Item>> = query
        .debounce(300)
        .map { it.trim() }
        .distinctUntilChanged()
        .mapLatest { q ->
            if (q.length < 2) emptyList()
            else try { repo.search(q) } catch (e: IOException) { emptyList() }
        }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

    fun onQueryChanged(text: String) { query.value = text }
}
Red flag to avoid:

Launching a new coroutine per keystroke with no cancellation, so older, slower responses overwrite newer results.

They may ask next:
  • How would you show a loading indicator while a search is running?
  • How would you test the debounce without the test actually waiting?
  • Why not use flatMapMerge here?
Say it in 60 seconds

Compose and UI 3 questions

Medium Technical round Mid-level, Senior Practice question

12. What triggers recomposition in Jetpack Compose, and how would you stop a screen from recomposing far more than it needs to?

What the interviewer is really testing:
Whether you understand that Compose re-runs functions that read changed state and can skip others, and know practical ways to keep that cheap.
Answer frame:

Trigger: a state object read during composition changes; readers are re-run.

Skipping: a composable with stable, unchanged inputs can be skipped.

Fixes: read state late, derivedStateOf, remember, stable models, keys in lazy lists.

Measure: recomposition counts in Layout Inspector, then frame timing on a release build.

Sample spoken answer:

"Compose tracks which state each composable reads. When that state changes, Compose re-runs the composables that read it, and it can skip children whose inputs are stable and haven't changed. Problems come from reading fast-changing state too high up. For example, if the whole screen reads a scroll offset, every scroll frame recomposes the screen. My fixes are to read state as low and as late as possible, like passing a lambda instead of a value, or using the lambda version of a modifier such as offset so the read happens in layout, not composition. I use derivedStateOf when the UI only cares about a threshold, like whether to show a scroll-to-top button. I remember expensive calculations, give lazy list items stable keys, and keep UI models immutable. Then I check the counts in Layout Inspector."

Code:
val listState = rememberLazyListState()
val showScrollToTop by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
LazyColumn(state = listState) {
    items(articles, key = { it.id }) { ArticleRow(it) }
}
Red flag to avoid:

Thinking the whole screen redraws on every change, or trying to fix performance before measuring recomposition counts.

They may ask next:
  • What makes a class stable or unstable in the eyes of the Compose compiler?
  • Why can a debug build make Compose performance look worse than it really is?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

13. In Jetpack Compose, what do remember and state hoisting mean, and why would you make a composable stateless?

What the interviewer is really testing:
Whether you understand how Compose keeps state between recompositions, and where state should live so components stay reusable and easy to test.
Answer frame:

remember: keeps a value across recompositions; lost when it leaves the screen or on rotation.

mutableStateOf: an observable value; changing it recomposes whatever reads it.

Hoisting: move state up to the caller; pass the value down and an event lambda up.

Why: one source of truth, easy previews and tests, reusable components.

Sample spoken answer:

"Composables can run again at any time, so a normal local variable resets on every recomposition. remember keeps a value across recompositions, and mutableStateOf makes it observable, so changing it recomposes whatever reads it. remember alone doesn't survive rotation, which is what rememberSaveable is for. State hoisting means taking that state out of a composable and moving it up to the caller. The composable receives the current value and a lambda like onQueryChange, and it just displays and reports. So my search field is stateless: it shows the text it's given and calls back when the user types. The caller, often backed by the screen's ViewModel, owns the real state. That gives me one source of truth, and the same component works in previews, tests and other screens. I hoist state to the lowest common parent that needs to read or change it."

Code:
@Composable
fun SearchField(query: String, onQueryChange: (String) -> Unit) {
    TextField(value = query, onValueChange = onQueryChange)
}

@Composable
fun SearchScreen(viewModel: SearchViewModel) {
    val query by viewModel.query.collectAsStateWithLifecycle()
    SearchField(query = query, onQueryChange = viewModel::onQueryChanged)
}
Red flag to avoid:

Holding UI state in a plain variable and wondering why it resets, or giving every small component its own copy of the screen's state.

They may ask next:
  • What goes wrong if you call mutableStateOf inside a composable without remember?
  • When would you keep state inside a composable instead of hoisting it?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

14. How does RecyclerView reuse views as you scroll, and what does DiffUtil give you over calling notifyDataSetChanged?

What the interviewer is really testing:
Whether you understand view holders and binding, and why precise list updates matter for smoothness and animations.
Answer frame:

Recycling: only enough view holders for the screen; scrolled-off ones are rebound.

Binding: onBindViewHolder must be cheap and must reset every view.

DiffUtil: computes exact inserts, removes, moves and changes.

ListAdapter: runs the diff off the main thread.

Sample spoken answer:

"RecyclerView only creates enough view holders to fill the screen plus a few extra. When a row scrolls off, its view holder goes into a cache or a shared pool, and when a new row scrolls in, RecyclerView reuses one and calls onBindViewHolder to fill it with new data. So binding has to be fast, and it has to set every view, or you see old data flicker in from a recycled row. notifyDataSetChanged tells the adapter that everything might have changed, so it rebinds all visible rows and you lose item animations. DiffUtil compares the old and new lists, using areItemsTheSame on the id and areContentsTheSame on the data, and sends only the real changes. I use ListAdapter, which runs that diff on a background thread and just needs submitList."

Red flag to avoid:

Saying RecyclerView creates a view for every item, or forgetting to reset view state in onBindViewHolder.

They may ask next:
  • Why do you see a checkbox appear ticked on the wrong row after scrolling?
  • When would you share a RecycledViewPool between several lists?
Say it in 60 seconds

Background Work 3 questions

Easy Technical round Fresher, Mid-level Practice question

15. What's the difference between an explicit and an implicit intent, and what is a PendingIntent for?

What the interviewer is really testing:
Whether you know how components are started inside and across apps, and how to let the system act on your behalf safely.
Answer frame:

Explicit: names the exact component; used inside your own app.

Implicit: describes an action; the system finds apps with a matching intent filter.

PendingIntent: a token that lets the system or another app fire your intent later as your app.

Safety: handle no matching app, and mark PendingIntents immutable unless they must change.

Sample spoken answer:

"An explicit intent names the exact class to start, like my own DetailsActivity, so I use it inside my app. An implicit intent describes what I want done, like viewing a web page or sharing text, and the system finds an app whose intent filter matches. With implicit intents I have to handle the case where no app can do it, otherwise startActivity throws. A PendingIntent wraps an intent and hands it to someone else, like the notification system or AlarmManager, so they can fire it later with my app's identity and permissions. That's how tapping a notification opens my screen even if my app isn't running. On recent Android versions I have to say whether it's mutable or immutable, and I choose immutable unless something like an inline reply needs to fill it in."

Red flag to avoid:

Not knowing what happens when no app matches an implicit intent, or treating PendingIntent as just another kind of intent.

They may ask next:
  • Why is a mutable PendingIntent a security risk?
  • How would you build a notification tap that opens a detail screen with a proper back stack?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

16. Users need their photos to finish uploading even if they close the app. Would you use a service, a foreground service or WorkManager?

What the interviewer is really testing:
Whether you know the modern rules on background work and pick the tool that guarantees the job without draining the battery or breaking policy.
Answer frame:

Background services: heavily limited since Android 8; not a safe home for this.

Foreground service: for ongoing work the user is aware of, with a visible notification and a declared type.

WorkManager: persisted, guaranteed work with constraints and retries.

Choice: WorkManager, made long-running with a notification if the upload is big.

Sample spoken answer:

"I'd use WorkManager. The work is persisted, so it survives the app being closed and even a reboot, and I can add a network constraint, retry with backoff, and use unique work so the same batch isn't queued twice. A plain background service isn't reliable here, because since Android 8 the system stops background services soon after the app leaves the foreground. A foreground service is meant for ongoing work the user is actively aware of, like navigation or music, and it needs a notification and, on newer versions, a declared service type. For a large upload the user started and is waiting on, I'd still use WorkManager but make the worker long-running with setForeground, which shows a progress notification. I'd also save upload progress so a retry doesn't start from zero."

Red flag to avoid:

Starting a plain background service or a thread from an activity and assuming it will keep running after the app closes.

They may ask next:
  • What is the shortest interval a periodic WorkManager job can run at?
  • How would you show upload progress in the UI while the worker runs?
  • What happens to your queued work if the user force-stops the app?
Say it in 60 seconds
Medium Situational round Fresher, Mid-level, Senior Practice question

17. Product wants to ask for background location on first launch, but the feature only needs location while the app is open. What do you do?

What the interviewer is really testing:
Whether you understand runtime permissions and store policy, and can steer product towards asking for the least access at the right moment.
Answer frame:

Least access: foreground location covers this feature.

Ask in context: request it when the user taps the feature, with a short reason first.

Denial: degrade gracefully, and send to settings only after repeated denials.

Risk: background location brings extra review and fewer users saying yes.

Sample spoken answer:

"I'd push back, because asking for more than we need hurts us twice. Users are much more likely to say no to a scary prompt on first launch, and background location brings a stricter store review where we'd have to justify it. Since the feature only runs while the app is open, foreground location is enough. I'd request it when the user taps the feature, using the activity result permission API, with a short explanation first if the system says a rationale should be shown. If they deny it, the feature falls back to letting them type a city. On recent Android versions, after two denials the system stops showing the prompt, so then I'd offer a button to the app's settings page instead. If we later need tracking during an active trip, a foreground service with a location type is the right route, not background access."

Red flag to avoid:

Requesting every permission on first launch, or treating the permission prompt as something to get past rather than a user choice.

They may ask next:
  • How do you handle a user who grants only approximate location?
  • What changes if the user revokes the permission while the app is in the background?
Say it in 60 seconds

Data and Networking 3 questions

Medium Technical round Fresher, Mid-level, Senior Practice question

18. You need to add a column to a Room table in an app that's already on users' phones. What do you do so nobody loses data?

What the interviewer is really testing:
Whether you know Room's versioning and migration rules and treat users' stored data as something you must never break.
Answer frame:

Version: bump the database version.

Migration: write a Migration with the SQL change and register it.

Avoid: destructive fallback, which wipes user data.

Test: export the schema and test every upgrade path.

Sample spoken answer:

"First I update the entity and bump the database version number. Then I write a Migration from the old version to the new one that runs the SQL, here an ALTER TABLE that adds the column with a default value that matches the entity, and I register it with addMigrations on the database builder. If I bump the version and forget the migration, Room throws when it opens the database, so the app crashes on launch for everyone who updates. The shortcut is fallbackToDestructiveMigration, but that deletes the user's data, so I only accept it for pure caches. For simple changes like adding a column, Room can also generate an auto-migration from the exported schemas. Either way I keep exportSchema on, commit the schema files, and write a migration test that upgrades a real old database."

Code:
val MIGRATION_3_4 = object : Migration(3, 4) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE orders ADD COLUMN note TEXT NOT NULL DEFAULT ''")
    }
}

Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .addMigrations(MIGRATION_3_4)
    .build()
Red flag to avoid:

Reaching for fallbackToDestructiveMigration on user data, or not knowing the app crashes when a migration is missing.

They may ask next:
  • How do you handle a user jumping from version 1 straight to version 4?
  • How would you rename a column or change its type in SQLite?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

19. How do you structure Retrofit calls in a repository so the UI gets clean loading, success and error states?

What the interviewer is really testing:
Whether you separate networking from UI, know which errors Retrofit throws, and turn them into states a screen can render.
Answer frame:

API interface: suspend functions returning data models.

Repository: calls the API, catches network and HTTP errors, maps to a result type.

ViewModel: turns results into one UI state the screen renders.

Cross-cutting: auth headers and logging in OkHttp interceptors.

Sample spoken answer:

"I declare the API as a Retrofit interface with suspend functions. The repository is the only thing that calls it. With a suspend function that returns the body directly, a non-success status code throws an HttpException and a network problem throws an IOException, so the repository catches those two and maps them to a small sealed result, like success with data or failure with a reason the UI can explain. If I need the status code or headers, I return Response and check it myself. The ViewModel then turns that into one UI state, loading, content or error with a retry action, and exposes it as a StateFlow. Things every request needs, like the auth token and logging, go into OkHttp interceptors rather than each call."

Code:
sealed interface ApiResult<out T> {
    data class Ok<T>(val data: T) : ApiResult<T>
    data class Err(val reason: Reason) : ApiResult<Nothing>
}
enum class Reason { OFFLINE, NOT_FOUND, SERVER }

suspend fun getUser(id: String): ApiResult<User> = try {
    ApiResult.Ok(api.getUser(id))
} catch (e: IOException) {
    ApiResult.Err(Reason.OFFLINE)
} catch (e: HttpException) {
    ApiResult.Err(if (e.code() == 404) Reason.NOT_FOUND else Reason.SERVER)
}
Red flag to avoid:

Calling Retrofit straight from an activity, or catching every exception and showing the same 'something went wrong' for all of them.

They may ask next:
  • How would you refresh an expired token without firing five refresh calls at once?
  • Where would you add caching so the screen shows something while offline?
Say it in 60 seconds
Medium Situational round Fresher, Mid-level, Senior Practice question

20. A teammate wants to put a third-party service's secret key in BuildConfig so the app can call that service directly. How do you respond?

What the interviewer is really testing:
Whether you know that anything shipped in the app can be extracted, and can offer a practical safer design instead of just saying no.
Answer frame:

The risk: anything in the APK can be pulled out, even with obfuscation.

Better design: the app calls our backend, which holds the secret.

If it must be client-side: a restricted key or short-lived token from our server.

Tone: explain, offer help, agree next steps.

Sample spoken answer:

"I'd explain it without making it personal. Anything inside the app, including BuildConfig fields and strings, can be pulled out of the APK with free tools in a few minutes, and obfuscation only slows that down. If it's a secret that can spend money or read other users' data, someone will eventually take it. The safer design is for the app to call our own backend, which holds the secret and calls the service, so we can also add rate limits and logging. If the service is meant to be called from the client, like a maps key, I'd use a key restricted to our package name and signing certificate, or have our server issue a short-lived token. I'd offer to help build the backend piece, so it doesn't feel like I'm just blocking the feature."

Red flag to avoid:

Believing obfuscation or hiding the key in a resource file makes it safe to ship.

They may ask next:
  • Does keeping the key in native code or encrypting it in the app solve the problem?
  • How would you store the user's own login token on the device?
Say it in 60 seconds

Architecture 4 questions

Easy Technical round Fresher, Mid-level Practice question

21. What problem does Hilt solve, and how would you give a ViewModel its repository through it?

What the interviewer is really testing:
Whether you understand dependency injection as a way to wire and swap dependencies, and know the basic Hilt annotations.
Answer frame:

Problem: classes building their own dependencies are hard to test and share.

Setup: HiltAndroidApp on Application, AndroidEntryPoint on screens.

ViewModel: HiltViewModel with an Inject constructor.

Bindings: a module with Binds or Provides, installed in a component with a scope.

Sample spoken answer:

"Without injection, each class creates its own dependencies, so a ViewModel might build its own repository, which builds its own Retrofit client. That's hard to test and wastes objects. Hilt, which is built on Dagger, generates that wiring at compile time. I annotate the Application with HiltAndroidApp and each activity or fragment with AndroidEntryPoint. The ViewModel gets HiltViewModel and an Inject constructor that asks for the repository interface. Then I tell Hilt which implementation to use in a module installed in the singleton component. I use Binds for my own interface-to-class mappings and Provides for things I can't annotate, like the Retrofit instance. In the screen I just use by viewModels, or hiltViewModel in Compose. In tests I can swap the module for fakes."

Code:
@HiltViewModel
class ProfileViewModel @Inject constructor(
    private val repo: ProfileRepository
) : ViewModel()

@Module
@InstallIn(SingletonComponent::class)
abstract class DataModule {
    @Binds @Singleton
    abstract fun bindProfileRepo(impl: DefaultProfileRepository): ProfileRepository
}
Red flag to avoid:

Describing Hilt as a library you add 'because everyone does' with no idea what problem it solves for testing.

They may ask next:
  • What's the difference between Binds and Provides?
  • What goes wrong if you scope everything as a singleton?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

22. Explain how you'd structure an app with MVVM and clean layers. Which logic lives in which layer?

What the interviewer is really testing:
Whether you can place real logic in the right layer and explain why the separation helps testing and change, not just name the pattern.
Answer frame:

UI layer: screens render state and send events; the ViewModel holds UI state.

Domain layer: optional use cases for business rules shared across screens.

Data layer: repositories over network and database sources.

Direction: state flows down, events go up, and each layer depends only on the one below it through interfaces.

Sample spoken answer:

"I split it into three layers. The UI layer is the activity, fragment or composables plus the ViewModel. The screen only renders a state object and sends user events up. The ViewModel turns events into calls and exposes one UI state as a StateFlow, and it doesn't hold views or contexts. Under that, an optional domain layer holds use cases, like applying a discount rule, when that logic is shared or complex enough to test alone. The data layer has repositories that hide where data comes from, network, Room or DataStore, and decide things like caching. State flows down and events go up. Each layer only knows about the one below it through interfaces, so I can test the ViewModel with a fake repository and swap a data source without touching the UI."

Red flag to avoid:

Putting network calls or business rules in the activity, or a ViewModel that holds a reference to a view or activity context.

They may ask next:
  • When would you skip the domain layer entirely?
  • Where does input validation for a form belong, and why?
Say it in 60 seconds
Hard System design round Mid-level, Senior Practice question

23. Design the data layer for a news feed that has to work offline, page smoothly and stay fresh in the background.

What the interviewer is really testing:
Whether you can combine a local source of truth, paging, background sync and offline writes into one coherent design and explain the trade-offs.
Answer frame:

Source of truth: the UI reads only from Room; the network only writes into Room.

Paging: Paging with a remote mediator that fetches the next page into the database.

Freshness: WorkManager periodic sync with constraints, plus refresh on open if data is stale.

Offline actions: an outbox table synced later, with a clear conflict rule.

Sample spoken answer:

"I'd make Room the single source of truth. The feed screen observes a paged query from Room, and the network never talks to the UI directly. It only writes into the database. For paging I'd use the Paging library with a remote mediator, so when the user nears the end of the cached items it fetches the next page and inserts it, and the list updates itself. Each row stores when it was fetched, so on opening the app I refresh only if the data is stale. For background freshness, a periodic WorkManager job runs on unmetered network, and I'd use conditional requests so an unchanged feed costs almost nothing. If the user likes or saves an article offline, I update the local row right away and write the action to an outbox table that a worker sends later, with the server as the final word on conflicts."

Red flag to avoid:

Showing network results directly and treating the database as an afterthought, or having no plan for actions taken while offline.

They may ask next:
  • How would you cap the size of the local cache over time?
  • What does the user see if a queued action is rejected by the server?
  • How would you handle a breaking change in the feed's API while old app versions are still in use?
Say it in 60 seconds
Hard Behavioral round Mid-level, Senior Practice question

24. Tell me about moving part of an app to a newer approach, like Compose, coroutines or a new architecture, without stopping feature work.

What the interviewer is really testing:
Whether you can modernise a codebase step by step, manage risk and keep the team shipping, rather than proposing a big rewrite.
Answer frame:

Why: the real problem the old approach caused.

Plan: where you started and how old and new lived side by side.

Risk: how you kept releases safe along the way.

Result: what improved, and what you'd do differently.

Sample spoken answer:

"In my last team, most screens used callbacks and a lot of logic lived in fragments, which made bugs hard to test. A full rewrite wasn't an option, so I proposed a rule: any screen we touch for a feature moves to a ViewModel with coroutines, and new screens are written in Compose. I started with one small settings screen to set the pattern, wrote a short guide, and used ComposeView so Compose screens sat inside our existing fragments. For shared code we wrapped the old callback APIs with suspendCancellableCoroutine, so new code didn't have to wait for the old code to change. It took about nine months, and the crash rate on migrated screens dropped noticeably. What I'd change is agreeing on the UI state pattern earlier, because the first few screens did it three different ways."

Red flag to avoid:

A story about a rewrite that froze features for months, or a migration done alone without the team agreeing on the pattern.

They may ask next:
  • How did you convince the team and your lead it was worth the time?
  • What did you decide never to migrate, and why?
Say it in 60 seconds

Performance and Stability 4 questions

Medium Technical round Fresher, Mid-level, Senior Practice question

25. What are the usual ways an Android app leaks memory, and how do you find a leak once you suspect one?

What the interviewer is really testing:
Whether you know the classic leak patterns around activities and views, and have a concrete way to prove and fix one.
Answer frame:

Pattern: something long-lived holds a reference to something short-lived.

Common cases: static or singleton holding an activity, listeners never removed, fragment view binding kept after the view dies.

Finding: LeakCanary in debug builds, heap dumps in the memory profiler.

Fixing: application context, unregister in the matching callback, clear references.

Sample spoken answer:

"A leak on Android is almost always something long-lived holding a reference to something short-lived, usually an activity or a view. The classic cases are a singleton or companion object keeping an activity context, a listener or callback registered with a system service and never unregistered, an inner class or long-running job capturing the activity, and in fragments, keeping the view binding after onDestroyView, because the fragment outlives its view. To find one, I run LeakCanary in debug builds, which dumps the heap after a screen is destroyed and shows the chain of references keeping it alive. For harder cases I take a heap dump in the Android Studio memory profiler. Fixes are usually small: use the application context for long-lived things, unregister in the matching lifecycle callback, and null the binding in onDestroyView."

Red flag to avoid:

Saying the garbage collector handles everything, or only ever finding leaks by waiting for out-of-memory crashes.

They may ask next:
  • How would you catch a new leak automatically before a release goes out?
  • Is holding the application context in a singleton ever a leak?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

26. What causes an ANR, and how would you track one down when it only shows up in production reports?

What the interviewer is really testing:
Whether you know that ANRs come from a blocked main thread and can work from traces to a root cause, not just guess.
Answer frame:

Cause: the main thread is blocked too long, for example an input event not handled within about five seconds.

Usual suspects: disk or network on main, lock waits, slow broadcast receivers, heavy startup work.

Evidence: Play Console ANR clusters, main thread stacks, exit reasons on the device.

Prevent: StrictMode in debug, move work off main, measure startup.

Sample spoken answer:

"An ANR means the main thread was blocked too long, for example an input event wasn't handled within about five seconds, or a broadcast receiver or service didn't finish in time. In production I start with the ANR clusters in the Play Console, which show the main thread's stack when it happened. The top of that stack tells me whether it was doing disk work, waiting on a lock, making a synchronous binder call or just doing too much. On newer Android versions the app can also read the exit reasons the system recorded for it, including the ANR trace. The common roots I've seen are database or SharedPreferences writes on main, a lock held by a background thread, and heavy work at startup. In debug builds I turn on StrictMode so disk and network on the main thread get caught before release."

Red flag to avoid:

Treating ANRs like crashes you can reproduce on your own phone, or guessing at fixes without looking at the main thread trace.

They may ask next:
  • The main thread stack shows it waiting on a lock. What do you look at next?
  • Why can an ANR happen even when your own code never touches the disk on the main thread?
  • How would you measure and cut app startup time?
Say it in 60 seconds
Hard Behavioral round Mid-level, Senior Practice question

27. Tell me about a crash or ANR in a live Android app that you traced to its real root cause. How did you get there?

What the interviewer is really testing:
Whether you debug from evidence, like stack traces, device data and reproduction, and fix causes rather than symptoms.
Answer frame:

Situation: the app, the symptom and how many users it hit.

Evidence: what the traces and device data showed.

Root cause: the real reason, not the first guess.

Fix and guard: the change, how you verified it, what you added to stop a repeat.

Sample spoken answer:

"At my last company, after a release our crash reports showed an IllegalStateException in a fragment transaction, mostly on older, low-memory phones. My first guess was a missing null check, but the stacks showed it happened after the activity had saved its state. Grouping the reports by device and by the steps before the crash, I found the pattern: users opened a payment screen, switched to their banking app, and our network callback then tried to show a dialog fragment while we were in the background. I reproduced it by starting a slow payment call and switching apps before it finished. The fix was to move the result into ViewModel state and show the dialog only when the screen was started again. I added a test for that path, and the crash disappeared from the next release."

Red flag to avoid:

A story where the fix was wrapping the code in try-catch, or where you never found out why it happened.

They may ask next:
  • What did you rule out before you found the real cause?
  • How did you confirm the fix worked in production and not just on your device?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

28. A product manager says the main list stutters on cheaper phones. How would you investigate before changing any code?

What the interviewer is really testing:
Whether you measure on the right device and build before optimising, and know where list jank usually comes from.
Answer frame:

Reproduce: a real low-end device, a release build, the same data.

Measure: frame timing, a system trace, recomposition counts or bind times.

Usual causes: heavy binding, oversized images, deep layouts, work on the main thread while scrolling.

Verify: re-measure after each change, and add a benchmark.

Sample spoken answer:

"First I'd reproduce it properly: on a low-end phone, with a release build, because debug builds, and Compose in particular, are much slower and can send me after the wrong problem. Then I'd measure instead of guessing. I'd record a system trace while scrolling to see which frames miss their deadline and what the main thread is doing in them. For a RecyclerView list I'd look at time in onBindViewHolder and layout. For Compose, I'd check recomposition counts in Layout Inspector. The usual culprits are loading full-size images into small thumbnails, formatting dates or parsing text during binding, deep nested layouts, and database or disk work landing on the main thread while scrolling. Once I've fixed the top cause, I'd re-measure and add a macrobenchmark for scrolling so it doesn't quietly get worse again."

Red flag to avoid:

Jumping straight to rewriting the list or adding caching everywhere without measuring on the device that has the problem.

They may ask next:
  • What is a baseline profile, and how could it help here?
  • How would you explain the fix and its effect to the product manager?
Say it in 60 seconds

Testing and Release 4 questions

Medium Technical round Fresher, Mid-level, Senior Practice question

29. How do you unit test a ViewModel that launches coroutines and talks to a repository?

What the interviewer is really testing:
Whether you can write fast, reliable JVM tests for coroutine code by controlling dispatchers and using fakes.
Answer frame:

Fakes: a fake repository you control instead of the real network.

Main dispatcher: replace it with a test dispatcher through a JUnit rule.

runTest: virtual time, so delays don't slow the test.

Assert on state: check the exposed state or collect the flow.

Sample spoken answer:

"I test the ViewModel as a plain JVM unit test, with no device. The repository is an interface, so I pass in a fake whose results I control, like returning data or throwing an IOException. viewModelScope runs on the Main dispatcher, which doesn't exist on the JVM, so I use a small JUnit rule that swaps Main for a test dispatcher before each test and resets it after. The test body runs inside runTest, which uses virtual time, so a delay or debounce doesn't make the test slow. Then I call the ViewModel's function and assert on the state it exposes. For flows that emit several values in order, I collect them in the test and check the whole sequence. If a class uses IO or Default, I inject the dispatcher so the test can replace it."

Code:
class MainDispatcherRule(
    val dispatcher: TestDispatcher = UnconfinedTestDispatcher()
) : TestWatcher() {
    override fun starting(description: Description) = Dispatchers.setMain(dispatcher)
    override fun finished(description: Description) = Dispatchers.resetMain()
}

class ProfileViewModelTest {
    @get:Rule val mainRule = MainDispatcherRule()

    @Test fun showsErrorWhenLoadFails() = runTest {
        val vm = ProfileViewModel(FakeProfileRepository(fail = true))
        vm.load()
        assertEquals(UiState.Error, vm.state.value)
    }
}
Red flag to avoid:

Adding Thread.sleep to wait for coroutines, or mocking so much that the test only checks that mocks were called.

They may ask next:
  • What's the difference between StandardTestDispatcher and UnconfinedTestDispatcher?
  • What would you test with instrumented tests instead of JVM tests?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

30. Walk me through getting a release build onto the Play Store safely, from the build itself to the full rollout.

What the interviewer is really testing:
Whether you know what changes in a release build, how signing and bundles work, and how to release gradually so a bad build reaches few people.
Answer frame:

Build: App Bundle, R8 shrinking and obfuscation, test the release variant itself.

Signing: upload key locally, app signing key held by Play.

Tracks: internal, then closed testing, then a staged production rollout.

Watch: crash and ANR rates, halt if needed; fixes ship as a new version.

Sample spoken answer:

"I build an Android App Bundle for the release variant with R8 turned on, which shrinks and obfuscates the code, and I test that exact build, because R8 can strip classes used through reflection, which only shows up as a crash in release. I keep the mapping file so crash stack traces can be read. The bundle is signed with our upload key, and Play holds the real app signing key, so a lost upload key can be reset. I bump the versionCode, then push to the internal testing track, then a closed group, then production as a staged rollout to a small share of users. I watch the crash and ANR rates in Android vitals for a day or two before widening it. If something's wrong I halt the rollout, but people who already updated keep it, so the fix goes out as a new version."

Red flag to avoid:

Testing only debug builds, or thinking you can roll users back to the previous version from the console.

They may ask next:
  • What would you do if R8 breaks your JSON parsing in release only?
  • How do you feature-flag a risky change so you can turn it off without a new build?
Say it in 60 seconds
Medium Behavioral round Fresher, Mid-level, Senior Practice question

31. Tell me about a bug that only happened on some users' devices, never on yours. How did you track it down?

What the interviewer is really testing:
Whether you think about device fragmentation, OS versions and manufacturer behaviour, and have a method when you can't reproduce something.
Answer frame:

Symptom: what users saw and which devices.

Narrowing: grouping reports by OS version, manufacturer or settings.

Cause: the device or system difference behind it.

Fix: the change and how you tested on the affected devices.

Sample spoken answer:

"In a fitness app, some users said their reminder notifications never arrived, but they always worked on my phone. The reports clustered on a couple of manufacturers, so I borrowed a device from a colleague and found its battery saver was stopping our app completely in the background, which also removed our scheduled alarms. On top of that, a few users on newer Android versions had never granted the notification permission, because we only asked for it deep inside settings. We fixed it in two parts: we asked for the notification permission at the moment users turned reminders on, and added a short help screen explaining how to exclude the app from battery saving on those phones. After that, the complaints dropped to almost none, and we added those device models to our test list."

Red flag to avoid:

Closing the report as 'cannot reproduce' without looking at which devices or OS versions were affected.

They may ask next:
  • How do you decide which devices go on your test list?
  • What would you do if you couldn't get hold of an affected device at all?
Say it in 60 seconds
Hard Situational round Mid-level, Senior Practice question

32. Your staged rollout is live and crash reports suddenly spike on one manufacturer's phones. What do you do in the first hour?

What the interviewer is really testing:
Whether you protect users first, then investigate calmly, and communicate clearly while you do it.
Answer frame:

Contain: halt the rollout and switch off the feature flag if one exists.

Tell people: a short note to the team and support.

Investigate: stack traces, device models, OS versions, what changed in this build.

Fix forward: a new build with a higher version, through testing again.

Sample spoken answer:

"First I'd halt the rollout in the Play Console so no more users get the build. If the new code sits behind a remote feature flag, I'd switch it off too, since that helps people who already updated. Then I'd post a short note to the team and support: what we're seeing, that the rollout is paused, and when I'll update them next. After that I'd dig in. I'd look at the stack traces, the device models and OS versions, and compare against what changed in this build, maybe a new library version or a camera or media API that behaves differently on that manufacturer. If I can't get the device, a cloud device lab often has it. The fix goes out as a new build with a higher version code, through internal testing first, and I'd add that device to our test list."

Red flag to avoid:

Starting to debug while the rollout keeps going, or keeping the team and support in the dark until the fix is ready.

They may ask next:
  • The crash is inside a third-party library. What are your options?
  • When would you keep the rollout going despite some crashes?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

The questions above are the prep. The call has ten more.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.

Download ClapAssist with 10 free minutes
Mac and Windows · Stays out of screen share · No card