20 คำถามสัมภาษณ์ Jetpack Compose ยอดนิยมประจำปี 2026

20 คำถามสัมภาษณ์ Jetpack Compose ที่พบบ่อยที่สุด: recomposition, การจัดการ state, navigation, ประสิทธิภาพ และ pattern สถาปัตยกรรม

คำถามสัมภาษณ์ Jetpack Compose สำหรับนักพัฒนา Android

Jetpack Compose ได้กลายเป็น UI toolkit มาตรฐานสำหรับการพัฒนา Android การสัมภาษณ์ทางเทคนิคในปัจจุบันทดสอบความชำนาญ Compose เป็นประจำ ตั้งแต่กลไก recomposition ไปจนถึงการจัดการ state และการเพิ่มประสิทธิภาพ ต่อไปนี้คือ 20 คำถามที่พบบ่อยที่สุด พร้อมคำตอบโดยละเอียดและตัวอย่างโค้ด

วิธีใช้คู่มือนี้

แต่ละคำถามมีคำตอบที่มีโครงสร้างและตัวอย่างโค้ด คำถามจัดเรียงตามความยากที่เพิ่มขึ้น: พื้นฐาน ระดับกลาง และขั้นสูง

พื้นฐาน Jetpack Compose

1. ความแตกต่างระหว่าง Compose และระบบ view XML คืออะไร?

Compose ใช้ paradigm แบบประกาศ: UI ถูกอธิบายเป็นฟังก์ชันของ state และ framework จัดการการอัปเดตโดยอัตโนมัติ ระบบ XML แบบดั้งเดิมเป็น แบบคำสั่ง ต้องการการจัดการ view ด้วยตนเองผ่าน findViewById หรือ View Binding

DeclarativeExample.ktkotlin
// Compose: UI updates automatically when count changes
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }  // Reactive state
    Button(onClick = { count++ }) {               // UI declaration
        Text("Clicks: $count")                    // Recomposed automatically
    }
}

ด้วย Compose ไม่จำเป็นต้องหา reference ของ TextView และอัปเดตด้วยตนเอง Recomposition จัดการทุกอย่าง

2. Recomposition คืออะไร?

Recomposition คือกระบวนการที่ Compose เรียกฟังก์ชัน @Composable ใหม่เมื่อ state เปลี่ยนแปลง เฉพาะฟังก์ชันที่ parameter เปลี่ยนแปลงเท่านั้นที่จะถูกเรียกใหม่ ซึ่งช่วยเพิ่มประสิทธิภาพ

RecompositionExample.ktkotlin
@Composable
fun UserCard(name: String, age: Int) {
    Column {
        Text("Name: $name")   // Recomposed only if name changes
        Text("Age: $age")     // Recomposed only if age changes
        StaticBadge()          // Not recomposed if its inputs remain the same
    }
}

@Composable
fun StaticBadge() {
    Text("Static badge")  // Compose knows this function is stable
}

จุดสำคัญสำหรับการสัมภาษณ์: recomposition เป็น แบบมองโลกในแง่ดี (Compose สันนิษฐานว่าสามารถยกเลิกได้) และ ไม่เรียงลำดับ (ลำดับการทำงานของ composable ไม่รับประกัน)

3. remember ทำอะไร?

remember รักษาค่าไว้ระหว่างการ recomposition หากไม่มี remember ทุกครั้งที่ recomposition จะรีเซ็ตตัวแปรเป็นค่าเริ่มต้น

RememberExample.ktkotlin
@Composable
fun InputField() {
    // ✅ Value survives recompositions
    var text by remember { mutableStateOf("") }

    // ❌ Without remember, text resets to "" on every recomposition
    // var text by mutableStateOf("")

    TextField(
        value = text,
        onValueChange = { text = it },  // Triggers recomposition
        label = { Text("Enter text") }
    )
}

4. ความแตกต่างระหว่าง remember และ rememberSaveable คืออะไร?

remember รักษาค่าไว้ระหว่าง recomposition แต่สูญเสียเมื่อ configuration change (หมุนหน้าจอ) rememberSaveable รักษาค่าผ่าน configuration change โดยใช้กลไก SavedInstanceState

RememberSaveableExample.ktkotlin
@Composable
fun SearchBar() {
    // Lost after screen rotation
    var query by remember { mutableStateOf("") }

    // Preserved after screen rotation
    var savedQuery by rememberSaveable { mutableStateOf("") }

    TextField(
        value = savedQuery,
        onValueChange = { savedQuery = it },
        placeholder = { Text("Search...") }
    )
}

การจัดการ State ใน Compose

5. State hoisting คืออะไร?

State hoisting หมายถึงการย้าย state ขึ้นจาก composable ไปยัง parent composable ลูกกลายเป็น stateless: รับ state เป็น parameter และแจ้งการเปลี่ยนแปลงผ่าน callback Pattern นี้จำเป็นสำหรับการสร้าง component ที่ใช้ซ้ำได้และทดสอบได้

StateHoistingExample.ktkotlin
// ✅ Stateless composable, easy to test and reuse
@Composable
fun EmailInput(
    email: String,                    // State provided by parent
    onEmailChange: (String) -> Unit,  // Callback to parent
    modifier: Modifier = Modifier
) {
    TextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("Email") },
        modifier = modifier
    )
}

// Parent manages the state
@Composable
fun LoginForm() {
    var email by remember { mutableStateOf("") }
    EmailInput(
        email = email,
        onEmailChange = { email = it }  // Parent controls state
    )
}

Pattern นี้เป็นพื้นฐานใน Compose และปรากฏบ่อยในการสัมภาษณ์

6. derivedStateOf ทำงานอย่างไร?

derivedStateOf สร้าง state ที่ได้รับมาซึ่งจะ trigger recomposition เฉพาะเมื่อผลการคำนวณเปลี่ยนแปลง ไม่ใช่ทุกการแก้ไขของแหล่งที่มา

DerivedStateExample.ktkotlin
@Composable
fun FilteredList(items: List<String>) {
    var searchQuery by remember { mutableStateOf("") }

    // Recalculated only when the filtered result actually changes
    val filteredItems by remember(items) {
        derivedStateOf {
            items.filter { it.contains(searchQuery, ignoreCase = true) }
        }
    }

    Column {
        TextField(value = searchQuery, onValueChange = { searchQuery = it })
        LazyColumn {
            items(filteredItems) { item -> Text(item) }
        }
    }
}
เมื่อใดควรใช้ derivedStateOf

กลไกนี้มีประโยชน์เมื่อ state เปลี่ยนแปลงบ่อยแต่ผลลัพธ์ที่ได้รับมาไม่ค่อยเปลี่ยน (เช่น รายการที่กรอง ปุ่ม enabled/disabled ตามความถูกต้องของฟอร์ม)

7. ความแตกต่างระหว่าง StateFlow และ Compose State<T> คืออะไร?

StateFlow (Kotlin coroutines) เป็น reactive stream จาก ViewModel State<T> เป็นกลไก native ของ Compose สำหรับ trigger recomposition ในทางปฏิบัติ StateFlow ถูก collect ใน composable ผ่าน collectAsStateWithLifecycle() สำหรับความเข้าใจเชิงลึกเกี่ยวกับ coroutine flow ดู คู่มือฉบับสมบูรณ์ Kotlin coroutines

StateFlowExample.ktkotlin
class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
}

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    // Converts StateFlow to State<T> for Compose
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    Text("Hello, ${uiState.userName}")
}

คำแนะนำคือใช้ collectAsStateWithLifecycle() (แทน collectAsState()) เพราะมันเคารพ lifecycle และหยุด collection เมื่อหน้าจอไม่แสดงอีกต่อไป

Side Effect และ Lifecycle

8. Side effect หลักใน Compose มีอะไรบ้าง?

Side effect ช่วยให้รันโค้ดที่ไม่ใช่ composable (เรียก network, logging, navigation) ในลักษณะที่ควบคุมได้ Side effect หลักได้แก่ LaunchedEffect, DisposableEffect, SideEffect และ Keyed SideEffect ใหม่ที่เปิดตัวใน Compose 1.12

SideEffectsExample.ktkotlin
@Composable
fun AnalyticsScreen(screenName: String) {
    // LaunchedEffect: runs once when screenName changes
    LaunchedEffect(screenName) {
        analyticsTracker.logScreenView(screenName)  // Suspended call
    }

    // DisposableEffect: with cleanup (like useEffect with cleanup)
    DisposableEffect(Unit) {
        val listener = onScrollListener()
        scrollView.addListener(listener)
        onDispose {
            scrollView.removeListener(listener)  // Cleanup guaranteed
        }
    }

    // SideEffect: runs after every successful recomposition
    SideEffect {
        logger.log("Screen recomposed")  // Non-suspended code
    }
}

อัปเดต Compose 1.12: Keyed SideEffect มี overload ใหม่ที่รองรับ argument key สำหรับ side effect ครั้งเดียวเมื่อ key เฉพาะเปลี่ยนแปลง ตาม benchmark อย่างเป็นทางการ สามารถเร็วกว่า LaunchedEffect ได้ถึง 90% สำหรับการทำงานแบบ non-suspended

9. เมื่อใดควรใช้ LaunchedEffect vs rememberCoroutineScope?

LaunchedEffect ผูกกับ composition: coroutine ถูกยกเลิกเมื่อ composable ออกจาก composition หรือเมื่อ key เปลี่ยน rememberCoroutineScope มี scope ที่ผู้ใช้ควบคุม มีประโยชน์สำหรับ action ที่ผู้ใช้ trigger (คลิกปุ่ม)

CoroutineScopeExample.ktkotlin
@Composable
fun DataScreen(userId: String) {
    // ✅ LaunchedEffect: automatic loading tied to lifecycle
    LaunchedEffect(userId) {
        loadUserData(userId)  // Re-launched if userId changes
    }

    // ✅ rememberCoroutineScope: one-off user action
    val scope = rememberCoroutineScope()
    Button(onClick = {
        scope.launch { refreshData() }  // Triggered manually
    }) {
        Text("Refresh")
    }
}

พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Layout และ Component ขั้นสูง

10. LazyColumn ทำงานอย่างไรและแตกต่างจาก RecyclerView อย่างไร?

LazyColumn เป็น Compose equivalent ของ RecyclerView มันจะ compose เฉพาะ element ที่แสดงบนหน้าจอและ recycle composable ที่เลื่อนออกจาก visible window

LazyColumnExample.ktkotlin
@Composable
fun UserList(users: List<User>) {
    LazyColumn(
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)  // Spacing between items
    ) {
        items(
            items = users,
            key = { it.id }  // Stable key to optimize recompositions
        ) { user ->
            UserCard(user)
        }
    }
}

จุดสำคัญ: ให้ parameter key ที่เสถียรเสมอเพื่อหลีกเลี่ยง recomposition ที่ไม่จำเป็นเมื่อเรียงลำดับหรือลบ element

11. วิธีสร้าง custom layout อย่างไร?

Compose ช่วยให้สร้าง custom layout ผ่านฟังก์ชัน Layout ซึ่งแทนที่การ implement ViewGroup แบบ custom จากระบบ view

CustomLayoutExample.ktkotlin
@Composable
fun OverlappingRow(
    overlapOffset: Dp = (-16).dp,  // Negative offset for overlap
    content: @Composable () -> Unit
) {
    Layout(content = content) { measurables, constraints ->
        val placeables = measurables.map { it.measure(constraints) }
        val width = placeables.sumOf { it.width } + (overlapOffset.roundToPx() * (placeables.size - 1))
        val height = placeables.maxOf { it.height }

        layout(width, height) {
            var xOffset = 0
            placeables.forEach { placeable ->
                placeable.placeRelative(xOffset, 0)
                xOffset += placeable.width + overlapOffset.roundToPx()
            }
        }
    }
}

12. วิธี implement custom theming ด้วย MaterialTheme?

Theming ใน Compose อาศัย CompositionLocal MaterialTheme มีค่าสี typography และ shape ที่เข้าถึงได้ทั่วทั้ง composable tree

CustomThemeExample.ktkotlin
// Custom color definitions
private val DarkColorScheme = darkColorScheme(
    primary = Color(0xFF6200EE),
    secondary = Color(0xFF03DAC6),
    background = Color(0xFF121212)
)

@Composable
fun AppTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = DarkColorScheme,
        typography = AppTypography,    // Custom typography
        content = content
    )
}

// Usage in a composable
@Composable
fun ThemedCard() {
    Card(colors = CardDefaults.cardColors(
        containerColor = MaterialTheme.colorScheme.surface  // Theme access
    )) {
        Text(
            text = "Content",
            style = MaterialTheme.typography.bodyLarge  // Theme typography
        )
    }
}

13. Compose Navigation ทำงานอย่างไร?

Compose Navigation ใช้ NavHost กับ route ที่ประกาศเป็น serializable type (Navigation 2.8+) ตั้งแต่ Navigation 2.9 library รองรับ value class และ List<Enum> เป็น argument type โดยไม่ต้อง implement NavType แบบ custom

NavigationExample.ktkotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "home") {
        composable("home") {
            HomeScreen(onNavigateToDetail = { id ->
                navController.navigate("detail/$id")  // Navigation with argument
            })
        }
        composable(
            route = "detail/{userId}",
            arguments = listOf(navArgument("userId") { type = NavType.StringType })
        ) { backStackEntry ->
            val userId = backStackEntry.arguments?.getString("userId") ?: ""
            DetailScreen(userId = userId)
        }
    }
}

14. วิธีส่งข้อมูลระหว่างหน้าจอ?

Argument อย่างง่าย (String, Int) ส่งตรงผ่าน route สำหรับ object ที่ซับซ้อน คำแนะนำปัจจุบันคือใช้ ViewModel ร่วมกันหรือส่งเฉพาะ identifier และโหลดข้อมูลที่หน้าจอปลายทาง Navigation 2.9 เปิดตัว CollectionNavType<T> สำหรับ argument แบบ collection เช่น list และ array

NavigationArgsExample.ktkotlin
// Type-safe navigation with Kotlin Serialization (Navigation 2.8+)
@Serializable
data class ProfileRoute(val userId: String, val tab: String = "info")

// Declaration
composable<ProfileRoute> { backStackEntry ->
    val route = backStackEntry.toRoute<ProfileRoute>()
    ProfileScreen(userId = route.userId, tab = route.tab)
}

// Navigation
navController.navigate(ProfileRoute(userId = "123", tab = "stats"))
Anti-pattern ที่ต้องหลีกเลี่ยง

อย่าส่ง object ที่ซับซ้อนที่ serialize ใน route ส่ง ID และให้หน้าจอปลายทางโหลดข้อมูลผ่าน ViewModel สำหรับแนวทางสถาปัตยกรรม ดู MVVM vs MVI: สถาปัตยกรรมไหนที่ควรเลือก

ประสิทธิภาพและการเพิ่มประสิทธิภาพ

15. วิธีป้องกัน recomposition ที่ไม่จำเป็น?

สามกลยุทธ์หลักเพื่อลด recomposition ที่ไม่จำเป็น:

PerformanceExample.ktkotlin
// 1. Use stable classes (data class with immutable properties)
@Stable  // Tells Compose this class is stable
data class UserState(
    val name: String,
    val avatar: String
)

// 2. Extract lambdas with remember
@Composable
fun OptimizedList(onItemClick: (String) -> Unit) {
    val stableCallback = remember(onItemClick) { onItemClick }
    LazyColumn {
        items(100) { index ->
            ItemRow(onClick = { stableCallback("item_$index") })
        }
    }
}

// 3. Use key() to help Compose identify elements
@Composable
fun UserTabs(users: List<User>) {
    Column {
        users.forEach { user ->
            key(user.id) {    // Stable identity
                UserRow(user)
            }
        }
    }
}

16. วิธี profile ประสิทธิภาพแอป Compose?

Layout Inspector ของ Android Studio แสดงจำนวน recomposition ต่อ composable Compose 1.13 alpha เปิดตัว RecompositionTracer เพื่อบันทึก recomposition invalidation flow flag debugInspectorInfo ก็ช่วยในการวินิจฉัย

ProfilingExample.ktkotlin
// Enable recomposition counters in debug
@Composable
fun DebugRecomposition(tag: String, content: @Composable () -> Unit) {
    val recompositionCount = remember { mutableIntStateOf(0) }

    SideEffect {
        recompositionCount.intValue++  // Incremented on every recomposition
        Log.d("Recomposition", "$tag: ${recompositionCount.intValue} times")
    }

    content()
}

// Usage
DebugRecomposition("UserCard") {
    UserCard(user)
}

นอกจากนี้ Compose Compiler Metrics สร้างรายงานโดยละเอียดเกี่ยวกับฟังก์ชัน skippable, restartable และ class stable/unstable

17. Modifier คืออะไรและทำไมสำคัญ?

Modifier คือ chain ของคำสั่งที่เรียงลำดับที่แก้ไขลักษณะและพฤติกรรมของ composable ลำดับของ modifier ส่งผลโดยตรงต่อการ render

ModifierOrderExample.ktkotlin
@Composable
fun ModifierOrderDemo() {
    // ❌ Padding THEN background = padding not colored
    Text(
        text = "Hello",
        modifier = Modifier
            .padding(16.dp)
            .background(Color.Red)
    )

    // ✅ Background THEN padding = padding is colored
    Text(
        text = "Hello",
        modifier = Modifier
            .background(Color.Red)
            .padding(16.dp)
    )
}

แนวปฏิบัติที่ดี: รับ parameter modifier: Modifier = Modifier ใน composable ที่ใช้ซ้ำได้เสมอเพื่อให้ parent ปรับแต่งได้

สถาปัตยกรรมและ Pattern ขั้นสูง

18. วิธีจัดโครงสร้างหน้าจอ Compose ด้วย ViewModel?

Pattern ที่แนะนำแยก UI state ใน data class, event ใน sealed interface และ ViewModel จัดการ business logic

ScreenArchitectureExample.ktkotlin
// UI State
data class ProfileUiState(
    val user: User? = null,
    val isLoading: Boolean = false,
    val error: String? = null
)

// User events
sealed interface ProfileEvent {
    data object Refresh : ProfileEvent
    data class UpdateName(val name: String) : ProfileEvent
}

// ViewModel
class ProfileViewModel(private val repo: UserRepository) : ViewModel() {
    private val _uiState = MutableStateFlow(ProfileUiState(isLoading = true))
    val uiState = _uiState.asStateFlow()

    fun onEvent(event: ProfileEvent) {
        when (event) {
            is ProfileEvent.Refresh -> loadProfile()
            is ProfileEvent.UpdateName -> updateName(event.name)
        }
    }
}

// Compose screen
@Composable
fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    ProfileContent(
        uiState = uiState,
        onEvent = viewModel::onEvent  // Event delegation
    )
}

19. วิธีทดสอบ composable?

Compose มี testing library ด้วย ComposeTestRule สำหรับ UI test และ semantic assertion ตั้งแต่ Compose 1.11 testing API v2 เป็น default โดย v1 deprecated การเปลี่ยนแปลงหลักคือการเปลี่ยนจาก UnconfinedTestDispatcher (execution ทันที) เป็น StandardTestDispatcher (execution แบบ queue) หมายความว่า test ตอนนี้สะท้อนพฤติกรรม production ได้ดีขึ้น

ComposableTestExample.ktkotlin
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun counter_incrementsOnClick() {
    composeTestRule.setContent {
        Counter()  // The composable under test
    }

    // Verify initial state
    composeTestRule.onNodeWithText("Clicks: 0").assertIsDisplayed()

    // Simulate a click
    composeTestRule.onNodeWithText("Clicks: 0").performClick()

    // With v2 APIs, advance the clock explicitly
    composeTestRule.waitForIdle()

    // Verify new state
    composeTestRule.onNodeWithText("Clicks: 1").assertIsDisplayed()
}

สำหรับ unit testing stateless composable การทดสอบ ViewModel แยกด้วย JUnit/Turbine test มาตรฐานมักมีประสิทธิภาพมากกว่า

20. วิธีรวม Compose เข้ากับแอปที่ใช้ XML เดิม?

Interoperability เป็นแบบสองทาง: ComposeView ฝัง Compose ใน XML และ AndroidView ใช้ classic view ใน Compose

InteropExample.ktkotlin
// Compose in XML (in a Fragment or Activity)
class ProfileFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(
                ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
            )
            setContent {
                AppTheme { ProfileScreen() }
            }
        }
    }
}

// XML View in Compose
@Composable
fun LegacyMapView() {
    AndroidView(
        factory = { context -> MapView(context).apply { onCreate(null) } },
        update = { mapView -> mapView.getMapAsync { /* config */ } }
    )
}
กลยุทธ์การย้ายที่แนะนำ

ย้ายทีละหน้าจอ เริ่มจากหน้าจอที่ง่ายที่สุด ทุกหน้าจอใหม่ควรเป็น Compose ทั้งหมด ในขณะที่หน้าจอเดิมย้ายทีละน้อย

พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

แหล่งข้อมูล

ประเด็นสำคัญสำหรับการสัมภาษณ์ Jetpack Compose

20 คำถามนี้ครอบคลุมพื้นฐานที่นักพัฒนา Android ทุกคนต้องเชี่ยวชาญสำหรับการสัมภาษณ์ Jetpack Compose สำหรับการฝึกปฏิบัติ ลอง module คำถามสัมภาษณ์ Jetpack Compose

  • ✅ เข้าใจ recomposition และพฤติกรรมแบบมองโลกในแง่ดี
  • ✅ เชี่ยวชาญ remember, rememberSaveable และ derivedStateOf
  • ✅ ใช้ state hoisting อย่างเป็นระบบ
  • ✅ รู้ side effect (LaunchedEffect, DisposableEffect, SideEffect และ Keyed SideEffect ใหม่)
  • ✅ เพิ่มประสิทธิภาพ (stable class, key, lambda)
  • ✅ จัดโครงสร้างหน้าจอด้วย ViewModel + UiState + Event
  • ✅ ทดสอบ composable ด้วย ComposeTestRule โดยใช้ testing API v2
  • ✅ จัดการ interoperability Compose/View

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Android เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 20 สิงหาคม 2569

แท็ก

#jetpack compose
#android
#interview
#kotlin
#ui

แชร์

บทความที่เกี่ยวข้อง