Design a music player with favorites cap
Company: Rippling
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
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.
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
- Use a hash map for the song library, a deque for queued songs, and two stacks/lists for backward and forward navigation.
- 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.