PracHub
QuestionsCoachesLearningGuidesInterview Prep

Quick Overview

This question evaluates proficiency in Android UI development with Kotlin and Jetpack Compose, emphasizing custom composable design (a stateless star-rating component), MVVM-style state management via a ViewModel, form validation, and handling asynchronous submission and success states.

  • medium
  • OpenAI
  • Coding & Algorithms
  • Android Engineer

Build a Compose Rating Card

Company: OpenAI

Role: Android Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

Build a **rating card** in Android using **Kotlin** and **Jetpack Compose**. The card lets a user leave feedback consisting of a star rating and a written comment, then submit it. The core requirements: - The card contains a **star rating** input and a **multi-line comment** text field. - The **Submit** button is **disabled** until the user has provided **both** a rating *and* a non-empty comment. - After a **successful submission**, the card shows a **success state** (a confirmation message / success UI). - Jetpack Compose has **no built-in rating bar**, so implement the star component yourself. - Manage UI state with a **`ViewModel`** (MVVM-style). Keep the Composables stateless where reasonable and hoist state up. - Code should be **clean and readable** — understandable at a glance without lengthy explanation. ### Constraints & Assumptions - Rating scale is **1 to 5 stars** (integer steps; half-stars are out of scope unless you choose to discuss them). - Single screen / single card — no navigation, no persistence layer required (an in-memory or mocked submit is fine). - "Submission" can be simulated (e.g. a `suspend` call with a short delay) — you do **not** need a real network/REST client, but your design should make swapping one in trivial. - Target a modern Compose + Material 3 setup; you may assume a recent stable Compose BOM. - Optimize for clarity and correct state handling over feature breadth. ```hint Where to start Separate the three concerns: a **stateless** `StarRating` composable, a **comment** field, and a **`RatingCard`** that wires them to a `ViewModel`. Think about state hoisting — which Composables own state, and which just render values and report events upward? ``` ```hint State model Try to drive the whole card from one observable state object exposed by the `ViewModel` (e.g. a `StateFlow` of an immutable UI-state class). Then ask: should "is Submit allowed?" be a stored flag, or computed from that state? Consider what goes wrong if the button's enabled-state and the validation rule are tracked separately. ``` ```hint Submission state A loose boolean (`isLoading`) struggles to express "submitting vs. succeeded vs. failed" at once. Consider what a richer representation of the submission lifecycle buys you, and how the button label/spinner and the success UI could be driven off it. The async work belongs off the UI thread. ``` ```hint Custom star bar With no built-in widget, you're rendering your own row of star icons. The two questions to answer: given the current rating, which stars look "selected"? And when the user taps a star, what rating does that produce? Keep the component stateless and don't forget accessibility for each star. ``` ### Clarifying Questions to Ask - Is the rating a strict 1–5 integer, or do we need half-star / fractional support? - Can a user **edit** their rating/comment after tapping a star, and can they re-rate after a successful submit (reset flow)? - Should the comment field have a **character limit** or other validation beyond "non-empty"? - What should happen if the simulated submit **fails** — show an error and allow retry, or is the happy path sufficient for this exercise? - Should the rating state **survive configuration changes / process death** (e.g. `SavedStateHandle`), or is in-memory `ViewModel` state enough? - Is there a design spec for the stars (size, color, spacing), or is reasonable Material 3 styling acceptable? ### What a Strong Answer Covers - **Unidirectional data flow / state hoisting**: stateless leaf composables, a single source of truth in the `ViewModel`. - **Single observable state** (`StateFlow`/`State`) with `canSubmit` *derived*, not duplicated — so the disabled-button rule and validation are guaranteed consistent. - A **correct custom star bar**: fill logic keyed off the index, tap-to-set, and accessibility (`contentDescription`, sensible touch targets). - **Explicit submission status** (idle/submitting/success, ideally error) driving the button and the success UI; async handled in `viewModelScope` so the UI thread isn't blocked. - **Recomposition awareness**: stable state, no business logic in the composable, `collectAsStateWithLifecycle`/`collectAsState` to observe. - **Readability**: small focused composables, clear naming, no overengineering — matching the "understandable at a glance" bar. - Brief mention of **testability** (the `ViewModel` logic is unit-testable without UI) and **previews**. ### Follow-up Questions - How would you make the rating and comment **survive process death** and configuration changes? - How would you **unit-test** the `ViewModel` (validation gating, submit success/failure transitions), and how would you UI-test the star bar with Compose testing APIs? - How would you extend the star bar to support **half-star** ratings or a drag/swipe gesture across the stars? - If `submit()` hit a real network endpoint, how would you handle **loading, error, and retry**, and prevent **double-submission** on rapid taps? - How would you make the component **reusable** across screens (theming, configurable max stars, custom icons) without leaking ViewModel coupling?

Quick Answer: This question evaluates proficiency in Android UI development with Kotlin and Jetpack Compose, emphasizing custom composable design (a stateless star-rating component), MVVM-style state management via a ViewModel, form validation, and handling asynchronous submission and success states.

Part 1: Simulate a ViewModel-Driven Rating Card

You are given a sequence of UI events for a rating card. The card has a rating from 1 to 5, a multi-line comment, and a Submit button. Rating 0 means no rating has been selected. The Submit button is enabled only when a valid rating is selected, the trimmed comment is non-empty, and the card is not already in the success state. A submit event succeeds immediately if submission is allowed. Editing the rating or comment after success returns the card to idle.

Constraints

  • 0 <= len(events) <= 10000
  • Ratings are intended to be integers from 1 to 5
  • Comment length is at most 1000 characters per event
  • All event names are strings; unknown event names should be ignored

Examples

Input: ([],)

Expected Output: ['0', '', 'idle', 'false', '']

Explanation: With no events, no rating or comment exists, so submit is disabled.

Input: ([('rate', '5'), ('comment', 'Great app'), ('submit',)],)

Expected Output: ['5', 'Great app', 'success', 'false', 'Thanks for rating 5 stars!']

Explanation: Both required fields are present, so submit succeeds and the card enters success state.

Input: ([('comment', ' '), ('rate', '3'), ('submit',)],)

Expected Output: ['3', ' ', 'idle', 'false', '']

Explanation: Whitespace-only comments do not count as non-empty.

Input: ([('rate', '6'), ('comment', 'ok'), ('submit',)],)

Expected Output: ['0', 'ok', 'idle', 'false', '']

Explanation: Rating 6 is invalid and ignored, so submission is blocked.

Input: ([('rate', '2'), ('comment', 'ok'), ('submit',), ('comment', 'updated')],)

Expected Output: ['2', 'updated', 'idle', 'true', '']

Explanation: Editing after success resets the card to idle, and the new valid state can be submitted.

Hints

  1. Keep only one source of truth: rating, comment, and status. Derive canSubmit instead of storing it.
  2. Treat submit as a state transition that only happens when the current derived validation passes.

Part 2: Restore Rating Card State After Rotation and Process Death

Simulate a rating card ViewModel backed by a saved-state bundle. Rating and comment should survive both configuration changes and process death. Transient submission status does not survive process death: after recreation, status becomes idle while the saved rating and comment are restored. A rotation event does not recreate the ViewModel, so nothing changes.

Constraints

  • 0 <= len(actions) <= 10000
  • Saved rating is always an integer from 0 to 5
  • Comment length is at most 1000 characters per event
  • Process death restores only rating and comment, not transient success status

Examples

Input: ([],)

Expected Output: ['0', '', 'idle', 'false']

Explanation: Initial state has no saved input.

Input: ([('rate', '4'), ('comment', 'nice'), ('rotate',)],)

Expected Output: ['4', 'nice', 'idle', 'true']

Explanation: Rotation does not lose ViewModel state.

Input: ([('rate', '5'), ('comment', 'Done'), ('submit',), ('process_death',)],)

Expected Output: ['5', 'Done', 'idle', 'true']

Explanation: Rating and comment are restored, but success status is transient and resets to idle.

Input: ([('rate', '3'), ('comment', ' '), ('process_death',)],)

Expected Output: ['3', ' ', 'idle', 'false']

Explanation: Whitespace comments survive, but still do not satisfy validation.

Input: ([('rate', '2'), ('comment', 'ok'), ('process_death',), ('rate', '9'), ('process_death',)],)

Expected Output: ['2', 'ok', 'idle', 'true']

Explanation: Invalid rating 9 is ignored and does not overwrite the saved rating.

Hints

  1. Think of two copies of state: the live ViewModel state and the persisted saved-state bundle.
  2. On process death, rebuild live state from the saved bundle and reset transient submission fields.

Part 3: Run Deterministic Tests for the Rating ViewModel and Star Bar

Build a tiny deterministic test runner for a rating-card model. Each scenario starts from a fresh initial state. Actions mutate the model, and expect steps assert values. Return the zero-based indexes of failed assertions across all scenarios. This mirrors unit-testing ViewModel validation and UI-testing a stateless star bar.

Constraints

  • 0 <= number of scenarios <= 1000
  • 0 <= total steps across all scenarios <= 10000
  • Ratings/taps outside 1 to 5 are ignored
  • Each scenario starts from rating 0, empty comment, and idle status

Examples

Input: ([],)

Expected Output: []

Explanation: No scenarios means no assertions can fail.

Input: ([[('expect', 'canSubmit', 'false'), ('tap', '4'), ('expect', 'selectedStars', '11110'), ('comment', 'Good'), ('expect', 'canSubmit', 'true'), ('submit',), ('expect', 'status', 'success'), ('expect', 'canSubmit', 'false')]],)

Expected Output: []

Explanation: All assertions match the expected state transitions and star rendering.

Input: ([[('tap', '3'), ('expect', 'canSubmit', 'true')]],)

Expected Output: [0]

Explanation: A rating alone is not enough; the comment is missing.

Input: ([[('comment', 'ok'), ('expect', 'canSubmit', 'false')], [('tap', '6'), ('expect', 'rating', '0'), ('tap', '2'), ('expect', 'selectedStars', '10000')]],)

Expected Output: [2]

Explanation: The third assertion fails because rating 2 renders '11000', not '10000'.

Input: ([[('comment', ' '), ('tap', '1'), ('submit',), ('expect', 'status', 'idle'), ('expect', 'canSubmit', 'false')]],)

Expected Output: []

Explanation: Whitespace-only comment blocks submission, so status remains idle.

Hints

  1. Separate action execution from assertion evaluation.
  2. For selectedStars, star i is selected exactly when i <= current rating.

Part 4: Convert Tap and Drag Positions to Half-Star Ratings

A custom star bar supports half-star ratings. The bar contains max_stars stars, each with the same width, separated by a fixed gap. Given tap or drag x-coordinates measured from the start of the bar, convert each coordinate into the rating it should select. Positions before the bar select 0.0. Positions beyond the end select max_stars. Inside a star, the left half selects k + 0.5 and the right half selects k + 1.0. Inside a gap, the previous full star remains selected.

Constraints

  • 0 <= max_stars <= 100000
  • 1 <= star_width <= 100000
  • 0 <= gap <= 100000
  • 0 <= len(coordinates) <= 100000
  • Coordinates may be negative or beyond the end of the bar

Examples

Input: (5, 20, 4, [-5, 0, 9, 10, 19, 20, 23, 24, 34, 200])

Expected Output: [0.0, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.5, 2.0, 5.0]

Explanation: Coordinates in the first gap keep rating 1.0; coordinate 24 starts the second star.

Input: (5, 20, 0, [0, 10, 20, 30, 99, 100])

Expected Output: [0.5, 1.0, 1.5, 2.0, 5.0, 5.0]

Explanation: With no gaps, every 20 pixels begins a new star except the final boundary, which is full max rating.

Input: (5, 20, 4, [])

Expected Output: []

Explanation: No interactions produce no ratings.

Input: (0, 20, 4, [-1, 0, 100])

Expected Output: [0.0, 0.0, 0.0]

Explanation: A bar with zero stars cannot select a positive rating.

Input: (3, 10, 5, [9, 10, 14, 15])

Expected Output: [1.0, 1.0, 1.0, 1.5]

Explanation: Coordinates 10 through 14 are in the gap after the first star.

Hints

  1. Each star plus following gap forms a repeated cell of size star_width + gap, except there is no gap after the last star.
  2. First clamp positions before the bar and beyond the total width, then handle star area versus gap area.

Part 5: Simulate Network Submission With Retry and Double-Submit Prevention

A real rating-card submission has loading, error, retry, and success states. Simulate this state machine. Submit starts a network request only if rating and comment are valid and no request is already in flight. Rapid repeated submits while submitting are ignored. Network resolution events include a request id; stale resolutions for non-current requests are ignored. After an error, the same valid input may be retried.

Constraints

  • 0 <= len(events) <= 10000
  • Valid ratings are integers from 1 to 5
  • Comment length is at most 1000 characters per event
  • While submitting, rating/comment edits and extra submit events are ignored

Examples

Input: ([('submit',), ('rate', '5'), ('submit',)],)

Expected Output: ['5', '', 'idle', 'false', '0', 'none', '']

Explanation: Both submits are invalid because the comment is missing.

Input: ([('rate', '4'), ('comment', 'ok'), ('submit',), ('submit',), ('resolve', '1', 'success')],)

Expected Output: ['4', 'ok', 'success', 'false', '1', 'none', '']

Explanation: The second submit during loading is ignored, so only one request starts.

Input: ([('rate', '3'), ('comment', 'ok'), ('submit',), ('resolve', '1', 'error'), ('submit',), ('resolve', '2', 'success')],)

Expected Output: ['3', 'ok', 'success', 'false', '2', 'none', '']

Explanation: After an error, retry starts request 2 and it succeeds.

Input: ([('rate', '3'), ('comment', 'ok'), ('submit',), ('resolve', '2', 'error')],)

Expected Output: ['3', 'ok', 'submitting', 'false', '1', '1', '']

Explanation: Resolution for request 2 is stale because only request 1 exists, so it is ignored.

Input: ([('rate', '1'), ('comment', 'a'), ('submit',), ('comment', 'b'), ('resolve', '1', 'error')],)

Expected Output: ['1', 'a', 'error', 'true', '1', 'none', 'Submission failed']

Explanation: Edits while submitting are ignored. The failed valid request can be retried.

Hints

  1. Model submission status as idle, submitting, success, or error rather than multiple loose booleans.
  2. Store the current pending request id so stale network completions cannot change state.

Part 6: Render a Reusable Configurable Rating Component

A reusable star-rating component should not depend on a ViewModel. It receives all configuration and current value as inputs and returns render descriptors. Support arbitrary max star count, optional half-star visuals, custom icon names, enabled/read-only behavior, and accessibility labels.

Constraints

  • 0 <= max_stars <= 100000
  • rating may be below 0 or above max_stars and must be clamped
  • step is either 1.0 or 0.5
  • symbols contains only string keys and string values

Examples

Input: (5, 3, 1.0, True, {'full': 'X', 'half': '/', 'empty': '-'})

Expected Output: ['X|true|1|Rate 1 of 5', 'X|true|2|Rate 2 of 5', 'X|true|3|Rate 3 of 5', '-|false|4|Rate 4 of 5', '-|false|5|Rate 5 of 5']

Explanation: Full-star mode ignores fractional visuals and renders three selected stars.

Input: (5, 3.5, 0.5, True, {'full': 'X', 'half': '/', 'empty': '-'})

Expected Output: ['X|true|1|Rate 1 of 5', 'X|true|2|Rate 2 of 5', 'X|true|3|Rate 3 of 5', '/|true|4|Rate 4 of 5', '-|false|5|Rate 5 of 5']

Explanation: Half-star mode renders the fourth star as half-selected.

Input: (1, 0, 1.0, False, {'full': 'F', 'empty': 'E'})

Expected Output: ['E|false|none|empty star 1 of 1']

Explanation: Disabled/read-only mode has no click value and uses a read-only label.

Input: (3, 10, 0.5, True, {'full': 'F', 'half': 'H', 'empty': 'E'})

Expected Output: ['F|true|1|Rate 1 of 3', 'F|true|2|Rate 2 of 3', 'F|true|3|Rate 3 of 3']

Explanation: Rating is clamped to max_stars.

Input: (0, 2, 1.0, True, {'full': 'F', 'empty': 'E'})

Expected Output: []

Explanation: A component configured with zero stars renders nothing.

Hints

  1. Make rendering a pure function of configuration and current rating.
  2. For half-star mode, star i is half-filled when rating is at least i - 0.5 but less than i.
Last updated: Jul 1, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,000+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • AI Coding Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Consistent Hashing Ring with Virtual Nodes for Shard Rebalancing - OpenAI (hard)
  • Infection Spread Simulation with Death Threshold - OpenAI (medium)
  • Spreading Contagion on a Grid - OpenAI (medium)
  • Streaming Entropy with Numerical Stability - OpenAI (hard)
  • Implement a Distributed Rate Limiter - OpenAI (medium)