© Interview RuntimeThis content is copyright. Copying is disabled; you may quote a short excerpt with a link to the page.

The follow-up decides the senior mobile interview

An interviewer's view of the moment one fact changes, and why that moment carries the offer.

A timeline diagram of an interview: the first answer continues as a grey line while a follow-up branch splits off in gold and carries to the decision point.

In short

  • In a senior mobile interview, a correct first answer is the entry fee; the score comes from the follow-up.
  • A follow-up question changes one fact and watches whether your design changes for the right reason.
  • Interviewers can only defend what they wrote down, so the debrief rewards stated trade-offs, not recalled facts.
  • More questions from a question bank train the first answer, which is the part that was already passing.
  • Practice the follow-up directly: answer, find the one gap, then answer again after one fact changes.

A senior mobile interview is rarely decided by your first answer. It is decided a minute later, when the interviewer changes one fact and asks what happens now. I have conducted more than 500 technical interviews from the hiring side, from early career screens through staff and engineering manager loops, and the pattern holds across all of them: the first answer gets you into the conversation, and the follow-up decides where the conversation ends.

This article is about that minute. What the interviewer is looking for, what they can write down and defend later, and how to practice for it, because it is the one part of the loop most preparation skips.

What is a follow-up question in a senior mobile interview?

A follow-up question is a change to one fact of a problem you have already solved, asked to see whether your solution changes for the right reason. The network becomes slow. Two callers arrive at once. The app is killed mid-write. The team grows from four engineers to forty.

The first question tests whether you know the material. The follow-up tests whether your answer was a decision or a recitation. A recited answer has no joints: when one fact moves, the whole thing either stays rigid or collapses. A decided answer has visible joints. You can point at the part that depended on the fact that changed and say what replaces it.

When I interview at the senior level, I plan the follow-ups before the loop. The opening question is often generic on purpose. It is the scaffolding the follow-ups hang on.

What the interviewer writes down

An interviewer does not score you in the room. They score you later, from notes, often hours later, and then they defend that score to people who were not in the room. That constraint shapes everything. If it is not in the notes, it did not happen.

Here is what makes it into my notes during a follow-up, roughly in the order I look for it:

  • Did the candidate name what broke? "The cache is fine, but two requests for the same URL both download" is a line I can quote. "I'd make it more thread-safe" is not.
  • Did the change stay proportional? Changing one fact should change one part of the design. A candidate who rewrites the whole thing has shown me they did not know which part carried the weight.
  • Was the trade-off stated out loud? Every fix costs something: memory, latency, complexity, a new failure mode. Naming the cost unprompted is the strongest single signal I record at the senior level.
  • How did they handle not knowing? "I'm not sure whether Task inherits actor isolation here, so I'd check that before relying on it" is a good line. Guessing with confidence is a bad one.

None of these are about the first answer. All of them are only observable after something changes.

Correct but not hireable

The most common no hire I have written at the senior level is not for a wrong answer. It is for an answer that was correct and then stopped. The candidate solves the problem cleanly, the interviewer pushes once, and the candidate repeats the original answer with more confidence, or adds a framework name, or says "it depends" and waits.

In the debrief, that reads as correct but not hireable: the candidate knew the material but gave no evidence of judgment when the material stopped being enough. For a mid-level role that can still be a hire. For a senior or staff role it usually is not, because the job is mostly the follow-up. Requirements change, the backend team changes a contract, a crash rate moves. Nobody at that level is paid to recite.

An iOS follow-up: the actor that was already correct

The opening is a standard senior iOS interview prompt: make an image cache safe to call from many tasks. Most candidates reach for an actor, which is a fine first answer. Then the follow-up arrives.

Asked in the loopYour cache is an actor now. Two views ask for the same URL at the same moment, and the image isn't cached yet. What happens?

The answer that loses the point

"It's an actor, so access is serialized. Only one call runs at a time, so there's no race."

The answer that keeps it

"Both download. The actor serializes access to its state, but it's reentrant: when the first call suspends at the await on the network, the second call runs, sees an empty cache, and starts its own download. The state is never corrupted, but the work is duplicated. I'd store the in-flight Task per URL so the second caller awaits the first one's result. The cost is that a failed download now fails both callers, which I think is right here, and that I have to clear the entry on failure or I'll cache the error forever."

The weak answer is true about data races and wrong about the question. The strong answer names reentrancy, proposes a proportional fix and states two costs without being asked.

import UIKit

actor ImageCache {
    private var images: [URL: UIImage] = [:]
    private var inFlight: [URL: Task<UIImage, Error>] = [:]

    func image(for url: URL) async throws -> UIImage {
        if let cached = images[url] { return cached }
        if let running = inFlight[url] { return try await running.value }

        let task = Task { try await Self.download(url) }
        inFlight[url] = task
        defer { inFlight[url] = nil }

        let image = try await task.value
        images[url] = image
        return image
    }

    private static func download(_ url: URL) async throws -> UIImage {
        let (data, _) = try await URLSession.shared.data(from: url)
        guard let image = UIImage(data: data) else {
            throw URLError(.cannotDecodeContentData)
        }
        return image
    }
}

The code is not what gets scored. What gets scored is the sentence "the actor is reentrant at the await". That sentence shows the candidate knows what an actor promises and what it does not, and it is exactly the line an interviewer writes down. The next follow-up is usually memory pressure or cancellation: if the first view disappears, should its cancellation cancel the shared download? A strong candidate says no, and explains why the shared task should outlive any single caller. The Swift concurrency questions article goes through more of these.

An Android follow-up: the flow that kept running

The Android interview version of the same shape: a ViewModel exposes a user profile that the repository streams from the database. The first answer is usually viewModelScope.launch { repo.observeProfile().collect { _state.value = it } } in init. It works. It survives rotation, because the ViewModel does. Then the fact changes.

Asked in the loopThe user sends the app to the background for twenty minutes. The profile flow is backed by a database observer and a network refresh. What is still running?

The answer that loses the point

"Nothing important. viewModelScope is cancelled when the ViewModel is cleared, so there's no leak."

The answer that keeps it

"Everything is still running. The ViewModel isn't cleared when the app is backgrounded, so the collector in init keeps the upstream alive: the database observer, and the refresh if it's part of that flow. It's not a leak in the memory sense, it's wasted work and battery. I'd turn it into a StateFlow with stateIn and WhileSubscribed(5_000), and collect it in the UI with collectAsStateWithLifecycle. The upstream stops five seconds after the last collector goes away, which covers a rotation without restarting the query. The cost is that returning from the background restarts the upstream, so the first frame shows the last cached value while it reloads."

"No leak" answers a question nobody asked. The strong answer separates memory leaks from work that outlives its screen, and explains the 5 second number instead of copying it.

class ProfileViewModel(repo: ProfileRepository) : ViewModel() {

    val profile: StateFlow<ProfileUiState> = repo.observeProfile()
        .map { ProfileUiState.Loaded(it) }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = ProfileUiState.Loading,
        )
}

@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
    val state by viewModel.profile.collectAsStateWithLifecycle()
    ProfileContent(state)
}

Again, the code is secondary. The line in my notes is "distinguished ViewModel lifetime from screen lifetime and chose the stop timeout on purpose". The Kotlin coroutines questions article covers the follow-ups that come after this one, such as what happens when the refresh throws.

How the debrief and leveling decide the offer

After the loop, each interviewer submits written feedback and a recommendation, usually before seeing anyone else's. Then the panel, or a hiring committee, reads all of it together. Two decisions come out: hire or no hire, and at what level.

The second decision is where the money is. Senior and staff mobile roles at large US tech companies commonly carry total compensation in the $300k to $400k range, and the level decides most of that number. levels.fyi publishes per-company data if you want to see the spread for the companies you are targeting. A loop that ends at senior instead of staff is a different offer, not a smaller version of the same one.

Leveling is argued from evidence in the written feedback. "Knew coroutines well" supports a hire. "Identified that the flow outlived the screen, chose WhileSubscribed and named the restart cost" supports a level. The second line only exists if a follow-up produced it. Interviewers who want to argue for a higher level need quotable moments, and those moments come almost entirely from pushback. The senior or staff signals article covers what separates the two levels in more detail.

One more thing about the debrief: disagreement is resolved by the strongest notes, not the strongest opinion. If one interviewer writes "solid" and another writes three specific trade-offs you named under pressure, the specific notes win. Your job in the room is to give every interviewer something specific to write.

Why more questions from a question bank do not fix it

The instinct after a rejection is to study more questions. For senior candidates this usually trains the wrong muscle. A question bank gives you first answers. First answers were already passing. What failed was the second minute, and a list of questions has no second minute.

It also builds a specific habit that interviewers notice: pattern matching to the nearest memorized answer. When the follow-up changes a fact, a candidate trained on banks reaches for a different memorized answer instead of modifying the one on the table. From my side of the table, that looks like the candidate abandoning their own design, which is worse than defending it badly.

The answer I hear most often from well-prepared candidates who still get a no is fluent, complete and fixed in place. They covered every point in the standard answer. They could not say which point mattered when the constraint moved.

How to practice the follow-up

Practice the follow-up as its own skill. The loop I recommend has three steps, and the third is the one people skip.

  1. Answer first, out loud, with a timer. Two to three minutes, as you would in the room. Write it down or record it. A first answer you only thought through is not a first answer.
  2. Find the one gap. Not every weakness, one. The single assumption that, if it changed, would break the most of your answer. For the cache it was "only one caller per URL". For the ViewModel it was "the screen and the ViewModel live equally long".
  3. Change that fact and answer again. Out loud, without restarting from scratch. The goal is a changed answer that keeps everything that still holds, names what replaced the broken part and states what the change costs.

Then do it again with a different fact. Three or four rounds on one question teach more than twenty new questions, because every round is practice at the part that gets scored.

Facts worth changing

If you cannot find the gap, change one of these and see what breaks: concurrency (two callers at once), lifetime (the owner outlives or dies before the work), failure (the call throws halfway), scale (the data is 100 times larger), connectivity (offline, then a conflicting edit), and team size (forty engineers now touch this module). These six cover most of the follow-ups I have asked in mobile loops. Offline conflicts and team size tend to come up in system design and architecture rounds; the offline-first sync article works through one in full.

Practice with someone who pushes back

Self-practice finds most gaps. A second person finds the ones you are blind to, because they do not share your assumptions. If you run a mock, ask the other person not to grade your first answer at all. Ask them to change one fact and write down what you said next. That note is the closest thing you will get to the interviewer's notes before the real loop.

I ran two annotated mocks with a colleague before my onsite, and I got the offer.
Daniel K., Senior iOS Engineer, early reader of Share Your Screen

Practice material built around the follow-up

I built Interview Runtime around this exact loop. Each question comes with a first answer, the follow-ups an interviewer is likely to ask, the weak answer that loses the point, and the strong answer with the trade-off stated. There are two editions: iOS Interview Training for senior and staff iOS roles, and Android Interview Training for the same levels on Android.

You can read a free sample before deciding: the iOS sample and the Android sample. It is preparation, not a guaranteed outcome. What it gives you is practice at the minute that decides the loop.

Questions readers ask

© 2026 Mike Salari, Interview Runtime. All rights reserved. Quoting a short excerpt with a link to this page is welcome; republishing the article, in full or in part, is not.

Practice the follow-up, not the first answer.

Interview Runtime is the interview book plus 90 days of Guided Practice: you answer first, see the one gap that would cost the point, then answer again when one fact changes.