Bird
Raised Fist0

Design a Music Streaming Library - Tracks, Albums, Playlists, Playback Queue

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
🏗️
Design a Music Streaming Library - Tracks, Albums, Playlists, Playback Queue
Design a scalable and extensible music streaming library managing tracks, albums, playlists, and playback queue with concurrency control.
mediumLibrary and Catalog SystemsFreq: medium
Prerequisites:OOP DesignSOLID PrinciplesJava Concurrency Basics
⚙️
Functional Requirements
  • Support storing metadata for Tracks and Albums
  • Allow users to create and manage multiple Playlists
  • PlaybackQueue must support concurrent access and modifications
  • Ensure track order is maintained in Playlists and PlaybackQueue
  • Provide thread-safe methods to add/remove tracks from Playlists and PlaybackQueue
  • Support querying tracks by album or playlist
  • Allow playback engine to fetch next track atomically
👤
Use Cases
  • User searches and browses tracks and albums
  • User creates, modifies, and deletes playlists
  • User adds/removes tracks to/from playlists
  • User plays tracks via playback queue
  • Playback engine manages playback queue and track transitions
🧑‍💻
Actors
  • User
  • System Administrator
  • Playback Engine
❓
Clarifying Questions to Ask
  • How many concurrent threads can modify playlists or playback queue? (Expected: multiple user threads + playback engine thread)
  • Is there a pricing strategy affecting track availability? (No, out of scope)
  • What happens if a track is removed while playing? (Playback engine should handle gracefully)
  • Are playlists shared among users or private? (Assume private per user for simplicity)
  • Should playback queue support shuffle or repeat modes? (Out of scope for v1)
Out of Scope
✗ User authentication and authorization
✗ Network streaming protocols
✗ Audio decoding and rendering
✗ Pricing and subscription management
🔢
Enumerations
enum TrackGenre
POP | ROCK | JAZZ | CLASSICAL | HIPHOP | ELECTRONIC
Enum prevents typo bugs in genre assignment and enables exhaustive switch statements for genre-specific logic.
enum PlaybackStatus
STOPPED | PLAYING | PAUSED
Enum models playback state explicitly, preventing invalid states and enabling clear state transitions.
📦
Track
5 fields
private final String idprivate final String titleprivate final String artistprivate final int durationSecondsprivate final TrackGenre genre
Represents a music track with immutable metadata.
public String getId()Returns the unique identifier of the track.
public String getTitle()Returns the title of the track.
public int getDurationSeconds()Returns the duration of the track in seconds.
📦
Album
4 fields
private final String idprivate final String nameprivate final String artistprivate final List<Track> tracks
Aggregates multiple tracks under a single album entity.
public synchronized void addTrack(Track track)Adds a track to the album in a thread-safe manner.
public List<Track> getTracks()Returns an unmodifiable list of tracks in the album.
📦
Playlist
3 fields
private final String idprivate String nameprivate final List<Track> tracks
Manages an ordered list of tracks that users can modify concurrently.
public synchronized void addTrack(Track track)Adds a track to the playlist ensuring thread-safe modification.
public synchronized boolean removeTrack(String trackId)Removes a track by ID from the playlist in a thread-safe manner.
public synchronized List<Track> getTracks()Returns a snapshot copy of the current track list.
📦
PlaybackQueue
2 fields
private final Queue<Track> queueprivate volatile PlaybackStatus status
Manages the playback order of tracks with concurrency control for multiple threads.
public synchronized void enqueueTrack(Track track)Adds a track to the playback queue atomically.
public synchronized Track dequeueTrack()Removes and returns the next track to play atomically.
public PlaybackStatus getStatus()Returns the current playback status with volatile visibility guarantees.
public synchronized void setStatus(PlaybackStatus status)Sets the playback status atomically.
📦
User
2 fields
private final String userIdprivate final Map<String, Playlist> playlists
Represents a user owning multiple playlists with thread-safe playlist management.
public synchronized Playlist createPlaylist(String name)Creates and adds a new playlist for the user.
public synchronized boolean deletePlaylist(String playlistId)Deletes a playlist by ID in a thread-safe manner.
public synchronized Playlist getPlaylist(String playlistId)Retrieves a playlist by ID.
🔗
Relationships
Album◆──►Track[1..*]
compositionAlbum owns Tracks; Tracks cannot exist independently in this model
Playlist◇──►Track[0..*]
aggregationPlaylist references Tracks; Tracks exist independently
User◆──►Playlist[0..*]
compositionUser owns Playlists; Playlists lifecycle tied to User
PlaybackQueue◇──►Track[0..*]
aggregationPlaybackQueue references Tracks; Tracks exist independently
🗄️
Tracks
PK: id
id STRINGtitle STRINGartist STRINGdurationSeconds INTgenre STRING
Stores immutable metadata for each track.
🗄️
Playlists
PK: userId
userId STRINGplaylistId STRINGname STRINGtrackIds LIST<STRING>
Stores playlists per user with ordered track references.
🔌
POST /users/{userId}/playlists
Request
{
  "name": "string"
}
Response
{
  "playlistId": "string",
  "name": "string"
}
Creates a new playlist for the user.
🔌
POST /playlists/{playlistId}/tracks
Request
{
  "trackId": "string"
}
Response
{
  "success": "boolean"
}
Adds a track to the specified playlist.
🔌
GET /playbackqueue/next
Request
{}
Response
{
  "track": "Track object"
}
Fetches the next track to play atomically.

Practice

(1/5)
1. In designing the Airline Reservation System, should the Seat entity be modeled as an abstract class or an interface, and why?
easy
A. Abstract class, because seats share common state like seat number and class, and behavior like availability checks.
B. Interface, because it allows multiple inheritance and flexible seat type combinations.
C. Interface, because seats only define behavior without any shared state or implementation.
D. Abstract class, because interfaces cannot enforce method signatures for seat operations.

Solution

  1. Step 1: Analyze seat characteristics

    Seats have shared state (seat number, class) and common behavior (availability, booking), which suggests code reuse.
  2. Step 2: Abstract class vs interface

    Abstract class allows shared state and default behavior implementation, while interface cannot hold state and only defines method signatures.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Shared state + behavior -> abstract class [check]
Hint: Shared state needs abstract class, interfaces lack fields [check]
Common Mistakes:
2. In designing a Vehicle abstraction for a parking system, should Vehicle be an abstract class or an interface? Consider that vehicles have state like license plate and behavior like park().
easy
A. Abstract class, because it can hold shared state and provide default behavior implementations.
B. Interface, because state can be stored externally and behavior is all that matters.
C. Interface, because it allows multiple inheritance and defines behavior without state.
D. Abstract class, because it forces all subclasses to implement all methods explicitly.

Solution

  1. Step 1: Analyze state and behavior requirements

    Vehicles have shared state (e.g., license plate) and common behavior (e.g., park). Abstract classes can hold state and provide default method implementations.
  2. Step 2: Eliminate interface options

    Interfaces cannot hold state (prior to Java 8 default methods) and rely on external storage for state, which complicates design. Abstract class better encapsulates state and behavior.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Shared state + default behavior -> abstract class [check]
Hint: Stateful base class -> abstract class, not interface [check]
Common Mistakes:
3. The Meeting Scheduler currently calculates fees using a FeeCalculator class. To add a new WeekendFeeStrategy without modifying existing code, which SOLID principle applies and what is the minimum change required?
medium
A. Dependency Inversion Principle: inject WeekendFeeStrategy into FeeCalculator by modifying its constructor.
B. Open/Closed Principle: create a new WeekendFeeStrategy class implementing FeeStrategy interface; FeeCalculator uses it polymorphically.
C. Single Responsibility Principle: modify FeeCalculator to handle weekend fees directly.
D. Liskov Substitution Principle: subclass FeeCalculator and override fee calculation for weekends.

Solution

  1. Step 1: Identify SOLID principle for extensibility

    Open/Closed Principle (OCP) requires adding new behavior without modifying existing classes.
  2. Step 2: Apply Dependency Inversion Principle (DIP)

    Injecting the new WeekendFeeStrategy via constructor or setter allows FeeCalculator to depend on abstractions, enabling extension without modification.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Inject new strategy -> respects OCP and DIP [check]
Hint: Inject new strategy -> OCP + DIP respected [check]
Common Mistakes:
4. Consider a tryConsume() method in TokenBucketRateLimiter that checks if tokens are available and then decrements the count without synchronization. Which exact thread interleaving causes double token consumption?
medium
A. Thread 1 reads tokens=1, decrements; Thread 2 waits until Thread 1 finishes
B. Thread 1 increments tokens while Thread 2 decrements tokens simultaneously
C. Thread 1 and Thread 2 both block on synchronized method, then proceed sequentially
D. Thread 1 checks tokens > 0 -> true, context switch; Thread 2 checks tokens > 0 -> true; both decrement tokens

Solution

  1. Step 1: Identify check-then-act race condition

    Without synchronization, both threads can read tokens > 0 before either decrements.
  2. Step 2: Trace exact interleaving causing double consumption

    Thread 1 checks tokens > 0 -> true, context switch; Thread 2 checks tokens > 0 -> true; both proceed to decrement, consuming same token twice.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Unsynchronized check-then-act allows double token consumption [check]
Hint: Check tokens > 0 twice before decrement -> double consume [check]
Common Mistakes:
5. You need to add support for ElectricCar and ChargingSpot in the parking system without modifying existing classes. What is the minimum change to the current design?
hard
A. Modify Vehicle and Spot classes to include electric-specific fields and logic.
B. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update compatibility map externally.
C. Create a new ElectricVehicle interface and make ElectricCar implement it, modifying existing classes to check for this interface.
D. Add a boolean isElectric flag to Vehicle and Spot classes and handle logic with conditionals.

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Enums can be extended with new constants, and new subclasses can be added without changing existing code.
  2. Step 2: Use external compatibility map update

    Updating the compatibility map externally avoids modifying existing classes, preserving OCP and avoiding class explosion.
  3. Step 3: Avoid flags or interface checks that require existing class changes

    Flags or interface checks require modifying existing classes, violating OCP.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    Extend enums + new subclass + external map update -> minimal change [check]
Hint: Extend enums + subclass + external map -> minimal change [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard