PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep

Quick Overview

This question evaluates a candidate's ability to design and implement in-memory data structures, stateful APIs, concurrency handling, deterministic policy enforcement for a fixed-capacity favorites feature, and reasoning about edge cases like removing the currently playing track or handling invalid IDs.

  • Medium
  • Rippling
  • Coding & Algorithms
  • Software Engineer

Design a music player with favorites cap

Company: Rippling

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: Medium

Interview Round: Onsite

Implement an in-memory music player that supports: addSong(id, metadata), removeSong(id), play(id), pause(), next(), prev(), getNowPlaying(), queueSong(id); and a favorites feature with fixed capacity 3 via addFavorite(id), removeFavorite(id), listFavorites(). Do not use an LRU cache; when attempting to add a fourth favorite, define deterministic behavior (e.g., reject with error). Handle corner cases: removing the currently playing song, duplicate adds, empty queue navigation, invalid song IDs, and concurrent calls. Describe chosen data structures, time/space complexities, and provide unit tests.

Quick Answer: This question evaluates a candidate's ability to design and implement in-memory data structures, stateful APIs, concurrency handling, deterministic policy enforcement for a fixed-capacity favorites feature, and reasoning about edge cases like removing the currently playing track or handling invalid IDs.

Implement `solution(operations)` to simulate an in-memory music player. Each element of `operations` is a tuple whose first value is a command name. Supported commands: - `('addSong', id, metadata)` -> add a song to the library. - `('removeSong', id)` -> remove a song from the library. - `('play', id)` -> start playing that song immediately. - `('pause',)` -> pause the current song. - `('next',)` -> go to the next song. - `('prev',)` -> go to the previous song. - `('getNowPlaying',)` -> inspect the current song. - `('queueSong', id)` -> append a song to the play queue. - `('addFavorite', id)` -> add a song to favorites. - `('removeFavorite', id)` -> remove a song from favorites. - `('listFavorites',)` -> list favorite song IDs. Behavior rules: - Song IDs are unique in the library. Adding the same song twice is an error. - `queueSong` may queue the same song more than once. - `play(id)` starts that song immediately. If a different song was current, it becomes the previous song. A manual `play` clears forward history created by `prev()`. - `prev()` moves to the most recent valid previously played song. - `next()` first uses forward history created by `prev()`. If there is no forward history, it uses the queue. - `pause()` only succeeds if a song is currently selected. - `getNowPlaying()` returns `None` if nothing is selected. Otherwise return `(id, metadata, state)` where `state` is `'playing'` or `'paused'`. - Favorites preserve insertion order and have fixed capacity 3. - `addFavorite(id)` must reject a fourth favorite with `ERROR: FavoritesFull`. - Removing a song deletes it from the library and from favorites. If it was currently playing, playback stops. Any stale copies of that song left in queue/history must be skipped during future navigation. - For the design portion, assume operations should be atomic under concurrent calls. In Python, a lock around shared state is sufficient. Return a list with one result per command: - Successful mutating commands return `'OK'` - Failures return one of these strings: `ERROR: DuplicateSong`, `ERROR: SongNotFound`, `ERROR: NothingPlaying`, `ERROR: NoNextSong`, `ERROR: NoPreviousSong`, `ERROR: AlreadyFavorite`, `ERROR: FavoritesFull`, `ERROR: FavoriteNotFound` - `getNowPlaying` and `listFavorites` return their values directly

Constraints

  • 0 <= len(operations) <= 20000
  • Song IDs are integers; metadata is a string of length at most 100
  • Favorites capacity is exactly 3; queue entries may contain duplicate song IDs
  • An efficient solution should use hash-based lookup plus queue/history structures and process most commands in amortized O(1)

Examples

Input: [('addSong', 1, 'A'), ('addSong', 2, 'B'), ('addSong', 3, 'C'), ('play', 1), ('queueSong', 2), ('queueSong', 3), ('next',), ('getNowPlaying',), ('prev',), ('pause',), ('getNowPlaying',), ('addFavorite', 1), ('addFavorite', 2), ('addFavorite', 3), ('listFavorites',)]

Expected Output: ['OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', (2, 'B', 'playing'), 'OK', 'OK', (1, 'A', 'paused'), 'OK', 'OK', 'OK', [1, 2, 3]]

Explanation: Basic flow: play song 1, queue songs 2 and 3, move next to 2, go back to 1, pause it, then fill the favorites list to its capacity.

Input: [('addSong', 10, 'X'), ('addSong', 10, 'X2'), ('play', 99), ('next',), ('prev',), ('pause',), ('addFavorite', 10), ('addSong', 11, 'Y'), ('addSong', 12, 'Z'), ('addSong', 13, 'W'), ('addFavorite', 11), ('addFavorite', 12), ('addFavorite', 13), ('listFavorites',)]

Expected Output: ['OK', 'ERROR: DuplicateSong', 'ERROR: SongNotFound', 'ERROR: NoNextSong', 'ERROR: NoPreviousSong', 'ERROR: NothingPlaying', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'ERROR: FavoritesFull', [10, 11, 12]]

Explanation: Covers duplicate song insertion, invalid play, navigation on empty history/queue, pausing when nothing is selected, and deterministic rejection of a fourth favorite.

Input: [('addSong', 1, 'A'), ('addSong', 2, 'B'), ('addSong', 3, 'C'), ('play', 1), ('queueSong', 2), ('queueSong', 1), ('addFavorite', 1), ('next',), ('removeSong', 2), ('getNowPlaying',), ('prev',), ('getNowPlaying',), ('removeSong', 1), ('listFavorites',), ('next',), ('queueSong', 2)]

Expected Output: ['OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', None, 'OK', (1, 'A', 'playing'), 'OK', [], 'ERROR: NoNextSong', 'ERROR: SongNotFound']

Explanation: Removing the currently playing song stops playback. Later `prev()` can still reach an earlier valid song. Removing song 1 also clears it from favorites, and stale queued copies are skipped so `next()` fails.

Input: []

Expected Output: []

Explanation: No operations means no results.

Input: [('addSong', 1, 'A'), ('addSong', 2, 'B'), ('addSong', 3, 'C'), ('play', 1), ('queueSong', 2), ('queueSong', 3), ('next',), ('prev',), ('next',), ('getNowPlaying',)]

Expected Output: ['OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', (2, 'B', 'playing')]

Explanation: After moving from 1 to 2, `prev()` returns to 1 and stores 2 in forward history. The next `next()` must use that forward history first, so it returns to 2 before considering queued song 3.

Hints

  1. Use a hash map for the song library, a deque for queued songs, and two stacks/lists for backward and forward navigation.
  2. When a song is removed, you do not have to eagerly scan and delete it from every history structure. Lazy deletion works well: skip invalid song IDs when `next()` or `prev()` reaches them.
Last updated: Apr 21, 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
  • 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

  • Implement a Searchable Logger Pipeline - Rippling (hard)
  • Implement an In-Memory File System - Rippling
  • Compare Complete and Partial Poker Hands - Rippling (medium)
  • Implement Courier Delivery Cost Tracking - Rippling (medium)
  • Implement Article Vote Tracking - Rippling (medium)