Android Room Interview Questions

SQLite persistence, entities, DAOs, and migrations.

50 questions in this topic · 8 sample questions below

Practice Room in the quiz engine

Sample questions

  1. A DAO returns Flow<List<User>> from a query on the users table. You update an unrelated row in the same table. What does Room's observer emit?

    • Nothing, because no row matching the query changed
    • A fresh emission of the full query result, because invalidation is tracked at table granularity — correct
    • Only the changed row wrapped in a delta object
    • An error, because concurrent writes invalidate the Flow

    Why: Room's InvalidationTracker works at the table level, so any write to an observed table re-runs the query and re-emits the whole result. The idea that Room emits only changed rows is a common misconception; add distinctUntilChanged to suppress identical re-emissions.

  2. You call fallbackToDestructiveMigration() and ship a schema-version bump without a Migration object. On update, what happens to existing user data?

    • Room copies old rows into the new schema automatically
    • Room keeps the old tables and adds the new columns as null
    • Room drops and recreates all tables, deleting existing data — correct
    • Room throws IllegalStateException and blocks the app from launching

    Why: fallbackToDestructiveMigration tells Room to drop and recreate the database when no migration path exists, so all local data is lost. Believing it preserves data is the classic trap; use a Migration object to keep data.

  3. Why does Room require @Transaction on a DAO method that returns an @Relation-based POJO?

    • Because relation queries are always slower and need a lock
    • Because Room runs the parent query and each child query separately, and @Transaction makes that set of reads consistent — correct
    • Because @Relation only works inside a write transaction
    • Because suspend functions cannot run without a transaction

    Why: Room resolves an @Relation by issuing a separate query for the child data, so without @Transaction the parent and child reads could observe different states after a concurrent write. It is about read consistency, not that @Relation mechanically requires a transaction.

  4. On which thread does a suspend DAO function annotated with a @Query execute its actual database I/O?

    • On the thread that called it, typically Main if called from a coroutine on Dispatchers.Main
    • On Dispatchers.IO only if you wrap the call in withContext(Dispatchers.IO)
    • On a Room-managed background dispatcher (the query/transaction executor), regardless of the caller's dispatcher — correct
    • On the Main thread, which is why long queries can cause ANRs

    Why: Room's generated code for suspend DAO functions dispatches the blocking I/O onto its own query executor, so you can safely call them from Main without manual withContext. The belief that they run on the caller's thread or need Dispatchers.IO is a widespread misconception.

  5. You model a many-to-many relationship between Student and Course. What is the idiomatic Room approach?

    • Store a comma-separated list of course ids in a column on Student
    • Use nested @Embedded objects on both entities
    • Declare a List<Course> field on Student and let Room persist it automatically
    • Create a junction/cross-ref entity holding both foreign keys and reference it via @Relation(associateBy = Junction(...)) — correct

    Why: Room has no notion of a persistable collection field, so many-to-many needs an explicit cross-reference table wired with associateBy = Junction. Room cannot persist a raw List<Course>, so that option would fail to compile.

  6. A query loads 500 authors, each with an @Relation list of books, producing one child query per author. What is this pattern called and Room's mitigation?

    • It is cursor thrashing; Room fixes it by enabling WAL mode
    • It is the N+1 query problem; Room batches the child lookups into chunked IN queries under @Transaction — correct
    • It is a Cartesian explosion; Room fixes it with SELECT DISTINCT
    • It is index starvation; Room fixes it by auto-creating indices on relations

    Why: Loading a parent list plus per-parent relation queries is the N+1 problem, and Room's generated relation code batches child ids into IN (...) queries rather than firing one round trip per parent. WAL mode and SELECT DISTINCT do not address the fan-out of relation queries.

  7. What does @Upsert do that @Insert(onConflict = REPLACE) does not?

    • It always deletes the old row and re-inserts, resetting auto-generated ids
    • It merges only the changed columns computed by a diff
    • It is identical; @Upsert is just a shorthand alias
    • It updates the existing row in place when a conflict occurs, preserving unrelated columns and foreign-key children — correct

    Why: @Upsert attempts an insert and falls back to an UPDATE on conflict, so it does not delete the row, unlike REPLACE which does a DELETE + INSERT that can cascade to child rows and reassign auto ids. Treating them as identical is the trap.

  8. With a foreign key declared onDelete = CASCADE from Book to Author, when do the child Book rows actually get deleted?

    • Automatically, but only if you call PRAGMA foreign_keys manually each session
    • Never; Room ignores onDelete and you must delete children yourself
    • Only when you also add @Delete methods for both entities
    • Automatically by SQLite when the parent Author row is deleted, because Room enables foreign key enforcement — correct

    Why: Room turns on SQLite foreign-key enforcement, so a CASCADE deletes dependent child rows automatically when the parent is removed. You do not need to run PRAGMA foreign_keys yourself; Room does it for every connection.

Practice all 50 Room questions

These 8 are a sample. The full Room bank is scored, tracks your progress, and explains every answer.

Open the quiz

More Android interview topics