Meta Mobile Developer (Entry Level) - Comprehensive Interview Preparation Guide
Meta's interview process for entry-level mobile developers consists of an initial recruiter screening followed by 1-2 technical phone screens with coding assessments. Successful candidates advance to the onsite stage (typically 4 rounds) featuring Meta's AI-enabled multi-part coding interview, system design fundamentals, mobile development technical assessment, and behavioral evaluation. The entire process spans 4-8 weeks and emphasizes problem-solving ability, code quality, mobile platform knowledge, technical communication, and cultural fit.
Interview Rounds
Recruiter Screening
What to Expect
Initial conversation with Meta recruiter lasting 20-30 minutes. This is a non-technical round focused on understanding your background, career motivation, and cultural fit. The recruiter will walk you through the interview process, confirm your interest in mobile development, and address logistical questions. They'll also gather information about your experience with iOS and/or Android development, your familiarity with cross-platform frameworks, and your reasons for applying to Meta. This round determines whether you advance to technical phone screens.
Tips & Advice
Be genuine and enthusiastic about mobile development and Meta's products. Prepare a 2-minute summary of your background, focusing on mobile development experience if you have it. Research Meta's mobile products (Instagram, WhatsApp, Messenger, Threads) and mention genuine interest in one or two. Ask thoughtful questions about the team, role expectations, and mobile technology stack at Meta. Have a Metacareers account created before this call. Clarify whether the role emphasizes iOS, Android, or cross-platform development.
Focus Topics
Clarifying Role Focus
Ask and understand whether the role focuses on iOS, Android, React Native, or a mix of platforms
Practice Interview
Study Questions
Mobile Development Experience
Overview of any iOS, Android, or cross-platform projects you've built or contributed to
Practice Interview
Study Questions
Career Motivation and Background Story
Concise explanation of your professional journey and why you're interested in mobile development
Practice Interview
Study Questions
Why Meta Specifically
Articulate reasons for applying to Meta over other companies; reference Meta's mobile products and mission
Practice Interview
Study Questions
Technical Phone Screen 1 - Coding Fundamentals
What to Expect
First technical interview lasting approximately 45 minutes conducted over phone/video with a Meta engineer. Expect 5-10 minutes of introductions and behavioral questions, followed by 35-40 minutes of coding. You'll solve one to two LeetCode-style problems of easy to medium difficulty, often related to arrays, strings, linked lists, or basic algorithms. The interviewer will assess your ability to clarify requirements, write clean code, think through edge cases, and communicate your approach. You're expected to write and execute working code. The session may touch on mobile-specific context (e.g., sorting a list of app names, handling API response data structures) but fundamentals are the primary focus.
Tips & Advice
Practice LeetCode problems at easy-to-medium difficulty; aim to solve 30-50 problems covering arrays, strings, hash maps, and basic graph traversal. Write code that is clean, readable, and well-structured—variable names matter. Always clarify the problem statement before coding: ask about input constraints, edge cases, and expected output format. Speak through your thought process but avoid over-narration. Test your code mentally or by walking through examples before submitting. If stuck, communicate your block to the interviewer—they appreciate transparency. You don't need to be perfectly fast; accuracy and communication matter more. Have a whiteboard or paper nearby to sketch out ideas if it helps your thinking.
Focus Topics
Problem Clarification and Communication
Asking clarifying questions about constraints, edge cases, and output format before coding
Practice Interview
Study Questions
Edge Case Identification
Recognizing and handling boundary conditions like empty inputs, single elements, negative numbers, and duplicates
Practice Interview
Study Questions
Basic Algorithm Patterns
Common patterns like two pointers, sliding window, binary search, and BFS/DFS
Practice Interview
Study Questions
Array and String Manipulation
Operations like searching, sorting, filtering, and transforming arrays and strings
Practice Interview
Study Questions
Hash Maps and Dictionaries
Using key-value pairs for fast lookups, grouping, and deduplication
Practice Interview
Study Questions
Technical Phone Screen 2 - Coding Problem-Solving
What to Expect
Second technical interview lasting approximately 45 minutes, conducted over phone/video with a different Meta engineer. Similar format to Phone Screen 1: 5-10 minutes introductions, followed by 35-40 minutes of coding. This screen typically features a medium-difficulty problem that may involve slightly more complex data structures or algorithms (e.g., linked lists, basic trees, or problems requiring nested loops). The interviewer assesses your ability to optimize solutions, handle complex scenarios, and articulate trade-offs. You may be asked to improve a brute-force solution or extend a problem with new requirements. Focus on code quality, clarity, and the reasoning behind your approach.
Tips & Advice
After Phone Screen 1, continue practicing medium-difficulty LeetCode problems. Focus on problems involving linked lists, trees, and problems that require optimization (e.g., improving time complexity from O(n²) to O(n log n)). Understand trade-offs between time and space complexity; be ready to discuss why you chose a particular approach. If asked to optimize, always explain what you're optimizing for and why. Practice writing code that's easy to extend—if the interviewer adds a constraint mid-problem, your code structure should adapt cleanly. Use meaningful variable names and add brief comments for complex logic. Stay calm if you don't immediately know the optimal solution; demonstrating your thought process is valuable.
Focus Topics
Code Extensibility
Writing code that handles requirement changes without major refactoring
Practice Interview
Study Questions
Linked List Operations
Traversal, reversal, cycle detection, and merging of linked lists
Practice Interview
Study Questions
Solution Optimization
Improving brute-force solutions and making trade-off decisions between time and space
Practice Interview
Study Questions
Tree Traversal and Basic Tree Problems
In-order, pre-order, post-order traversal; finding height, LCA, and path sums
Practice Interview
Study Questions
Time and Space Complexity Analysis
Understanding Big-O notation and analyzing your solution's efficiency
Practice Interview
Study Questions
Onsite Round 1 - AI-Enabled Multi-Part Coding Interview
What to Expect
In-person or virtual coding interview lasting 60 minutes, conducted with a Meta engineer. This is Meta's signature AI-enabled coding round where you work through one multi-part problem structured into 2-3 stages. The problem often involves building or extending functionality from scratch or working with a partial implementation. For mobile developers, problems might involve app logic like building a feature within an existing codebase (e.g., adding filtering to a list, implementing a cache, or handling state). You have access to AI assistance (like Copilot in CoderPad) but using it is optional—Meta evaluates your ability to think critically and make informed decisions, not how much you use AI. You're expected to run code, read test failures, debug in real time, and iterate. The interviewer observes how you understand existing code, extend systems, and ensure solutions meet requirements.
Tips & Advice
This round rewards running and iterating over perfection. Start by understanding the problem deeply: restate it in your own words, clarify inputs/outputs/constraints, and identify edge cases. Create a visible requirements checklist before coding. Use the AI as a scaffolding tool—ask it to generate boilerplate or test templates, but always review and understand the generated code. Run your code early and often; don't wait until the end. When tests fail, read the failure messages carefully and debug methodically. Communicate your next steps while the AI generates code. After runs, summarize what passed/failed and what you'll fix. Mention complexity and acknowledge trade-offs. Practice this round format extensively before your interview; the ability to iterate and debug in real-time is critical.
Focus Topics
Technical Communication and Reasoning
Explaining your approach, discussing trade-offs, asking clarifying questions, and incorporating feedback
Practice Interview
Study Questions
Code Quality and Maintainability
Writing clean, readable, well-structured code with meaningful names; evaluating and improving code quality
Practice Interview
Study Questions
Debugging and Iterating
Reading error messages, identifying bugs, fixing code in real-time, and re-running tests
Practice Interview
Study Questions
Code Navigation and Understanding Existing Codebases
Reading and building on partial implementations, understanding code structure, and extending working code
Practice Interview
Study Questions
Writing and Running Tests
Creating test cases covering golden path and edge cases, running tests, and reading test output
Practice Interview
Study Questions
Problem Clarification and Requirements Definition
Restating problems, identifying inputs/outputs, constraints, edge cases, and defining clear requirements checklist
Practice Interview
Study Questions
Onsite Round 2 - System Design Fundamentals
What to Expect
In-person or virtual interview lasting 45-60 minutes with a Meta engineer or architect. This round assesses your understanding of how systems and applications are designed at scale. For entry-level mobile developers, expect questions focused on foundational system design concepts as they apply to mobile apps—for example, designing a feature like a newsfeed, caching strategy for an image-heavy app, or how a mobile app communicates with backends. You'll discuss trade-offs, scalability considerations, and mobile-specific challenges (network latency, battery efficiency, offline-first design). The interviewer is not expecting you to design Netflix infrastructure; rather, they want to see that you understand how mobile clients fit into larger systems and can reason about basic architectural decisions. You should be able to sketch diagrams (on whiteboard or virtual drawing tool) and articulate why certain choices matter for mobile platforms.
Tips & Advice
Study basic system design principles: client-server architecture, APIs, caching, databases, and load balancing. For mobile-specific design, focus on: how mobile apps fetch and cache data, offline-first strategies, network optimization, battery efficiency, and security. Draw and explain diagrams clearly—the visual communication is important. Practice discussing trade-offs: consistency vs availability, latency vs bandwidth, battery life vs feature richness. Familiarize yourself with common mobile architecture patterns (MVC, MVP, MVVM) and why they matter. When given a problem, start by asking clarifying questions about scale (number of users, devices, data volume), requirements (latency, consistency, security), and constraints. At entry-level, don't overcomplicate your design; focus on clarity and explaining fundamental concepts. Research Meta products (Instagram Stories, Messenger) and understand at a high level how they likely work—this shows genuine interest.
Focus Topics
Basic Architecture Patterns (MVC/MVP/MVVM)
Structural patterns for organizing mobile app code, separation of concerns, and testability
Practice Interview
Study Questions
Mobile Network Optimization
Minimizing network requests, batching requests, handling network latency, and offline-first design
Practice Interview
Study Questions
Battery Efficiency and Performance
Impact of background tasks, location services, push notifications, and how design affects battery drain
Practice Interview
Study Questions
Caching Strategies for Mobile
In-memory caching, disk caching, cache invalidation, and trade-offs for resource-constrained devices
Practice Interview
Study Questions
Client-Server Architecture and APIs
How mobile apps communicate with backends, REST vs GraphQL basics, request/response patterns
Practice Interview
Study Questions
Onsite Round 3 - Mobile Development Technical Assessment
What to Expect
In-person or virtual interview lasting 45-60 minutes with a mobile platform specialist (iOS or Android engineer, or React Native/Flutter expert depending on the role focus). This round dives deep into mobile platform-specific knowledge and problem-solving. You may be asked to write platform-specific code (Swift for iOS, Kotlin for Android, or JavaScript/TypeScript for React Native), discuss lifecycle management, handle asynchronous operations, manage state, implement UI components, or debug common mobile issues. You might work on a small project like building a simple screen with a network request, handling user input, or integrating a third-party SDK. The focus is on your practical mobile development skills, understanding of the platform you're specializing in, and ability to build features end-to-end within the mobile environment. For entry-level, the expectations are foundational: you should know your platform's fundamentals, common patterns, and be able to write working code.
Tips & Advice
Deepen your expertise in your primary platform (iOS, Android, or cross-platform framework). If iOS: master Swift syntax, UIKit or SwiftUI basics, view controllers, delegation, and common design patterns. If Android: master Kotlin syntax, Activity and Fragment lifecycle, RecyclerView, coroutines, and modern Android architecture. If React Native: master JavaScript/TypeScript, React hooks, navigation, and platform-specific APIs. Practice writing small projects end-to-end: build a screen that fetches data from a mock API, displays it in a list, handles errors, and responds to user interactions. Understand and be able to explain: your platform's threading model, how to handle asynchronous operations, state management approaches, and common performance pitfalls. Know how to use debugging tools on your platform. Be familiar with the app store deployment process (basics like versioning and signing). For entry-level, you're not expected to be expert-level, but you should be fluent in fundamentals and eager to learn.
Focus Topics
Performance and Battery Optimization
Writing efficient code, avoiding memory leaks, and minimizing battery drain
Practice Interview
Study Questions
State Management
Managing application state, handling data flow, and persisting user data locally
Practice Interview
Study Questions
Mobile-Specific Features (Push Notifications, Camera, Location, Storage)
Implementing platform capabilities and integrating third-party SDKs
Practice Interview
Study Questions
Networking and API Integration
Making HTTP requests, parsing JSON responses, handling errors, and working with RESTful or GraphQL APIs
Practice Interview
Study Questions
Asynchronous Programming and Threading
Handling network requests, background tasks, and managing main thread vs background threads
Practice Interview
Study Questions
Platform Fundamentals (iOS: Swift, UIKit/SwiftUI; Android: Kotlin, Activity/Fragment; Cross-platform: React Native/Flutter)
Language syntax, core framework concepts, view/component lifecycle, and basic UI development
Practice Interview
Study Questions
Onsite Round 4 - Behavioral and Cultural Fit Interview
What to Expect
In-person or virtual interview lasting 30-45 minutes with a Meta manager, team lead, or senior engineer. This round assesses your cultural alignment, teamwork, communication, and past experience. Expect behavioral questions about challenges you've faced, how you handle feedback, collaboration with teammates, conflict resolution, and motivation. The interviewer will ask about your career goals, why Meta, and what you're looking for in a team. This is your opportunity to demonstrate Meta values: Focus on Impact, Move Fast, Be Direct, Build What the World Needs, and Live the Mission. Prepare concrete examples from your education, internships, or projects that show these values in action. For entry-level, interviewers understand you're early in your career; they're evaluating your ability to learn, collaborate, and grow. They're also assessing whether you'll thrive in Meta's fast-paced environment.
Tips & Advice
Prepare 5-7 concrete stories using the STAR method (Situation, Task, Action, Result) that demonstrate Meta's core values. Examples: a time you delivered something fast despite constraints (Move Fast), a time you gave or received critical feedback (Be Direct), a project where you focused on user impact (Focus on Impact), or a time you collaborated cross-functionally to solve a problem. Research Meta's mobile products and understand how they impact billions of users (Build What the World Needs). Practice telling your stories out loud to make them sound natural, not rehearsed. Be authentic and honest—interviewers can detect insincerity. For entry-level, it's okay to draw examples from school projects, internships, or personal projects if you don't have extensive professional experience. Show genuine curiosity about the team, product, and culture. Ask thoughtful questions about the team's goals, how they measure impact, and what success looks like. Avoid generic questions. Listen carefully to the interviewer and adapt your answers to what they're looking for.
Focus Topics
Learning and Growth Mindset
Examples of learning new skills, adapting to feedback, and taking on stretch challenges
Practice Interview
Study Questions
Teamwork and Collaboration
Examples of working effectively with designers, backend engineers, product managers, or teammates; handling disagreements constructively
Practice Interview
Study Questions
Meta Core Value: Be Direct
Stories about giving or receiving honest feedback, having difficult conversations, and valuing transparency
Practice Interview
Study Questions
Meta Core Value: Focus on Impact
Projects where you prioritized user outcomes over technical perfection; examples of shipping features that mattered
Practice Interview
Study Questions
Meta Core Value: Move Fast
Examples of shipping quickly, making decisions under uncertainty, and iterating based on feedback
Practice Interview
Study Questions
Tell Me About Yourself
Concise personal narrative covering background, key accomplishments, and career focus on mobile development
Practice Interview
Study Questions
Frequently Asked Mobile Developer Interview Questions
In sparse vector manipulation you may want to move zeros to the end while keeping relative order of nonzero elements. Given an integer array, implement a Python in-place algorithm that moves all zeros to the end. Example: input = [0,1,0,3,12] -> [1,3,12,0,0]. Explain how this helps when compressing sparse representations.
Sample Answer
Direct answer
Use a single read/write pointer pass: keep a write index starting at 0, and for every element you read that is nonzero, swap it into position write and advance write. By the time the scan finishes, every nonzero element has been moved (in its original relative order) into the front of the array, and every zero has been pushed toward the back, all without any extra array. This is O(n) time and O(1) extra space, a single linear pass with two pointers moving at different speeds.
Structured elaboration
Why a two-speed pointer pair works. The read pointer visits every element exactly once. The write pointer only ever advances when a nonzero element is found, so it always points at the next open slot for a nonzero value. Because write <= read at all times, swapping nums[read] into nums[write] never overwrites an element read hasn't looked at yet; the elements between write and read (if any gap has opened up) are exactly the zeros collected so far, and the swap places one of them further back, which is what keeps zeros trailing.
Why relative order among nonzero elements is preserved. Nonzero elements are written into the front of the array in the exact order the read pointer encounters them, since each one gets a swap into the next available write slot in sequence. No nonzero element ever gets moved past another nonzero element, which is the "keeping relative order" requirement in the question.
Connection to sparse-vector compression. A sparse vector's useful information is its nonzero entries and their positions; long runs of zeros carry no information beyond "there are this many." Once all nonzero values have been compacted to a contiguous prefix of the array (which is exactly what this routine produces), a sparse representation becomes cheap to derive: store just the compacted prefix (the nonzero values and, for a true sparse encoding, their original indices) plus a single count of how many trailing zeros were dropped, rather than storing every zero explicitly. In other words, this in-place partition is the mechanical first step that turns a dense array into something ready to be run-length-encoded or converted into a (value, index) sparse pair list, since it groups all the "real" data together and reduces the zero tail to a single number.
Worked example
Full runnable code with pinned test cases, including the question's own example:
def move_zeroes(nums):
"""In-place: move all zeros to the end, keep relative order of nonzero
elements. O(n) time, O(1) extra space. Single read/write pointer pass."""
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 1
return nums
if __name__ == "__main__":
tests = [
([0, 1, 0, 3, 12], [1, 3, 12, 0, 0]),
([0, 0, 0], [0, 0, 0]),
([1, 2, 3], [1, 2, 3]),
([0], [0]),
([], []),
([4, 0, 5, 0, 0, 6], [4, 5, 6, 0, 0, 0]),
]
for arr, expected in tests:
arr_copy = list(arr)
result = move_zeroes(arr_copy)
print(f"input={arr!r:20} -> {result!r:22} (expected {expected!r})")
Output (actual run):
input=[0, 1, 0, 3, 12] -> [1, 3, 12, 0, 0] (expected [1, 3, 12, 0, 0])
input=[0, 0, 0] -> [0, 0, 0] (expected [0, 0, 0])
input=[1, 2, 3] -> [1, 2, 3] (expected [1, 2, 3])
input=[0] -> [0] (expected [0])
input=[] -> [] (expected [])
input=[4, 0, 5, 0, 0, 6] -> [4, 5, 6, 0, 0, 0] (expected [4, 5, 6, 0, 0, 0])
Given [0, 1, 0, 3, 12] -> [1, 3, 12, 0, 0] from the last test above: the nonzero values 1, 3, 12 land at indices 0, 1, 2 in exactly the order they appeared, and the two zeros are pushed to indices 3 and 4. A sparse representation could then be stored as nonzeros=[1, 3, 12] (with original positions [1, 3, 4] recorded separately if needed) plus trailing_zero_count=2, instead of keeping five explicit slots.
Trade-offs and pitfalls
- Building a new array (copy nonzero elements into a fresh list, then append zeros) is simpler to write correctly on the first try, but costs O(n) extra space; naming it as a valid baseline before presenting the in-place two-pointer version shows you understand what the O(1)-space constraint is actually buying.
- A common bug: swapping unconditionally instead of only when
nums[read] != 0, which would just be an identity/no-op most of the time but risks a wasted swap whenread == write; the code above avoids extra work here since a self-swap is harmless, but it's worth being explicit that the swap is a genuine no-op in that case, not a source of a bug. - This partition preserves order at the cost of doing real swaps (not just overwrites), which is necessary specifically because order matters; if order among the nonzero elements didn't matter, a simpler two-pointer partition (like Dutch National Flag style) could do fewer writes.
- The "sparse compression" framing is a motivation for why this operation is useful, not an added algorithmic requirement: the core routine here is doing the compaction only, and a full sparse-vector encoder would be a separate downstream step (recording positions, applying run-length encoding to the zero tail) built on top of this compacted array.
- If the array holds
-0.0(a floating-point negative zero) rather than integers, remember that-0.0 == 0.0isTruein most languages, so it should still be treated as a zero for this purpose; the code above already handles this correctly since it uses!=for comparison rather than checking sign or type.
Describe the steps to register a mobile app for push notifications on both iOS and Android and obtain a device token. Explain how APNs device tokens and FCM registration tokens are obtained and refreshed, common failure modes, and how you would securely associate tokens with an authenticated backend user.
Sample Answer
Overview — steps to register (iOS & Android)
-
iOS (APNs)
- Enable Push Notifications and Background Modes in Xcode; upload APNs key/cert to your server or provider.
- Request user permission via UNUserNotificationCenter.requestAuthorization.
- Call UIApplication.shared.registerForRemoteNotifications().
- Implement application(_:didRegisterForRemoteNotificationsWithDeviceToken:) to receive the device token (Data), convert to hex string, and send to backend.
-
Android (FCM)
- Add Firebase to project, include google-services.json, and FirebaseMessaging dependency.
- Ensure correct manifest permissions and services.
- Use FirebaseMessaging.getInstance().getToken() (or onNewToken in FirebaseMessagingService) to obtain registration token and send it to backend.
How tokens are obtained & refreshed
- APNs: device token issued by APNs after registerForRemoteNotifications; it can change after app reinstall, OS update, or device restore — app should register at each launch and on relevant lifecycle events.
- FCM: getToken() returns current token; onNewToken called when token is rotated (app restore, reinstall, user clears app data). Always handle token refresh callbacks.
Common failure modes
- User denied notification permission
- Network errors when sending token to backend
- Incorrect APNs cert/key or bundle ID mismatch
- Expired/invalid FCM credentials or wrong Sender ID
- Token conversion bugs (truncation or encoding mistakes)
Securely associating tokens with authenticated users
- Require user authentication (e.g., OAuth/JWT) before accepting token updates.
- Send token over HTTPS with short-lived auth token; validate token on backend and tie to user ID.
- Store tokens hashed or encrypted at rest; keep mapping user -> device_id, platform, token, last_seen.
- Verify ownership: on token submit, include device fingerprint (UUID) and require authenticated session; optionally send a confirmation push to verify delivery.
- Revoke tokens on logout, account removal, or detected compromise; implement rate limits and audit logs.
Rewrite a dense, jargon-heavy sentence or short paragraph into a direct, plain-language version that keeps the meaning but removes filler words and unnecessary qualifiers.
Sample Answer
Direct answer
Read the sentence for what it is actually trying to say, restate that meaning in the fewest plain words, and remove verbal padding (filler words, unnecessary qualifiers, and jargon that doesn't add precision) rather than just shortening it mechanically.
Structured elaboration
- Separate meaning from wording first. Read the sentence and paraphrase its actual point out loud in your own words before touching the original text; this stops you from just deleting words from the existing structure and instead lets you rebuild a clean sentence.
- Remove filler and hedges: "um," "like," "you know," "sort of," "basically," "at the end of the day," and throat-clearing openers ("so, I mean").
- Remove unnecessary qualifiers that soften a claim without adding real uncertainty: "kind of important," "a little bit concerning," "somewhat unclear," when the writer actually means "important," "concerning," "unclear."
- Replace jargon with the plain-language equivalent only where the jargon isn't doing real precision work; keep a technical term if a more common word would actually lose meaning.
- Prefer active voice and a direct subject-verb-object order, which is usually both shorter and clearer than passive constructions. Active voice means the subject of the sentence does the action, for example "the team shipped the fix." Passive voice flips this around so the subject receives the action instead of doing it, and often hides or drops who actually did it, for example "the fix was shipped by the team" (actor still named, but buried at the end) or "the fix was shipped" (actor dropped entirely, so the reader can't tell who's responsible).
Worked example
Original: "So, um, basically what we're trying to do here is, like, sort of make the checkout flow a little bit faster, if that makes sense, because right now it's kind of slow for some users."
Rewrite: "We're speeding up checkout. It's currently slow for some users."
Word count drops from 35 words to 10, a 71% reduction, while the two facts (goal: faster checkout; problem: currently slow for some users) both survive intact. Everything removed was filler, hedging, or a qualifier that added no information.
Trade-offs and pitfalls
- Removing every qualifier can accidentally remove real uncertainty the speaker meant to convey; "somewhat unclear" sometimes genuinely means partially unclear, not fully unclear, so check whether the hedge was doing real work before deleting it.
- Jargon isn't always the enemy: "p95 latency" (the 95th-percentile response time) is more precise than "how fast it usually is," and rewriting it away for a technical audience would lose information, not just words.
- This is a skill best practiced by rewriting your own recent messages after the fact; it is much harder to self-edit in the moment than it is with a few minutes of distance.
Describe common concurrency and race conditions that occur when saving state in lifecycle callbacks (for example writing to disk in onPause while a background thread is mutating the same model). Propose robust patterns to avoid corruption and ensure crash-safe persistence on both Android and iOS.
Sample Answer
Situation / problem summary
Saving state in lifecycle callbacks (onPause/onStop on Android; applicationWillResignActive/applicationDidEnterBackground on iOS) is risky when background threads may mutate the same model: you can get race conditions, partial writes, corrupted files, or crashes if the process is suspended mid-write.
Common concurrency issues
- Read/write races: background thread mutates while lifecycle thread serializes -> inconsistent snapshot.
- Lost updates: two threads write concurrently and one overwrites the other.
- Partial/atomicity failure: write interrupted -> truncated file.
- Deadlocks: locking the UI thread during long IO causes ANR or watchdog termination.
- Ordering issues: background work scheduled after lifecycle callback leads to stale persistence.
Robust patterns to avoid corruption (platform-agnostic)
- Snapshot-before-write: create an immutable copy of model state and write that copy.
- Single writer principle: funnel all persistence through one serial/producer (single-threaded writer).
- Atomic file writes: write to temp file, fsync, then atomic rename/replace.
- Transactional DBs: use ACID DB (SQLite/Core Data) with transactions/WAL and background contexts.
- Coordination primitives: lightweight locks only around model copy; avoid blocking main thread.
- Crash-safe fsync: ensure data and metadata flushed when required for critical data.
Android-specific recommendations
- Use a single-threaded Executor / HandlerThread for all disk writes. Example:
// Kotlin: single-writer executor
private val io = Executors.newSingleThreadExecutor()
fun saveSnapshot(state: MyModel) {
val snapshot = state.copy() // immutable snapshot
io.execute {
val tmp = File(filesDir, "state.tmp")
tmp.writeText(serialize(snapshot))
tmp.sync() // extension to fsync
tmp.renameTo(File(filesDir,"state.json")) // atomic replace
}
}
- For SQLite/Room: perform writes via write transactions on background thread; enable WAL for durability.
- Avoid long blocking in onPause; schedule synchronous persistence on writer thread and wait only briefly if necessary (or use foreground service / lifecycle aware background task).
iOS-specific recommendations
- Use a serial DispatchQueue for file writes or use Core Data’s private queue context / performBackgroundTask.
- Use FileManager.replaceItemAt or write(to: options: .atomic) and call FileHandle.synchronizeFile() if needed.
let diskQueue = DispatchQueue(label: "com.app.disk")
func save(state: Model) {
let snapshot = state.snapshot()
diskQueue.async {
let tmp = url.appendingPathExtension("tmp")
try snapshotData.write(to: tmp, options: .atomic)
try FileManager.default.replaceItemAt(url, withItemAt: tmp)
}
}
- For Core Data: use performBackgroundTask, merge changes on main context; enable journaling and persistent store options for WAL.
Additional best practices
- Minimize work in lifecycle callbacks; trigger the writer so it owns durability, don't perform heavy IO on UI thread.
- Use versioning and checksums: write checksum/manifest to detect corruption and recover.
- Implement retry and recovery: on startup, detect temp files or incomplete writes and recover/rollback.
- Test with stress tests, signal termination and low-memory conditions, and verify atomicity across devices.
Outcome: funneling persistence through a single, background writer, writing immutable snapshots, and using atomic write/transaction primitives yields race-free, crash-safe persistence on both Android and iOS.
Explain certificate pinning strategies for mobile clients. Describe how to implement pinning on iOS (URLSession/Trust evaluation) and Android (OkHttp CertificatePinner), and discuss trade-offs such as pin rotation, enterprise TLS interception, and operational complexity. When is pinning appropriate and when should it be avoided?
Sample Answer
Approach summary
Describe runtime validation of server identity by embedding expected public-key hashes or leaf certs in the app and rejecting connections that don’t match. Implement on iOS via URLSession/TLS trust evaluation and on Android via OkHttp CertificatePinner.
iOS (URLSession / Trust evaluation)
- Use URLSessionDelegate’s urlSession(_:didReceive:completionHandler:) or evaluate serverTrust in URLSessionTaskDelegate.
- Example (Swift):
// in URLSessionDelegate
func urlSession(_ s: URLSession, didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let trust = challenge.protectionSpace.serverTrust!
// compute SHA256 of public key
// compare with stored base64 pins
if matchesPin(trust) { completionHandler(.useCredential, URLCredential(trust: trust)) }
else { completionHandler(.cancelAuthenticationChallenge, nil) }
}
Android (OkHttp CertificatePinner)
- Use OkHttp’s CertificatePinner with hostname and public-key SHA-256 pins:
val pinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAA...=")
.build()
val client = OkHttpClient.Builder().certificatePinner(pinner).build()
Trade-offs / operational issues
- Pin rotation: embed multiple pins (current + backup) and support server-side rollout; release app updates before retiring old pins.
- Enterprise TLS interception: pinning blocks corporate proxies; provide opt-outs only with explicit user/enterprise workflows.
- Complexity: increases release coordination, more fragile during cert changes, harder debugging for users.
- Security vs availability: strong protection against rogue CAs, but risk of app failures if pins stale.
When appropriate / when to avoid
- Use pinning for high-value apps (banking, health) where MITM risk is critical.
- Avoid in general-purpose apps with many third-party endpoints, or where enterprise interception must be supported; prefer standard TLS + monitoring (CT, HPKP deprecated) and short-lived certs/OCSP stapling instead.
Walk me through your career, starting with your first relevant role and ending with where you are today.
Sample Answer
Direct answer: Structure this as a small number of milestones (three to six), each with employer context, title, and approximate timeframe, one line on what you did, and the reason you moved to the next step. Open with a one-line frame of what connects these jobs (your through-line) rather than a start date, and don't treat every job change as equally important.
Structured elaboration
The milestone model, not a diary
Pick three to six career milestones (a promotion, a title change, a deliberate move) rather than narrating every job change. For a deep-technical background, emphasize technical growth across those milestones: scope of systems owned, complexity of problems solved, or seniority of team, not just tenure.
What to name at each milestone
For each milestone, give: the employer context and title (for example, "a small startup, as the second backend hire" rather than a bare timeline), the approximate timeframe, team size and the stack central to that stage, and one sentence on why you moved to the next step. Naming promotions and title changes explicitly signals growth the interviewer would otherwise have to infer from your resume. If you're naming a real former employer, that's fine to do live in the room; when practicing or writing this out generically, use employer type and context rather than a name so the shape of the answer stays reusable across interviews.
Explaining the "why" at each transition
For every step, answer two questions in one breath: how this role prepared you for the next one, and why you moved on. A career that reads as a sequence of things that happened to you reads as passive; a career narrated as a sequence of deliberate choices reads as intentional.
Timeline shape: highlight two moments, not every moment
Across the whole walkthrough, spend real time on exactly two things: one early-career learning moment (a mistake, a skill gap you closed, a foundational lesson) and one major turning point (a promotion, a pivot, a project that changed your trajectory). Everything else gets one line.
Worked example
Skeleton: "I started at [employer type], as a [title], on a team of [size], working mainly in [stack]. [One early-career learning: what I didn't know yet and how I closed the gap]. After [timeframe], I moved to [next employer type/title], because [reason tied to growth] [team size/stack if it changed]. [Major turning point: the milestone that changed my trajectory]. Today I'm at [current stage], where [what prepared me specifically for this next move]."
Filled illustration: "I started at a small agency as a junior data analyst on a two-person team, mostly writing SQL against a single reporting database. I didn't yet know how to translate a stakeholder's vague question into a testable metric, and closing that gap became the thing I got known for. After about two years, I moved into a data analyst role at a mid-size product company, because I wanted to work closer to product decisions instead of reporting after the fact. That's also where I was promoted to senior analyst on a team of six, once I'd built the first self-serve dashboard the product org relied on daily. That promotion was the turning point: it shifted me from someone who answered questions to someone who framed which questions were worth asking. Today I lead analytics for one product area, which is what draws me to a role that combines that ownership with a bigger surface area."
Trade-offs & pitfalls
- A pure date-by-date recitation (title, dates, title, dates) has no through-line and makes the interviewer do the synthesis work themselves.
- Treating every job change as equally important dilutes the two moments that actually matter: the early lesson and the big turning point.
- Omitting why you moved makes moves look reactive (laid off, bored, following a friend) rather than deliberate, even when the real reason was a good one.
- For senior candidates, spending equal time on early junior work as on recent scope is a common miscalibration; weight time toward the stages most relevant to this role.
Show how you would define an API interface in Kotlin using Retrofit and a JSON converter (Moshi or Gson) for an endpoint that returns a paginated list of articles. Provide a suspend function signature, required request headers for authorization, and an example of calling this API from a coroutine while handling HttpException and IOExceptions.
Sample Answer
Approach
Define DTOs, Retrofit interface using Moshi converter, include Authorization header, and call from a coroutine with proper exception handling.
Models
data class Article(val id: String, val title: String, val body: String)
data class PaginatedResponse<T>(
val items: List<T>,
val page: Int,
val pageSize: Int,
val total: Int
)
Retrofit interface
interface ArticlesApi {
@GET("articles")
suspend fun getArticles(
@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 20,
@Header("Authorization") auth: String // e.g. "Bearer <token>"
): PaginatedResponse<Article>
}
Retrofit builder (Moshi)
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(MoshiConverterFactory.create())
.build()
val api = retrofit.create(ArticlesApi::class.java)
Calling from coroutine with error handling
CoroutineScope(Dispatchers.IO).launch {
try {
val response = api.getArticles(page = 1, pageSize = 20, auth = "Bearer $token")
// process response.items on main thread if updating UI
} catch (e: HttpException) {
// handle non-2xx, use e.code() and e.response()?.errorBody()
} catch (e: IOException) {
// network failure, timeout, no connectivity
}
}
Notes: Prefer injecting token via an OkHttp interceptor for cleaner calls and to centralize retry/auth refresh logic.
Design an online algorithm that processes a stream of characters and at any time can return the first non-repeating character seen so far. Describe the data structures you'd use and implement an online method next(char) that returns the current first non-repeating character or None. Discuss time and space complexity.
Sample Answer
Direct answer
Maintain a hash map from character to its running count, plus a queue that holds candidates for
"first non-repeating" in the order they first appeared. On each next(char), update the count
and push the character onto the queue if it is new; then pop from the front of the queue while
its count is greater than 1, since a repeated character can never again be the answer. Whatever
remains at the front (or None if the queue is empty) is the current answer.
Structured elaboration
Why a hash map alone is not enough. A hash map of counts tells you WHETHER a character
repeats, but not which non-repeating character came FIRST. You need an ordering structure
alongside it.
Why a plain list of "seen order" is not enough either. If you kept every character ever
seen in a list and re-scanned it on every query, you would pay O(k) per query, where k grows
with the stream. The insight that makes this online and efficient: once a character's count
exceeds 1, it can never become the answer again, so it should be permanently removed from
consideration, not re-scanned every time.
The deque of candidates. A deque (double-ended queue) holds characters that MIGHT still be
the first non-repeating one, in first-seen order. On each new character: increment its count in
the hash map, append it to the deque. Then, while the deque's front character has a count
greater than 1, pop it (it is now permanently disqualified). The deque's new front, if any, is
the answer.
Complexity. Each character is appended to the deque exactly once and popped at most once
over the life of the stream, so total deque work across n calls is O(n), making each
next() call amortized O(1), even though a single call can trigger several pops if multiple
front characters were just disqualified. Space is O(distinct characters seen so far), bounded
by the alphabet size for a fixed character set (for example 256 for byte-level input), not by
stream length.
Worked example
from collections import deque, defaultdict
class FirstNonRepeating:
def __init__(self):
self.counts = defaultdict(int)
self.candidates = deque()
def next(self, char):
self.counts[char] += 1
self.candidates.append(char)
while self.candidates and self.counts[self.candidates[0]] > 1:
self.candidates.popleft()
return self.candidates[0] if self.candidates else None
class BruteForceFirstNonRepeating:
"""O(k) per call: re-scans everything seen so far, no deque pruning."""
def __init__(self):
self.seen_order = []
self.counts = defaultdict(int)
def next(self, char):
self.counts[char] += 1
self.seen_order.append(char)
for c in self.seen_order:
if self.counts[c] == 1:
return c
return None
stream = "aabcbcd"
fnr = FirstNonRepeating()
fast_answers = [fnr.next(ch) for ch in stream]
for ch, ans in zip(stream, fast_answers):
print(ch, "->", ans)
brute = BruteForceFirstNonRepeating()
brute_answers = [brute.next(ch) for ch in stream]
print("brute-force cross-check:", brute_answers, "| matches:", brute_answers == fast_answers)
Running this against the stream "aabcbcd" prints:
a -> a
a -> None
b -> b
c -> b
b -> c
c -> None
d -> d
brute-force cross-check: ['a', None, 'b', 'b', 'c', None, 'd'] | matches: True
Tracing it by hand confirms each step: after "a" the only character is a (answer a);
after "aa", a now repeats (answer None); after "aab", b is the only non-repeat (answer
b); after "aabc", both b and c are non-repeating but b came first (answer b); after
"aabcb", b now repeats and c is the earliest remaining non-repeat (answer c); after
"aabcbc", c now repeats too and nothing is left (answer None); after "aabcbcd", d is
the only non-repeat (answer d). The independent brute-force cross-check above (re-scanning the
full seen-order list and counts on every call, an O(k) per-call approach) produces the exact
same seven answers on this stream, confirming the deque-pruning optimization changes performance
without changing the result.
Trade-offs and pitfalls
- Do not use a plain hash map without an ordering structure. A map alone can tell you a
character's count but has no notion of "which non-repeating character came first," since hash
maps do not guarantee iteration order matches insertion order in every language and even where
they do (Python'sdictsince 3.7), scanning the whole map on every query throws away the
benefit of an online algorithm. - A common bug is popping from the deque unconditionally instead of only while the front is
disqualified; that would discard characters that are still valid candidates. - This generalizes directly to the first non-repeating word problem if you tokenize on word
boundaries instead of characters: same data structures, same amortized-O(1)-per-token
argument, as long as the vocabulary fits in memory. - For a bounded alphabet (fixed character set), the space bound is a true constant, not just
"bounded by input"; for an open-ended token vocabulary (arbitrary words), space instead grows
with distinct tokens seen, which matters for a long-running service.
Describe the BLoC (Business Logic Component) pattern in Flutter. Explain streams and sinks, how the UI subscribes to the BLoC, where validation and repository calls should live, and provide a high-level example for implementing a paginated list including events and states.
Sample Answer
Brief description
BLoC (Business Logic Component) separates UI from business logic by exposing Streams (output) and Sinks (input). The UI sends events into sinks; the BLoC processes them, interacts with repos/validation, and emits new states on streams the UI listens to.
Streams & Sinks
- Stream<T>: read-only output the UI subscribes to (e.g., Stream<State>).
- Sink<Event>: input where UI adds events (user actions).
- Use StreamController or flutter_bloc package (Bloc/BlocProvider) for lifecycle.
Where to place logic
- Validation: in the BLoC (or small validator helpers) so UI stays dumb.
- Repository/API calls: in repository layer; BLoC coordinates calls and maps results to states.
Paginated list — high level
Events:
abstract class ListEvent {}
class FetchPage extends ListEvent { final int page; FetchPage(this.page); }
class Refresh extends ListEvent {}
States:
abstract class ListState {}
class ListLoading extends ListState {}
class ListLoaded extends ListState { final List<Item> items; final bool hasMore; }
class ListError extends ListState { final String message; }
Flow:
- UI dispatches FetchPage when scrolled near bottom.
- BLoC checks current page, calls repository.fetchPage(page), emits ListLoading or appends to ListLoaded, sets hasMore.
- UI subscribes via StreamBuilder or BlocBuilder to rebuild on state changes.
Best practices
- Keep BLoC testable, small, persist only UI state; handle errors and debounce rapid events (e.g., scroll).
Your team reduced the authentication endpoint's p95 latency from 500ms to 350ms, a 30% improvement. For three audiences: a non-technical CEO, external developer customers, and internal engineering managers, write a short tailored message explaining the business value and one key metric each audience should track.
Sample Answer
Direct answer
The number doesn't change across audiences, but what it's evidence for does: to a CEO it's a business outcome (conversion, retention, cost), to external developers it's a reliability guarantee they can build on, to internal engineering managers it's a load and capacity signal. Same 30% improvement, three different "so what," each with the one metric that audience should actually track next.
Structured elaboration
The technique is picking, per audience, which consequence of the number they actually own:
- Translate the metric into the currency that audience is measured on before stating it. A CEO is measured on revenue and retention; an external developer is measured on their own app's reliability; an internal engineering manager is measured on system load and incident risk.
- Give exactly one metric to track next, not a dashboard's worth. Too many numbers reads as "we're not sure which one matters"; one number reads as a clear owner and a clear signal.
- State the baseline and direction explicitly, down from 500ms to 350ms, not just "faster," so nobody has to ask what the improvement actually was.
This same three-move pattern is what you would reuse for a different metric to the same or different audiences: messaging a lazy-loading improvement to executives, sales, and developers, an API deprecation to engineering, customers, and executives, or a throughput doubling to a CTO, operations, and sales. The technique is constant; only which consequence you lead with changes per audience.
Worked example
p95 latency means the response time that 95% of requests are faster than, so it's a measure of how slow the worst-but-common cases are, not the average case.
To the CEO: "We cut the time it takes customers to sign in from half a second to a third of a second, 500ms to 350ms, a 30% improvement, on the slowest 5% of requests, the ones customers actually notice as lag. Faster sign-in means fewer people abandoning at login and less friction on every visit. Metric to track: conversion rate on the sign-up-to-first-action flow over the next two weeks, to see if that translates into fewer drop-offs."
To external developer customers: "Our authentication endpoint's p95 latency, the response time 95% of your calls beat, dropped from 500ms to 350ms. You should see fewer client-side timeouts and retries against this endpoint. Metric to track: your own timeout and retry rate against our auth endpoint, it should trend down."
To internal engineering managers: "We cut auth p95 from 500ms to 350ms through caching and query tuning, which lowers tail latency for every downstream service that calls auth before doing its own work. Metric to track: queue length and tail latency on the services immediately downstream of auth, to confirm the improvement is propagating rather than just moving the bottleneck."
Trade-offs and pitfalls
Reusing the same "30% faster" framing for every audience without a metric attached invites the follow-up "compared to what, and how would I know it's working," which is exactly what the per-audience metric answers in advance. It's also a mistake to promise a business outcome, like "this will increase conversion," as a fact rather than a hypothesis. Latency and conversion correlate but aren't guaranteed to move together for every product, so the honest version says "we would expect to see," not "this will." And if a later measurement shows a segment of customers saw no improvement, say a region or client version, that needs its own honest message rather than folding it quietly into the aggregate number.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths
Browse Mobile Developer jobs
AI-enriched listings across hundreds of company career pages
Explore Jobs