Entry Level Mobile Developer Interview Preparation Guide - FAANG Standards
This guide is based on general FAANG interview practices and may not reflect specific company procedures.
Entry Level Mobile Developer interviews at FAANG companies typically consist of 5 main rounds designed to assess coding fundamentals, mobile platform knowledge, practical implementation skills, and cultural fit. The process emphasizes learning ability, problem-solving approach, and foundational competency in mobile development across iOS and/or Android platforms. Expect a mix of coding problems, platform-specific technical questions, practical feature implementation scenarios, and behavioral discussions.
Interview Rounds
Recruiter Screening
What to Expect
The initial recruiter screen is a 15-20 minute call to assess your background, motivation for mobile development, and basic technical awareness. The recruiter will verify your qualifications, discuss your experience with mobile platforms, and determine if you meet the baseline requirements. This is also your opportunity to learn about the role, team structure, and what to expect in subsequent interviews. The focus is on communication skills, genuine interest in mobile development, and cultural fit. You should be prepared to discuss your projects, why you chose mobile development, and what platforms you have experience with.
Tips & Advice
Keep your introduction concise (30 seconds max about who you are and why mobile development). Have a clear, genuine reason for pursuing mobile development. Mention specific projects or accomplishments, even if they are personal projects or course work. Ask thoughtful questions about the team, the mobile platforms they support, and the types of applications they build. Research the company and the role beforehand. Be honest about your experience level - entry level roles expect learning. Smile and be enthusiastic; entry level candidates should show eagerness and positive attitude.
Focus Topics
Communication and Professionalism
Practice clear, concise communication about technical topics without jargon. Be able to explain mobile concepts to non-technical people. Demonstrate professional communication, punctuality, and enthusiasm. Show that you can articulate ideas clearly and listen actively to questions.
Practice Interview
Study Questions
Experience with iOS and/or Android Platforms
Have a clear answer about which platform(s) you have hands-on experience with. Know the basics: What is iOS? What is Android? What languages are used (Swift/Objective-C for iOS, Kotlin/Java for Android, or React Native/Flutter for cross-platform)? Have 1-2 project examples you can discuss (even if they are learning projects).
Practice Interview
Study Questions
Mobile Development Background and Motivation
Be prepared to articulate why you are interested in mobile development, what attracted you to this career path, and what specific areas (iOS, Android, or cross-platform) interest you most. Have concrete examples of personal projects, course work, or contributions to open source that demonstrate your hands-on experience with mobile platforms.
Practice Interview
Study Questions
Technical Phone Screen - Coding Fundamentals
What to Expect
This 45-60 minute technical phone screen assesses your coding fundamentals and problem-solving approach. You will typically solve 1-2 coding problems of easy to medium difficulty, similar to LeetCode easy-medium level. The interviewer is evaluating your ability to write clean code, think through problems logically, and communicate your approach. You may be asked to code in your preferred language on a shared editor like CoderPad or Google Docs. The problems are often not mobile-specific at this stage but test fundamental computer science concepts like data structures (arrays, hash maps, linked lists), basic algorithms, string manipulation, or simple recursion.
Tips & Advice
Before coding, ask clarifying questions about the problem. Discuss your approach out loud before implementing - this helps the interviewer understand your thinking and allows them to give guidance if you are going the wrong direction. Start with a brute force solution and then optimize if needed. Write clean, readable code with proper variable names. Handle edge cases. Test your solution mentally with a few examples. If you get stuck, ask for hints - interviewers appreciate candidates who communicate when stuck rather than silent struggling. Time management is important; aim to have a working solution with a few minutes left for discussion. For entry level, correctness and clarity matter more than optimal efficiency.
Focus Topics
Complexity Analysis and Optimization
Understand Big O notation. For entry level, focus on being able to analyze time and space complexity of your solution. Know the difference between O(n), O(n^2), O(log n), O(n log n), etc. For simple problems, you do not need optimal solutions, but show awareness of complexity and ability to optimize when asked.
Practice Interview
Study Questions
Code Quality and Best Practices
Write code that is clean, readable, and well-structured. Use meaningful variable names, avoid code duplication, add comments for complex logic, and handle edge cases. Demonstrate awareness of input validation, boundary conditions, and error handling. Write code you would be proud to show a senior engineer.
Practice Interview
Study Questions
Data Structures Fundamentals
Master the basics of arrays, hash maps/dictionaries, linked lists, stacks, and queues. Understand when to use each data structure and their basic operations (insert, delete, search, access). Know the time and space complexity of common operations. Be able to implement these data structures from scratch or manipulate them effectively in your preferred language.
Practice Interview
Study Questions
Algorithm Problem-Solving Approach
Develop a systematic approach to solving coding problems: understand the problem, work through examples, identify patterns, develop a solution, implement, and test. Practice breaking down complex problems into smaller steps. Learn to recognize common problem patterns (two-pointer, sliding window, divide-and-conquer, etc.). Practice explaining your thought process clearly.
Practice Interview
Study Questions
Technical Interview - Mobile Platform Architecture and Lifecycle
What to Expect
This 60-minute technical interview focuses on your understanding of mobile platform fundamentals. You will be asked questions about iOS or Android architecture (or both if you have worked with both). Expect deep-dive questions about the mobile app lifecycle, component architecture, memory management concepts, and how mobile applications are structured. For iOS, this includes questions about UIKit/SwiftUI, view controllers, and the app lifecycle (app delegate, scenes, background/foreground states). For Android, this includes activities, fragments, intents, and the activity lifecycle. You may also be asked about cross-platform considerations if you have React Native or Flutter experience. The interviewer is assessing whether you understand how mobile applications fundamentally work and can explain architectural concepts clearly.
Tips & Advice
Choose the platform you are most comfortable with and be prepared to discuss it deeply. Draw diagrams when explaining lifecycles or architecture - this helps clarify your understanding. Be able to explain why certain architectural patterns exist (e.g., why fragments exist in Android, why view controllers separate concerns in iOS). Provide concrete code examples or scenarios to illustrate your understanding. If asked about a platform you have not used, be honest but draw parallels to what you do know. Practice explaining concepts out loud to build clarity. For entry level, understanding fundamentals and your reasoning is more important than memorizing every API. Prepare to discuss a project you have built and explain the architectural decisions you made.
Focus Topics
Cross-Platform Architecture Concepts (React Native/Flutter)
If you have cross-platform experience, understand how React Native bridges native code or how Flutter's Dart compiles. Know the differences between native and cross-platform approaches and their trade-offs. Understand widget-based architecture in Flutter or component-based architecture in React Native. Be able to discuss when to use cross-platform vs. native development.
Practice Interview
Study Questions
Mobile Memory Management and Resource Handling
Understand memory constraints on mobile devices compared to desktop. Know the concept of automatic reference counting (ARC) in iOS and garbage collection in Android. Understand memory leaks, retain cycles, and how to avoid them. Know weak vs. strong references and when to use each. For Android, understand the low-memory killer and how apps are prioritized. Understand the importance of releasing resources properly in lifecycle methods.
Practice Interview
Study Questions
Android App Architecture and Lifecycle
Understand Android application components: Activities, Services, Content Providers, and Broadcast Receivers. Master the Activity lifecycle: onCreate, onStart, onResume, onPause, onStop, onDestroy and when each is called. Understand Fragments and their role as modular UI components within Activities. Know the difference between process priority in Android and how it affects app lifecycle. Be familiar with Android Architecture Components and ViewModel, LiveData patterns.
Practice Interview
Study Questions
iOS App Architecture and Lifecycle
Understand iOS application structure including AppDelegate, SceneDelegate (iOS 13+), and UIWindow. Master the app lifecycle states: Not Running, Inactive, Active, Background, and Suspended. Know key lifecycle methods and when they are called. Understand UIViewController hierarchy, view lifecycle (viewDidLoad, viewWillAppear, etc.), and how views are managed. Be familiar with the Model-View-Controller (MVC) or Model-View-ViewModel (MVVM) architectural patterns used in iOS development.
Practice Interview
Study Questions
Technical Interview - Mobile Features and Implementation
What to Expect
This 60-minute technical interview focuses on implementing mobile-specific features and integrating with device capabilities and external services. You will be asked practical questions about implementing features: push notifications, location services, camera integration, file storage, API integration, networking, and handling permissions. You may be given scenarios or small coding tasks related to these features. For example: 'How would you implement location tracking with battery optimization?' or 'How would you handle API requests with proper error handling and offline support?' The interviewer is assessing your practical knowledge of mobile development, ability to think through requirements, and familiarity with mobile APIs and frameworks.
Tips & Advice
Discuss the requirements and constraints before diving into implementation. For features involving permissions, always mention how you would handle permission requests on different OS versions. When discussing networking, mention error handling, retry logic, and offline support considerations. For push notifications, discuss the differences between local and remote notifications and when each is appropriate. Draw architecture diagrams to show how components interact. Use your practical project experience to provide concrete examples. Show awareness of battery efficiency when relevant (e.g., location services). Be familiar with common third-party SDKs (Firebase for push notifications, location frameworks, etc.). For entry level, understanding the concepts and being able to reason through implementations is more important than memorizing API documentation.
Focus Topics
Asynchronous Programming and Concurrency
Understand the main thread vs. background threads in mobile development. Know how to dispatch work off the main thread to prevent UI freezing. In iOS: understand Grand Central Dispatch (GCD), OperationQueue, async/await. In Android: understand threading, Coroutines, LiveData, and reactive programming concepts. Know the threading model differences between iOS and Android. Understand race conditions and how to avoid them with proper synchronization.
Practice Interview
Study Questions
Mobile Data Storage and Persistence
Understand different storage options on mobile: UserDefaults (iOS), SharedPreferences (Android) for small key-value data; SQLite databases for structured data; file system storage. Know the differences between temporary, cached, and persistent storage. Understand data migration and schema versioning for databases. Know security considerations: where sensitive data should be stored, encryption options, avoiding hardcoded credentials.
Practice Interview
Study Questions
API Integration and Networking
Understand REST APIs and HTTP fundamentals (GET, POST, PUT, DELETE, status codes). Know how to make network requests in iOS (URLSession) and Android (Retrofit, OkHttp, or HttpURLConnection). Understand asynchronous programming concepts: callbacks, promises, async/await (or equivalent in your platform). Know how to handle network errors, timeouts, and implement retry logic. Understand JSON parsing/serialization. Be aware of security considerations: HTTPS, certificate pinning, secure storage of tokens.
Practice Interview
Study Questions
Mobile-Specific Features Implementation
Understand how to implement common mobile features: push notifications (local and remote), camera access, photo library access, location services, file storage, contacts access, and calendar integration. Know the permission models on iOS (Info.plist, user prompts) and Android (manifest, runtime permissions). Understand the lifecycle considerations for each feature and how to handle user permissions and failures gracefully. Be familiar with using platform-specific frameworks (UIImagePickerController on iOS, Intent-based camera on Android, etc.).
Practice Interview
Study Questions
Behavioral and Culture Fit Interview with Hiring Manager
What to Expect
This final 45-minute round focuses on your soft skills, learning ability, teamwork, and cultural fit with the team. The hiring manager or team lead will ask behavioral questions designed to understand how you approach problems, work with others, handle challenges, and align with the company's values (for FAANG companies, often values like innovation, ownership, collaboration, customer focus). You will discuss your background, career motivation, experiences working in teams, how you learn new technologies, how you handle setbacks, and what you are looking for in a role. The interviewer is assessing whether you would be a good team member, whether you can grow with the company, and whether your values align with the organization.
Tips & Advice
Use the STAR method (Situation, Task, Action, Result) for behavioral questions to provide clear, structured examples. Be specific with examples from projects or coursework; avoid generic answers. Show humility about your current level while demonstrating eagerness to learn. Give examples of asking for help when needed and learning from mentors - this shows coachability. Discuss how you have approached learning new mobile technologies. Be genuine and authentic - this is not about perfect answers but showing you are someone people want to work with. Ask thoughtful questions about the team, company culture, and growth opportunities. Research the company's values and be ready to align your answers to those values. For entry level, enthusiasm, learning ability, and teamwork are more important than years of experience.
Focus Topics
Motivation for Mobile Development and Career Goals
Be clear about why you are interested in mobile development specifically. Is it the user engagement? Building products people use daily? The technical challenges? Discuss your career goals: do you want to deepen expertise in mobile, eventually move to leadership, specialize in a domain, or explore other areas? Show that you have thought about your career path. Discuss why this specific role and company appeal to you.
Practice Interview
Study Questions
Problem-Solving Approach and Handling Challenges
Discuss how you approach problems systematically: breaking them down, researching solutions, trying different approaches. Give examples of overcoming technical challenges (debugging, performance issues, unexpected problems). Show that you can stay calm under pressure and think through issues logically. Discuss how you gather information when stuck and when you ask for help vs. trying independently.
Practice Interview
Study Questions
Learning Ability and Growth Mindset
Demonstrate that you actively learn and improve your skills. Share examples of learning a new technology, language, or framework independently. Discuss how you stay current with mobile development (blogs, podcasts, courses, side projects). Show ability to learn from mistakes and feedback. Discuss how you approach problems when you do not have immediate answers. Demonstrate curiosity and enthusiasm for mobile development as a field.
Practice Interview
Study Questions
Teamwork and Collaboration
Provide examples of working effectively with others: team projects, code reviews, pair programming, receiving feedback. Discuss how you communicate with teammates about technical decisions. Show examples of helping teammates or asking for help when needed. Discuss how you handle disagreements about technical approaches. Demonstrate awareness that software development is a team sport, not individual achievement.
Practice Interview
Study Questions
Frequently Asked Mobile Developer Interview Questions
What does good version-control hygiene look like day to day: commit granularity and messages, branch naming and PR size, and how you'd handle large binary or generated files if your project has them? Give one example of a commit message that helps a future reader and one that doesn't.
Sample Answer
Direct answer. Good version-control hygiene means every commit and PR tells a clear, minimal, reviewable story: small commits with messages that explain WHY, PRs scoped to one reviewable change, and a branching approach the whole team actually follows consistently.
Commit granularity and messages
- Each commit should represent ONE logical change that could, in principle, be reverted independently without breaking something unrelated -- a commit that mixes a bug fix with an unrelated formatting sweep makes both harder to review and harder to revert cleanly later.
- Good message:
Fix race condition in order total calculation (see INC-204)\n\nTwo concurrent requests could both read the stale total before either\nwrite committed. Wrap the read-modify-write in a transaction.-- explains WHY (the bug, the mechanism) not just WHAT (which is visible in the diff already). - Bad message:
fix bug-- tells a future reader (includinggit blamesix months from now) nothing they couldn't already see from the diff itself, and gives zero context for WHY this change was needed.
PR size and branch/PR conventions
- Smaller PRs get reviewed faster and more thoroughly -- a reviewer can hold a 100-line diff in their head; a 2,000-line diff gets a rubber-stamp approval because nobody can meaningfully review it in one sitting.
- A consistent branch-naming convention (
fix/order-total-race,feature/bulk-export) makes it easy to scan open branches and understand what's in flight without opening each one. - Rebasing versus merging is a team-level convention choice (rebase keeps history linear and easier to bisect; merge preserves the exact chronological record) -- the specific choice matters less than the TEAM actually agreeing on and following one consistently, so history reads predictably regardless of who wrote it.
Handling large binary or generated files
- Committing large binaries (design assets, model weights, video fixtures) directly into a normal git history bloats every future clone and slows down operations like
git logandgit blamefor the whole team, forever, even after the file is deleted, since git history keeps every version. - Use Git LFS (or an equivalent large-file extension) for binaries that genuinely need to be version-controlled alongside code, so the main repository only stores a lightweight pointer.
- For anything that can be REGENERATED from source (build output, compiled assets, lockfile-derived artifacts), keep it out of version control entirely via
.gitignorerather than committing it and then fighting merge conflicts on a file nobody hand-edits. - If large files were already committed by mistake, history-rewriting tools (
git filter-repo) can remove them retroactively, but that rewrites shared history and needs the same coordination caution as any other history rewrite on a branch others have pulled.
Why this matters for maintainability specifically
Git history is a maintainability tool in its own right: git blame and git log are often the FASTEST way to understand why a confusing piece of code exists, but only if commit messages actually explain the why -- a history of 'fix bug', 'wip', 'more fixes' gives future maintainers nothing to work with when they're trying to understand a decision made months or years ago.
Trade-offs and pitfalls
- Enforcing small PRs can pressure people to under-scope a change that's genuinely indivisible (e.g., a schema migration that must ship atomically with the code that depends on it) -- the goal is REVIEWABLE size, not an arbitrary line-count limit that ignores what a change actually requires.
- Rewriting history (interactive rebase, squashing) before merging to clean up a messy WIP trail is generally good practice, but doing it on a SHARED branch others have already pulled causes real pain -- keep history rewrites scoped to your own not-yet-shared branch.
For a memory-critical Android app that performs heavy image processing and background syncs, analyze the pros and cons of using android:largeHeap versus redesigning work into a separate process or leveraging native memory pools. Discuss how each approach affects OS memory pressure, battery, multi-process trade-offs, and maintainability.
Sample Answer
Summary / recommendation
For a memory‑critical Android app, prefer redesign (process isolation + careful memory pools) over relying on android:largeHeap. largeHeap is a stopgap that hides problems and worsens OS-level memory pressure; use it only when impossible to refactor.
android:largeHeap — Pros / Cons
- Pros: Quick to enable; reduces immediate OOMs for large images.
- Cons: Encourages memory bloat; Android treats it per-process but overall system memory pressure increases; poorer multitasking (other apps get killed); no battery benefit — larger heaps increase GC pauses and CPU for GC, increasing energy use; hard to reason about across OEM variants.
Separate process / process isolation — Pros / Cons
- Pros: Limits blast radius (e.g., heavy image pipeline in a dedicated process); Android can kill/restart it independently; improves UI responsiveness and reduces main process OOMs. Good for background syncs or batch work.
- Cons: IPC overhead (Binder) and serialization costs; increased APK complexity, lifecycle coordination, and testing; two processes double baseline memory footprint (code and native libs), affecting overall memory and battery.
Native memory pools (NDK) — Pros / Cons
- Pros: Predictable, manual allocation (e.g., pooled bitmaps, native allocators) reduces Java heap pressure and frequent GC; can use mmap/ashmem to share buffers across processes; often lower CPU/GPU copy cost -> battery savings.
- Cons: Unsafe (leaks crash process), platform differences, debugging harder; asset lifetime must be carefully managed; increases code complexity.
Trade-offs & practical guidance
- Start by profiling (Android Studio Memory Profiler, dumpsys meminfo).
- If large allocations are transient, use pooled native buffers + Bitmap pooling (inSampleSize, BitmapFactory options, Bitmap re-use).
- Use a separate process for non-UI heavy work (e.g., worker process for transforms/sync) when isolation and restartability matter; mitigate IPC cost with shared ashmem or file descriptors.
- Reserve android:largeHeap as last resort with clear monitoring and timeboxed mitigation plan.
Maintainability / testing
- Prefer patterns that keep behavior deterministic: pool + clear contracts, small IPC surface, thorough integration tests. Document ownership of native memory and lifecycle to avoid hidden leaks.
During a major campaign, one destination API starts throttling requests and your integration queue grows quickly. What signals would help you determine whether the bottleneck is in your producer, your pipeline, or the vendor, and what controls would you put in place to protect both throughput and freshness?
Sample Answer
Signals to check
I would look at three layers: producer, pipeline, and vendor. Producer signals include send rate, retry rate, and publish latency. Pipeline signals include queue depth, age of oldest message, consumer lag, and dead-letter growth. Vendor signals include 429 responses, timeout rate, and response latency.
How I would interpret it
If the queue grows while the producer rate is flat and vendor 429s rise, the bottleneck is likely the vendor. If queue depth rises but the vendor looks healthy, the consumer or transformation layer may be slow. If the producer rate spikes unexpectedly, the issue may be upstream demand.
Controls
- Apply backpressure, so producers slow down when the queue age crosses a threshold
- Use rate limits and bounded concurrency per destination
- Prioritize fresh or user-facing updates over low-value backlog
- Pause or batch noncritical sends when the vendor is throttling
- Use retries with jitter and a dead-letter queue for repeated failures
Example
If oldest-message age jumps from 2 minutes to 18 minutes and the vendor starts returning many 429s, I would cap concurrency, lower request rate, and alert on freshness rather than just queue size, because old messages are often more harmful than a larger queue.
Tell me about a piece of work you took on that was clearly beyond what you had done before. Why did you take it on, what did you do about the parts you could not yet do, and how did it turn out?
Sample Answer
Direct answer
I take on a stretch assignment when the upside is real and I have a concrete plan for closing the specific gaps rather than just confidence that it'll work out. I close those gaps in parallel with actually doing the work, ask for help on the exact piece I'm missing rather than vaguely, and I use how it turns out to decide what to go after next, not just as a story that ends when the project ships.
Structured elaboration
- Decide whether to take it on. I weigh what's genuinely new against what's actually adjacent to things I already know, whether a mistake here would be recoverable, and whether there's someone I could turn to if I got truly stuck, before saying yes.
- Name the specific gaps up front. Not a vague feeling of nervousness, but a short list of the particular things I don't yet know how to do, split into what I can pick up just-in-time on my own and what genuinely needs someone more experienced.
- Ask for support surgically. Rather than a general "let me know if I need help," I ask for something specific: a fixed block of a senior colleague's time on the one hard part, or a review at a particular checkpoint, so the ask is easy to say yes to and actually gets me what I need.
- Make decisions under real uncertainty by keeping them reversible where I can. When I'm not sure yet, I favor choices I can undo, and I flag the specific things I'm still unsure about to whoever's relying on the outcome, rather than presenting more confidence than I actually have.
- Let the outcome change what I go after next. Whether it went well or only partly well, I use it to recalibrate: what did I learn I'm actually capable of, and what specific thing should I deliberately go looking for next because this one exposed it as a real gap or a real strength.
Worked example
Early in a role, I was asked to take primary ownership of a technical evaluation for a large prospective customer, something I hadn't done before since I'd mostly supported more senior colleagues on similar calls. I took it on because the downside was recoverable (a more senior person was still one message away) and because the specific gap was narrow: I understood our product well, but I'd never had to run the whole evaluation conversation myself, including handling pushback in the room. I asked a specific colleague for thirty minutes beforehand to walk through how they usually handled the two hardest objections we tended to get, rather than asking generally for "advice." During the evaluation itself, I hit a technical question I genuinely didn't know the answer to, and rather than guessing, I said plainly that I'd confirm and follow up by end of day, which the customer accepted without issue. It closed successfully, and afterward I realized the part that had actually gone well wasn't the product knowledge, it was staying composed when I didn't know something, which told me the next stretch I should look for was one that put me in front of harder, more adversarial conversations rather than more technical depth.
Trade-offs and pitfalls
The risk on one side is taking on stretch work recklessly, with no way to recover if it goes wrong and nobody to turn to, which can do real damage rather than build a genuine capability. The risk on the other side is treating any unfamiliar work as too risky and never stretching at all, which just keeps you at the same level. The other common mistake is hiding uncertainty from the people relying on the outcome instead of flagging it, and treating the assignment as a one-off story rather than letting it actually inform what you deliberately go after next.
Given an array of integers and a target value, find the indices of two numbers that add up to the target, in a single pass and without assuming the array is sorted. What is the best achievable time complexity, and what do you trade for it?
Sample Answer
Direct answer
Walk the array once while maintaining a hash map from value to the index where you first saw it. For each element x at index i, check whether target - x is already a key in the map before inserting x itself; if it is, you've found the pair in that same pass. That gives O(n) average time using O(n) extra space for the map, which is the best achievable without assuming the array is sorted, since you need to have looked at the data at least once to guarantee you haven't missed a valid pair; what you trade for that speed is the extra memory the map uses.
Approach
- Keep a dictionary
seenmapping each value to the first index it appeared at. - For each index
iand valuex, computeneed = target - xand checkseenforneedbefore addingxtoseen; checking first is what prevents pairing an element with itself whentarget == 2 * x. - Return the pair of indices the moment a match is found; a single pass suffices.
from typing import List, Optional
def two_sum(nums: List[int], target: int) -> Optional[List[int]]:
"""Returns indices [i, j] such that nums[i] + nums[j] == target.
Average O(n) time, O(n) space."""
seen: dict[int, int] = {}
for i, x in enumerate(nums):
complement = target - x
if complement in seen:
return [seen[complement], i]
if x not in seen:
seen[x] = i
return None
if __name__ == "__main__":
print(two_sum([2, 7, 11, 15], 9)) # [0, 1]
print(two_sum([3, 3], 6)) # [0, 1]
print(two_sum([1, 2, 3], 100)) # None
Running this prints [0, 1], [0, 1], and None. In the second case, target 6 with nums = [3, 3]: at i=0, need=3 isn't in seen yet, so 3 is stored at index 0; at i=1, need=3 is now in seen, giving [0, 1], correctly using two distinct indices for the same value rather than pairing index 0 with itself.
Key points
- Checking for the complement before inserting the current value is what stops the algorithm from matching an element with itself when
targetequals twice that element's value. - "Average" O(n), not worst-case, is the honest label: each hash map operation is O(1) on average given a reasonably distributed hash, but an input engineered to collide against the hash function could in principle degrade individual lookups, which is a real (if unusual) concern in adversarial settings.
- The streaming variant, where you can't store the whole array up front, still needs O(n) auxiliary memory for values seen so far, since you must remember what's already been scanned; there's no way around that memory cost unless an additional constraint (like a bounded value range) lets you swap the hash map for a fixed-size bitset.
Complexity
Time: O(n) average (n hash map operations, each O(1) average). Space: O(n) for the seen map.
Edge cases
- No valid pair exists: the function should signal that clearly (returning
Nonehere, or raising, depending on the calling convention) rather than silently returning a wrong pair. - Duplicate values that sum to the target: handled correctly by storing the first occurrence and checking the complement before inserting the current value.
- Negative numbers: work without special-casing, since dictionary keys and arithmetic on
target - xdon't assume non-negative values. - An empty array or an array with fewer than two elements: no valid pair is possible.
Implement an in-memory LRU cache in Kotlin for Android that supports generics and provides O(1) get and put operations. The API should be:
class LruCache<K, V>(private val capacity: Int) {
fun get(key: K): V?
fun put(key: K, value: V)
}
Explain thread-safety considerations and how you'd extend it to a disk-backed cache.
Sample Answer
Approach (brief)
Use a HashMap for O(1) key lookup plus a doubly-linked list to maintain recency order. On get/put move node to head. Evict tail when capacity exceeded.
Kotlin implementation
class LruCache<K, V>(private val capacity: Int) {
init { require(capacity > 0) { "capacity must be > 0" } }
private inner class Node(val key: K, var value: V, var prev: Node? = null, var next: Node? = null)
private val map = HashMap<K, Node>(capacity)
private var head: Node? = null
private var tail: Node? = null
private var size = 0
fun get(key: K): V? {
val node = map[key] ?: return null
moveToHead(node)
return node.value
}
fun put(key: K, value: V) {
val node = map[key]
if (node != null) {
node.value = value
moveToHead(node)
} else {
val newNode = Node(key, value)
addToHead(newNode)
map[key] = newNode
size++
if (size > capacity) {
tail?.let { evict(it) }
}
}
}
private fun addToHead(node: Node) {
node.next = head
node.prev = null
head?.prev = node
head = node
if (tail == null) tail = node
}
private fun moveToHead(node: Node) {
if (node === head) return
// unlink
node.prev?.next = node.next
node.next?.prev = node.prev
if (node === tail) tail = node.prev
addToHead(node)
}
private fun evict(node: Node) {
map.remove(node.key)
node.prev?.next = null
tail = node.prev
if (tail == null) head = null
size--
}
}
Complexity & edge cases
- get/put: O(1) time, O(capacity) space.
- Handle capacity <= 0, null keys depending on use (current assumes non-null keys).
Thread-safety
- Current class is NOT thread-safe. For simple safety, mark public methods synchronized or use a ReentrantLock to protect map and list manipulations. For high concurrency on Android, consider ConcurrentHashMap + a separate lock for list ops or use Kotlin's @Synchronized on methods.
Disk-backed extension (Android)
- Use this in-memory LRU as primary cache; on eviction write value to disk (Room, file storage, or DiskLruCache). On get-miss, check disk cache asynchronously, load into memory, and return (or return placeholder while loading). Persist metadata (access times) to keep LRU order across restarts if desired. Use background threads/Coroutines and size-based eviction for disk.
What would make you excited to take this specific role, even on a difficult day?
Sample Answer
Direct answer
Name two or three sources of engagement that are durable, ownership of a real problem, autonomy to fix something broken, seeing your work actually used, because those are what survive a bad day, unlike novelty or initial excitement, which don't.
The framework
- Separate fragile excitement from durable motivation: prestige, initial novelty, and a strong first impression wear off; ownership of an outcome, autonomy, and a teammate depending on you tend not to.
- Contrast durable motivators with compensation explicitly: this question is implicitly testing whether your engagement is pay-dependent, if the work is only worth doing because of the paycheck, a bad day has nothing to pull you back, so naming non-comp durable motivators directly answers what's being tested.
- Acknowledge that bad days are real rather than claiming immunity to them; naming what specifically pulls you through one is more credible than implying you never have them.
- Tie the durable motivator to something concrete about this specific role's day-to-day, not a generic value that would apply anywhere.
Worked example
On a genuinely hard day earlier in my career, a project I owned hit a frustrating, unglamorous problem that took much longer than planned to actually fix. What got me back in the next morning wasn't excitement about the company, it was that the problem was mine to solve and a teammate was blocked on it; finishing it meant something specific and immediate, not abstract. That's the kind of motivator I'm looking for in this role too: a defined area of ownership where the work is visibly mine and visibly used, not just a mission statement I agree with in the abstract.
Trade-offs and pitfalls
| Weak pattern | Strong pattern |
|---|---|
| Naming only external motivators (perks, pay, prestige) | Naming ownership, autonomy, and visible use of your work |
| Claiming you never have bad days | Acknowledging bad days and naming what specifically pulls you through |
| A motivator generic enough to apply to any employer | A motivator tied to something concrete in this role's actual day-to-day |
| Implying money doesn't matter at all | Contrasting durable motivators with pay explicitly, without dismissing pay |
An answer built entirely on external motivators invites the obvious follow-up about leaving for more money, while an answer that claims total immunity to bad days reads as unconvincing; the credible middle ground names something specific that held up under a real bad day.
You're building a fitness app sampling accelerometer and gyroscope at 50Hz continuously. Propose optimizations to minimize battery drain while maintaining required accuracy: sensor batching, adaptive sampling, using hardware pedometer/step-counter, leveraging OS activity recognition APIs, batching and duty-cycling, native processing vs JS/Dart, and platform-specific APIs for low-power sensing on Android and iOS.
Sample Answer
Approach summary
Trade accuracy vs energy by moving as much work as possible to low-power hardware, batching, and intelligent sampling. Prioritize hardware pedometer/OS activity APIs and native processing; fall back to 50 Hz only when strictly necessary.
Concrete optimizations
- Sensor batching & hardware sensors
- Use hardware step-counter / step-detector (Android Step Counter / iOS CMPedometer) for continuous step counts at micro-power.
- On Android use SensorManager batching (registerListener with maxReportLatency) and SensorDirectChannel where available to let SoC buffer samples in HAL.
- On iOS use CMSensorRecorder (historical samples) + CMPedometer for steps and CMMotionManager for live when needed.
- Adaptive sampling & duty-cycling
- Default to low-rate or hardware sensors; raise to 50 Hz only during detected activity windows.
- Example: poll at 5–10 Hz for activity classification; when activity probability > threshold, switch to 50 Hz for 10–30s.
- Use exponential backoff: if no motion, increase interval and reduce active time.
- OS activity recognition
- Use Android ActivityRecognitionClient (Activity Recognition Transition API) and iOS CMMotionActivity to detect walking/running and gate high-rate sampling.
- Native processing vs JS/Dart
- Perform sensor sampling, batching, and fusion in native modules (Kotlin/Swift) or background native services. Send summarized events to JS/Dart to avoid wakeups and bridge overhead.
- If using Flutter/React Native, use platform channels / background isolates to run continuous logic off the JS thread.
- Batching & processing
- Aggregate features (mean, std, step count) on-device and upload only summaries or events.
- Use sensor fusion (simple complementary filter) in native to reduce need for high-rate gyroscope if accelerometer suffices.
- Platform-specific tips
- Android: prefer hardware step sensor, use JobScheduler/Foreground Service for long runs, respect Doze (use setAndAllowWhileIdle sparingly), use Sensor.TYPE_SIGNIFICANT_MOTION for wake-ups.
- iOS: use CMPedometer & CMMotionActivity for low-power detection; use background modes (motion) carefully—CMPedometer continues in background with low cost; prefer CMSensorRecorder for long-term sampling on supported devices.
Why this works
Hardware counters + OS activity detection eliminate constant high-rate sampling. Batching and native processing reduce CPU/GPU and JS bridge wakeups, minimizing battery without losing accuracy when high-fidelity data is required.
How can thread confinement and message passing help you avoid locks? Give an example of confining state to one thread or queue, and say when you would still need synchronization.
Sample Answer
Direct answer
If only one thread ever touches a piece of data, nothing can race on it, so it needs no lock. Thread confinement means giving each piece of mutable state exactly one owning thread (or one serial queue). Message passing means every other thread asks the owner to act by putting a message on a queue, instead of reaching into the data. The queue is then the only place threads meet, and it carries its own synchronization. You still need real synchronization at that queue, for data that is genuinely shared, and for anything that escapes the owner by pointer.
How it removes the lock
- Pick the owner: a dedicated thread, a goroutine (Go's lightweight thread, started with the
gokeyword), or a serial dispatch queue (a queue, as in Apple's Grand Central Dispatch, that runs its tasks strictly one after another). - The owner is the only code that holds a reference to the state.
- Other threads send small command or event messages.
- The owner processes messages one at a time, so every operation on the state is already atomic with respect to every other operation.
The queue itself is synchronized by the language runtime. In Go a queue between goroutines is a channel, and a send on a channel is synchronized before the corresponding receive completes (Go memory model, go.dev/ref/mem), which means everything the sender wrote before sending is guaranteed visible to the receiver. Java documents the same kind of guarantee, called happens-before (an action that happens-before another is guaranteed to be visible to it), for concurrent collections: actions before placing an object into one happen-before actions after taking it out in another thread.
Two familiar examples. The Android UI toolkit is not thread-safe, and its developer documentation tells you not to access it from outside the UI thread: the main thread owns every view, and worker threads post results to it. A server can give one goroutine ownership of a counter map and let request handlers send events to it.
Worked example, run with the Go race detector
The Go syntax to know: <-chan event is a channel the function may only receive from and chan<- map[string]int one it may only send on; for e := range in receives until the channel is closed; defer wg.Done() runs when the function returns, telling the WaitGroup (a counter the main goroutine waits on) that one worker has finished. The owner goroutine below is the only code that touches counts. Four goroutines send 1,000 events each. When the owner finishes it sends the whole map to the receiver and never touches it again, which is a clean hand-over of ownership. The second half shows the hole in the pattern: a message that carries a pointer.
package main
import (
"fmt"
"os"
"sync"
)
// Confinement: only the owner goroutine ever touches counts. Everyone else sends a message.
type event struct{ word string }
func owner(in <-chan event, done chan<- map[string]int) {
counts := map[string]int{} // confined: no other goroutine has a reference
for e := range in {
counts[e.word]++
}
done <- counts // ownership of the map transfers to the receiver; owner never touches it again
}
// The hole: a message that carries a pointer lets sender and receiver share memory.
type buf struct{ data []int }
func main() {
in := make(chan event, 16)
done := make(chan map[string]int)
go owner(in, done)
var wg sync.WaitGroup
for w := 0; w < 4; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
in <- event{"hit"}
}
}()
}
wg.Wait()
close(in)
fmt.Println("confined counter:", (<-done)["hit"])
// Escaped pointer: sender keeps writing to a buffer it already sent.
if len(os.Args) > 1 && os.Args[1] == "leak" {
ch := make(chan *buf, 1)
b := &buf{data: make([]int, 4)}
ch <- b
var wg2 sync.WaitGroup
wg2.Add(1)
go func() { defer wg2.Done(); r := <-ch; r.data[0] = 1 }()
b.data[0] = 2 // sender still writes after sending: shared, unsynchronized
wg2.Wait()
fmt.Println("leaky version finished")
} else {
ch := make(chan *buf, 1)
b := &buf{data: make([]int, 4)}
b.data[0] = 2
ch <- b
b = nil // the sender gives up its reference after sending
var wg2 sync.WaitGroup
wg2.Add(1)
go func() { defer wg2.Done(); r := <-ch; r.data[0] = 1; fmt.Println("receiver wrote", r.data[0]) }()
wg2.Wait()
}
}
Run in a golang:1.23 container with go run -race confine.go:
confined counter: 4000
receiver wrote 1
Running go run -race confine.go leak takes the other branch, where the sender keeps writing to a buffer after sending its pointer. The race detector reports a data race between two writes, one on the receiver goroutine (line 48) and one on the main goroutine (line 49). The report appeared on each of a dozen re-runs; the address, the goroutine numbering and which of the two writes is listed as the earlier one can differ between runs, because that depends on which goroutine gets there first. The report is written to standard error the moment the detector sees the second unordered access, so it appears before the program's own last print, leaky version finished:
confined counter: 4000
==================
WARNING: DATA RACE
Write at 0x00c000142000 by goroutine 12:
main.main.func2()
/w/confine.go:48 +0xac
Previous write at 0x00c000142000 by main goroutine:
main.main()
/w/confine.go:49 +0x600
Goroutine 12 (running) created at:
main.main()
/w/confine.go:48 +0x5dc
==================
leaky version finished
Found 1 data race(s)
exit status 66
Read it as: the two stacks name the two unordered writes to the same address (r.data[0] = 1 in the receiver, b.data[0] = 2 in main), and exit status 66 is how go run reports that the race detector fired. Here the file is named confine.go and /w is the container's working directory. The channel gave an ordering for what happened before the send, but the sender's later write has no ordering with the receiver's write.
What stays shared even when every buffer is per-thread
The first two items cause wrong results in everyday code, so check them first; the last three are narrower, mostly performance or special contexts.
- Escaped pointers. Sending a pointer, slice or reference shares the memory. Either send a copy, or treat the send as a transfer and drop your own reference, as the non-leaking branch does.
- Static and global state. A function-local variable is confined; a global, a singleton (the one shared instance of a class) or a
staticis not, no matter which thread's code touches it. - The allocator. The allocator is the runtime component behind
mallocornewthat hands out memory. Each thread's buffer comes from a shared heap. Allocators synchronize internally (often with per-thread caches), so this is correct, but heavy allocation from many threads still contends, and freeing memory from a thread other than the one that allocated it can be slower. - False sharing. The cache line is the block of memory (typically 64 bytes) that cores copy between their caches, and a core must own the whole line to write any byte of it. Two threads' private counters that sit in the same cache line make the cores fight over that line. Results stay correct but throughput collapses. Pad or align per-thread data to separate lines.
- Signal handlers. A signal interrupts a thread and runs on top of it. Confinement does not protect that thread's data from its own handler, and only a small set of async-signal-safe functions (the short list of functions the system documents as safe to call from inside a handler, because each is either reentrant or cannot be interrupted by a signal handler; functions like
mallocandprintfare not on it, since a handler could interrupt them while they hold an internal lock) may be called inside one.
When you still need synchronization
- At the queue: its enqueue and dequeue must be thread-safe (the runtime's channels and concurrent queues are).
- For read-mostly data many threads share (a configuration object): publish an immutable copy through an atomic reference rather than confining it.
- When one operation spans two owners, such as moving money between two accounts owned by different threads: either have one owner coordinate with a two-step message exchange, or take locks in a fixed order.
- When owners wait for each other: if owner A sends a request to owner B and blocks for the reply while B does the same to A, you have a deadlock built from queues instead of locks.
Trade-offs
Confinement trades locks for latency and for a bottleneck: all work on that state is serial, and an owner that blocks on slow I/O stalls everyone waiting on it. Keep owner handlers short, bound the queue so a flood applies backpressure (senders are forced to wait or are refused when the queue is full) instead of filling memory, and move slow work to other threads that report back by message.
Legal or compliance flags that something you're about to ship may violate a regulation in a key market and asks for a freeze, but the business wants to proceed. How do you work through that?
Sample Answer
Direct answer
When legal or compliance flags a possible regulatory problem on something about to ship, that flag is new information, not an attack on the project. The first move is to separate the specific risk from the whole feature: find out exactly what triggers the concern, then look for a way to ship everything outside that blast radius (the specific data, users, or markets the flagged concern actually touches) while the risky piece gets handled properly. Treating the flag as either a full block to fight or a formality to route around are both weak answers; the senior move is to make the freeze as small as the actual risk.
Structured elaboration
1. Turn the flag into a scoped, written finding
Ask for the specific clause or regulation, the specific data flow or behavior it applies to, and which markets or user segments are affected. A flag that sounds like 'this violates a regulation' often narrows down to 'this one data field, in these two markets.' Until that scoping happens, nobody can reason about mitigation, they can only argue about the abstract freeze.
2. Sort what's actually blocked from what's just slow
Once scoped, most flags fall into three buckets: genuinely unsafe to ship anywhere (rare, but real, treat it as a hard stop); unsafe in specific markets or for specific data (the common case, often scoped out with a flag or market-level rule); or unsafe as currently designed but fixable with a smaller change than a full freeze (needs a scoped rework, not a blanket delay).
3. Bring a mitigation, not just a constraint
Offer a concrete option: disable the flagged behavior for the affected markets, gate it behind a feature flag (a toggle that turns a piece of functionality on or off without a new deployment), or ship a version that omits the specific data flow while the rest proceeds. This turns the conversation from 'can we go or not' into 'does this mitigation satisfy the concern,' which moves much faster.
4. Get joint, written sign-off before proceeding
Both the business owner and compliance need to agree in writing on what shipped, what did not, the remaining risk, and who owns closing it. This protects everyone if the interpretation is questioned later and prevents the same argument from recurring next release.
5. If a real freeze can't be avoided, negotiate the timeline explicitly
Sometimes there is no safe scoped path and the freeze has to hold for the affected piece. Here the negotiation shifts to: what's the minimum change needed to clear the concern, who is assigned to it, and can the review be fast-tracked with a dedicated reviewer instead of sitting in a general queue. A freeze with a committed, shrinking timeline is a very different conversation from an open-ended one.
Worked example
A team is about to ship a feature that logs a new field for product analytics, and legal flags that collecting that field may violate a data-protection rule in one region. Scoping the flag shows the issue is narrow: one field, one region. Instead of freezing the whole release, the team ships everywhere else immediately, and for the flagged region ships the same feature with that one field's collection disabled behind a config switch. Legal signs off on the scoped version in writing. The team opens a follow-up item, with an owner and a target date, to redesign how that field is collected (for example, aggregating it instead of storing it per user), so the region isn't stuck without the feature indefinitely.
Trade-offs and pitfalls
- Treating every compliance flag as either a full block or a nuisance to route around is the most common mistake here; both extremes erode trust with the compliance function over time.
- Scoped mitigations (flags, market gating, field exclusions) are good short-term tools but can quietly become permanent if nobody owns the follow-up fix. The sign-off should name an owner and a date, not just describe a workaround.
- Escalating past compliance to force a ship date, without addressing the underlying concern, tends to resurface later as a bigger problem: a real violation or a regulator inquiry. Speed gained by skipping the process rarely survives contact with the risk it was protecting against.
- The strongest signal of seniority isn't how fast the team got to yes, it's whether the final decision is something both sides would still defend the same way months later.
Recommended Additional Resources
- LeetCode - Practice coding problems at Easy and Medium difficulty levels (aim for 50-100 problems before interviews)
- Apple's Swift Programming Language Guide and iOS Human Interface Guidelines - Official documentation for iOS development fundamentals
- Google Android Developers official documentation and Codelabs - Comprehensive resource for Android platform concepts and implementation
- Cracking the Coding Interview by Gayle McDowell - Essential for coding interview preparation with explanations of common problem patterns
- Ray Wenderlich tutorials - High-quality iOS and Android development tutorials with practical examples
- HackerRank and Codewars - Alternative platforms for practicing coding problems with mobile-specific challenges
- Stanford's Developing iOS Apps with Swift course (available free on YouTube) - Comprehensive iOS fundamentals from a top university
- Udemy and Coursera mobile development courses - Self-paced structured learning for iOS, Android, or cross-platform development
- GitHub - Study open-source mobile apps to see real-world architecture patterns and best practices
- Interview.dev or Pramp - Platform for practicing mock interviews with real people, including mobile development interviews
- Android Architecture Components documentation - Understanding MVVM and lifecycle-aware components
- Firebase documentation - Common third-party service for push notifications, analytics, and real-time databases
- System Design Interview resources - For understanding distributed systems concepts that may apply to mobile backends and API design
Search Results
40 Common iOS Interview Questions (With Sample Answers)
Briefly explain the architecture of iOS applications. · What are the main features of the iOS platform? · What are some tech websites, blogs and podcasts you ...
Top 25 Mobile App Developer Interview Questions and Answers for ...
... mobile app developer interview questions confidently” “senior mobile app developer interview questions what to expect” “entry level mobile app developer ...
50+ Essential Vue Interview Questions & Answers (Easy to Advanced)
Use this list of Vue interview questions and answers to prepare for your upcoming meeting with a tech recruiter or lead front-end engineer!
Top 65+ React JS Interview Questions & Answers for 2026
TL;DR: Explore the most important React JS interview questions and answers for beginners, intermediate, and experienced developers.
Top 10 Angular Developer Interview Questions - Full Scale
2. How long have you been coding Angular as the primary language? 3. What is the difference between AngularJS and Angular? 4. Explain data binding. Which form ...
Apple Software Engineer Interview Questions
Tell me about a time when you had too many things to do, and you were required to prioritize your tasks. · Give me an example of a time when you tried to ...
Top 50+ API Testing Interview Questions [Free Template]
1. What is an API? 2. What are the main differences between API and Web Service? 3. What are the Limits of API Usage? 4. How does an API work? 5. What are the ...
50 Most Popular Salesforce Interview Questions & Answers ...
41. At a high level, can you describe the Software Development Lifecycle? · 42. Can you name a few ways to help improve Salesforce user adoption? · 43. What can ...
This interview preparation guide was generated using AI-powered research from the sources listed above. While we strive for accuracy, we recommend verifying critical information from official company sources.
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