Junior Mobile Developer (Android) Interview Questions: Complete Guide

Milad Bonakdar
Author
Master Android development fundamentals with essential interview questions covering Kotlin, Activities, Fragments, Jetpack Compose, data persistence, and Android best practices for junior developers.
Introduction
Android development powers billions of devices worldwide, making it one of the most important mobile platforms. With Kotlin as the preferred language and modern tools like Jetpack Compose, Android developers create rich, performant applications for diverse users.
This guide covers essential interview questions for Junior Android Developers. We explore Kotlin fundamentals, Android components, UI development, data persistence, networking, and best practices to help you prepare for your first Android developer role.
Kotlin Fundamentals (6 Questions)
1. What is the difference between val and var in Kotlin?
Answer:
val: Declares an immutable (read-only) variable. Once assigned, its value cannot be changed.var: Declares a mutable variable. Its value can be changed after initialization.- Best Practice: Use
valby default for safety. Only usevarwhen you need to reassign the value.
val name = "John" // Cannot be changed
var age = 25 // Can be changed
age = 26 // Valid
// name = "Jane" // Error: Val cannot be reassignedRarity: Very Common Difficulty: Easy
2. Explain Nullable types and the Safe Call operator in Kotlin.
Answer: Kotlin's type system distinguishes between nullable and non-nullable types to prevent null pointer exceptions.
- Nullable Type: Add
?after the type:String? - Safe Call (
?.): Calls a method only if the object is not null - Elvis Operator (
?:): Provides a default value if null - Not-null Assertion (
!!): Throws exception if null (use sparingly)
var email: String? = "user@example.com"
// Safe call
val length = email?.length // Returns null if email is null
// Elvis operator
val displayEmail = email ?: "No email"
// Let function
email?.let {
println("Email: $it")
}
// Not-null assertion (dangerous)
val len = email!!.length // Throws exception if email is nullRarity: Very Common Difficulty: Easy
3. What is the difference between a class and a data class in Kotlin?
Answer:
- Regular Class: Standard class definition
- Data Class: Automatically generates useful methods for holding data
equals()andhashCode()toString()copy()functioncomponentN()functions for destructuring
// Regular class
class Person(val name: String, val age: Int)
// Data class
data class User(val name: String, val age: Int)
val user1 = User("John", 25)
val user2 = User("John", 25)
println(user1 == user2) // true (equals() generated)
println(user1) // User(name=John, age=25) (toString() generated)
// Copy with modifications
val user3 = user1.copy(age = 26)
// Destructuring
val (name, age) = user1Rarity: Very Common Difficulty: Easy
4. What are Lambda expressions and Higher-order functions in Kotlin?
Answer:
- Lambda: Anonymous function that can be passed as a value
- Higher-order Function: Function that takes functions as parameters or returns a function
// Lambda expression
val sum = { a: Int, b: Int -> a + b }
println(sum(5, 3)) // 8
// Higher-order function
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val result = calculate(10, 5) { x, y -> x * y }
println(result) // 50
// Collection operations
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }
println(doubled) // [2, 4, 6, 8, 10]
println(evens) // [2, 4]Rarity: Very Common Difficulty: Medium
5. Explain Extension Functions in Kotlin.
Answer: Extension functions allow you to add new functions to existing classes without modifying their source code.
// Extension function for String
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
println("radar".isPalindrome()) // true
println("hello".isPalindrome()) // false
// Extension function with parameters
fun Int.times(action: () -> Unit) {
repeat(this) {
action()
}
}
3.times {
println("Hello")
}
// Prints "Hello" three timesRarity: Common Difficulty: Easy
6. What is the difference between == and === in Kotlin?
Answer:
==: Structural equality (compares values usingequals())===: Referential equality (compares memory references)
val str1 = "Hello"
val str2 = "Hello"
val str3 = str1
println(str1 == str2) // true (same value)
println(str1 === str2) // true (string pool optimization)
println(str1 === str3) // true (same reference)
val list1 = listOf(1, 2, 3)
val list2 = listOf(1, 2, 3)
println(list1 == list2) // true (same content)
println(list1 === list2) // false (different objects)Rarity: Common Difficulty: Easy
Android Components (5 Questions)
7. What is an Activity and explain its lifecycle.
Answer: An Activity represents a single screen with a user interface. It has a well-defined lifecycle:
onCreate(): Activity is created. Initialize UI, set content view.onStart(): Activity becomes visible.onResume(): Activity is in foreground and interactive.onPause(): Activity losing focus (another activity coming to foreground).onStop(): Activity no longer visible.onDestroy(): Activity is destroyed.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize views, setup data
}
override fun onStart() {
super.onStart()
// Activity becoming visible
}
override fun onResume() {
super.onResume()
// Activity is interactive, start animations
}
override fun onPause() {
super.onPause()
// Activity losing focus, pause ongoing actions
}
}Rarity: Very Common Difficulty: Easy
8. What is the difference between an Activity and a Fragment?
Answer:
- Activity: Represents a full screen. Entry point for user interaction. Has its own lifecycle.
- Fragment: Reusable portion of UI within an Activity. Multiple fragments can exist in one activity. Has its own lifecycle tied to the host activity.
- Benefits of Fragments:
- Reusability across activities
- Modular UI components
- Support for tablets (multi-pane layouts)
- Navigation component integration
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_my, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Initialize views
}
}
// In Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, MyFragment())
.commit()
}
}Rarity: Very Common Difficulty: Easy
9. What is an Intent and what are its types?
Answer: Intent is a messaging object used to request an action from another app component.
- Explicit Intent: Specifies the exact component to start (by class name)
- Implicit Intent: Declares a general action, and the system finds the appropriate component
// Explicit Intent - start specific activity
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("USER_ID", 123)
startActivity(intent)
// Implicit Intent - open URL
val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(webIntent)
// Implicit Intent - share text
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Check this out!")
}
startActivity(Intent.createChooser(shareIntent, "Share via"))
// Implicit Intent - make phone call
val callIntent = Intent(Intent.ACTION_DIAL).apply {
data = Uri.parse("tel:1234567890")
}
startActivity(callIntent)Rarity: Very Common Difficulty: Easy
10. What is a Service in Android?
Answer: A Service is a component that runs in the background to perform long-running operations without a user interface.
- Types:
- Foreground Service: Performs noticeable operations (music player). Shows notification.
- Background Service: Performs operations not directly noticed by user.
- Bound Service: Allows components to bind to it and interact.
class MyService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Perform background work
Thread {
// Long-running operation
Thread.sleep(5000)
stopSelf()
}.start()
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
// Start service
val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent)
// Stop service
stopService(serviceIntent)Rarity: Common Difficulty: Medium
11. What is a BroadcastReceiver?
Answer: BroadcastReceiver is a component that responds to system-wide broadcast announcements.
- Use Cases: Battery low, network connectivity changes, SMS received, boot completed
- Registration:
- Static: In AndroidManifest.xml (limited in newer Android versions)
- Dynamic: In code (preferred)
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_BATTERY_LOW -> {
// Handle low battery
}
ConnectivityManager.CONNECTIVITY_ACTION -> {
// Handle network change
}
}
}
}
// Register dynamically
class MainActivity : AppCompatActivity() {
private val receiver = MyBroadcastReceiver()
override fun onStart() {
super.onStart()
val filter = IntentFilter(Intent.ACTION_BATTERY_LOW)
registerReceiver(receiver, filter)
}
override fun onStop() {
super.onStop()
unregisterReceiver(receiver)
}
}Rarity: Common Difficulty: Easy
UI Development (4 Questions)
12. What is the difference between LinearLayout, RelativeLayout, and ConstraintLayout?
Answer:
- LinearLayout: Arranges children in a single row or column. Simple but can lead to nested layouts.
- RelativeLayout: Positions children relative to each other or parent. More flexible but complex.
- ConstraintLayout: Modern, flexible layout. Flat view hierarchy. Recommended for complex UIs.
<!-- ConstraintLayout example -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>Rarity: Very Common Difficulty: Easy
13. What is RecyclerView and how does it work?
Answer: RecyclerView is an efficient widget for displaying large lists by recycling views.
- Components:
- Adapter: Binds data to views
- ViewHolder: Holds references to views (avoids
findViewByIdcalls) - LayoutManager: Positions items (Linear, Grid, Staggered)
// Data class
data class User(val name: String, val email: String)
// ViewHolder
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val nameText: TextView = itemView.findViewById(R.id.nameText)
private val emailText: TextView = itemView.findViewById(R.id.emailText)
fun bind(user: User) {
nameText.text = user.name
emailText.text = user.email
}
}
// Adapter
class UserAdapter(private val users: List<User>) :
RecyclerView.Adapter<UserViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return UserViewHolder(view)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(users[position])
}
override fun getItemCount() = users.size
}
// In Activity
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = UserAdapter(userList)Rarity: Very Common Difficulty: Medium
14. What is Jetpack Compose?
Answer: Jetpack Compose is Android's modern declarative UI toolkit.
- Declarative: Describe what the UI should look like, not how to build it
- Benefits: Less code, intuitive, powerful, accelerates development
- Composable Functions: Building blocks of Compose UI
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
@Preview
@Composable
fun PreviewCounter() {
Counter()
}Rarity: Very Common Difficulty: Easy
15. What is the difference between match_parent and wrap_content?
Answer: These are layout parameters that define view dimensions:
match_parent: View expands to fill the parent's sizewrap_content: View sizes itself to fit its content- Fixed Size: Specific dp value (e.g.,
100dp)
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Fills parent width, wraps content height -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title" />
<!-- Wraps both dimensions -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me" />
<!-- Fixed size -->
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/icon" />
</LinearLayout>Rarity: Very Common Difficulty: Easy
Data & Networking (5 Questions)
16. How do you make a network request in Android?
Answer:
Use libraries like Retrofit or OkHttp for networking. Avoid using HttpURLConnection directly.
// Retrofit setup
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@POST("users")
suspend fun createUser(@Body user: User): User
}
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/"
val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
// Usage in ViewModel
class UserViewModel : ViewModel() {
fun fetchUser(userId: Int) {
viewModelScope.launch {
try {
val user = RetrofitClient.apiService.getUser(userId)
// Update UI
} catch (e: Exception) {
// Handle error
}
}
}
}Rarity: Very Common Difficulty: Medium
17. What is Room and how do you use it?
Answer: Room is an abstraction layer over SQLite for easier database access.
- Components:
- Entity: Represents a table
- DAO (Data Access Object): Defines database operations
- Database: Database holder
// Entity
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "email") val email: String
)
// DAO
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): List<User>
@Query("SELECT * FROM users WHERE id = :userId")
fun getUserById(userId: Int): User?
@Insert
suspend fun insertUser(user: User)
@Delete
suspend fun deleteUser(user: User)
@Update
suspend fun updateUser(user: User)
}
// Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build()
INSTANCE = instance
instance
}
}
}
}Rarity: Very Common Difficulty: Medium
18. What is SharedPreferences?
Answer: SharedPreferences stores small amounts of primitive data as key-value pairs.
- Use Cases: User settings, preferences, simple flags
- Not for: Large data, complex objects (use Room instead)
// Save data
val sharedPref = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
with(sharedPref.edit()) {
putString("username", "John")
putInt("age", 25)
putBoolean("isLoggedIn", true)
apply() // or commit()
}
// Read data
val username = sharedPref.getString("username", "Guest")
val age = sharedPref.getInt("age", 0)
val isLoggedIn = sharedPref.getBoolean("isLoggedIn", false)
// Remove data
with(sharedPref.edit()) {
remove("username")
apply()
}
// Clear all
with(sharedPref.edit()) {
clear()
apply()
}Rarity: Very Common Difficulty: Easy
19. What is the difference between apply() and commit() in SharedPreferences?
Answer: Both save changes to SharedPreferences, but differ in behavior:
apply(): Asynchronous. Returns immediately. Changes written to disk in background. No return value.commit(): Synchronous. Blocks until changes are written. Returns boolean (success/failure).- Best Practice: Use
apply()unless you need the return value.
val editor = sharedPref.edit()
// apply() - asynchronous, preferred
editor.putString("key", "value")
editor.apply() // Returns immediately
// commit() - synchronous
editor.putString("key", "value")
val success = editor.commit() // Waits for write, returns boolean
if (success) {
// Save successful
}Rarity: Common Difficulty: Easy
20. What is Coroutines in Kotlin?
Answer: Coroutines provide a way to write asynchronous code sequentially, making it easier to read and maintain.
- Benefits: Lightweight, structured concurrency, exception handling
- Key Concepts:
suspend: Function that can be paused and resumedlaunch: Starts a coroutine (fire and forget)async: Starts a coroutine and returns a result- Dispatchers: Control which thread the coroutine runs on
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
try {
// Main thread
showLoading()
// Switch to IO thread for network call
val data = withContext(Dispatchers.IO) {
fetchDataFromNetwork()
}
// Back to main thread
updateUI(data)
} catch (e: Exception) {
handleError(e)
}
}
}
suspend fun fetchDataFromNetwork(): String {
delay(2000) // Simulates network delay
return "Data from server"
}
}
// Parallel execution
viewModelScope.launch {
val deferred1 = async { fetchUser(1) }
val deferred2 = async { fetchUser(2) }
val user1 = deferred1.await()
val user2 = deferred2.await()
}Rarity: Very Common Difficulty: Medium




