Bird
Raised Fist0

Job Scheduler

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Design a Job Scheduler with Cron, Priority Queue, and Retry Policy
40 min ยท medium
medium

You need to design a Job Scheduler system that supports scheduling jobs based on cron expressions, executes jobs according to priority, and supports configurable retry policies for failed jobs.

  • Jobs are scheduled using Cron expressions to specify execution times.
  • Jobs have priorities; higher priority jobs should be executed before lower priority ones.
  • Retry policies are pluggable strategies that define how and when to retry failed jobs.
  • The JobScheduler must be a singleton to coordinate job execution globally.
  • Job execution must be thread-safe and support concurrent scheduling and execution.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the Job abstraction with execution skeleton
Design a Job class that defines the execution skeleton with hooks for subclasses to implement specific job logic. Consider how to enforce a template method pattern in Java.
๐Ÿ’ก Hint
Use an abstract class with a final execute() method that calls abstract doExecute() to enforce execution steps.
abstract class Job with final execute() method and abstract doExecute() - abstract class needed because it holds instance state and provides partial implementation; interface cannot hold state or method bodies prior to Java 8 default methods.
Mid: Recognizes Template Method pattern. Senior: Explains why abstract class is needed to hold instance state and enforce execution skeleton with final method preventing override.
โš ๏ธCommon Wrong Answer
Using interface for Job with execute() default method leads to inability to hold instance state like jobId or status, violating encapsulation and making retry logic harder.
Step 2
Implement CronExpression parsing and validation
Design a CronExpression class that can parse and validate cron strings and determine next execution times.
๐Ÿ’ก Hint
Use a final immutable class with private final fields representing cron parts; provide a method nextExecutionTime(Date from) returning next valid time.
final class CronExpression with immutable fields and thread-safe methods - immutability ensures thread safety without synchronization; parsing done once at construction.
Mid: Uses immutable class for thread safety. Senior: Explains how immutability avoids synchronization overhead and prevents visibility issues in concurrent access.
โš ๏ธCommon Wrong Answer
Mutable CronExpression with setters causes race conditions when accessed concurrently, leading to inconsistent scheduling behavior.
Step 3
Design RetryPolicy as a pluggable strategy
Create a RetryPolicy interface to allow different retry algorithms (e.g., fixed delay, exponential backoff).
๐Ÿ’ก Hint
Use interface RetryPolicy with method boolean shouldRetry(int attempt, Exception e) and long getRetryDelay(int attempt).
interface RetryPolicy defines retry contract without state; implementations hold state if needed. Strategy pattern realized via interface for pluggability.
Mid: Identifies Strategy pattern. Senior: Explains interface use for pluggability and separation of concerns, enabling open-closed principle.
โš ๏ธCommon Wrong Answer
Hardcoding retry logic inside Job class violates single responsibility and makes retry behavior inflexible and hard to extend.
Step 4
Implement JobScheduler as a thread-safe singleton with priority queue
Design JobScheduler to manage job submissions and executions using a priority queue, ensuring thread-safe singleton access.
๐Ÿ’ก Hint
Use volatile static instance with double-checked locking for singleton; use PriorityBlockingQueue<Job> for thread-safe priority ordering.
JobScheduler singleton with volatile instance and double-checked locking ensures safe lazy initialization; PriorityBlockingQueue provides thread-safe priority ordering and blocking retrieval.
Mid: Names Singleton and Priority Queue patterns. Senior: Explains volatile prevents instruction reordering, double-checked locking avoids synchronization overhead, and PriorityBlockingQueue ensures atomic priority-based retrieval.
โš ๏ธCommon Wrong Answer
Using non-volatile instance with double-checked locking leads to partially constructed singleton visible to other threads, causing subtle bugs.
Step 5
Ensure thread-safe job execution and retry handling
Design how JobScheduler executes jobs concurrently and applies retry policies safely.
๐Ÿ’ก Hint
Use synchronized blocks or atomic variables to track job execution state; use ExecutorService for concurrency; retry attempts tracked per job instance.
Use ExecutorService for concurrent execution; synchronized or AtomicInteger for retry attempt count; volatile boolean flags for job status visibility across threads.
Mid: Uses ExecutorService for concurrency. Senior: Details how volatile ensures visibility of job status changes, AtomicInteger prevents race conditions on retry counts, and synchronized blocks protect critical sections.
โš ๏ธCommon Wrong Answer
Not synchronizing retry attempt increments causes lost updates, leading to infinite retries or premature job failure.
๐Ÿ”Œ
API Design Challenge
Design the public API for submitting jobs with cron expressions, specifying priority, and attaching retry policies. Consider how clients will interact with the JobScheduler to add, cancel, and query jobs.
  • Provide a submitJob(Job job) method that accepts a Job instance encapsulating cron, priority, and retry policy.
  • Include methods like cancelJob(jobId) and getJobStatus(jobId) for lifecycle management.
  • Design Job and RetryPolicy interfaces/classes to be extensible for future enhancements.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract class Job with final execute() method and instance state
  • final immutable CronExpression class for thread-safe scheduling
  • RetryPolicy interface for pluggable retry strategies
  • JobScheduler singleton with volatile instance and double-checked locking
  • PriorityBlockingQueue for thread-safe priority job ordering
Good to Have - 30%
  • ExecutorService for concurrent job execution
  • volatile and AtomicInteger for safe retry attempt tracking
  • SOLID principles explicitly applied and explained
  • Clear lifecycle ownership between JobScheduler, Job, and RetryPolicy
  • Separation of concerns between scheduling, execution, and retry logic
Bonus - 10%
  • Support for dynamic addition/removal of RetryPolicy implementations
  • Extensible CronExpression supporting custom cron
โœ…
Model Answer
โ–ผ Reveal

    • abstract class Job with final execute() method implementing Template Method pattern; holds jobId, priority, and retryPolicy state.
    • final immutable CronExpression class parsing cron strings once; thread-safe without synchronization due to immutability.
    • RetryPolicy interface defining pluggable retry strategies; implementations encapsulate retry logic separately from Job.
    • JobScheduler singleton with volatile static instance and double-checked locking to ensure safe lazy initialization.
    • PriorityBlockingQueue<Job> used inside JobScheduler for thread-safe priority-based job ordering and retrieval.
    • ExecutorService manages concurrent job execution; synchronized or AtomicInteger used to track retry attempts safely.
    • volatile boolean flags in Job to ensure visibility of execution status changes across threads.
    • Separation of concerns enforced by SOLID principles: Single Responsibility (Job vs RetryPolicy), Open-Closed (pluggable RetryPolicy), and Dependency Inversion (Job depends on RetryPolicy interface).
    • Composition relationship: JobScheduler composes Job instances; Job aggregates RetryPolicy implementations (can exist independently).
    • Lifecycle ownership: JobScheduler owns Job lifecycle; RetryPolicy implementations can be shared across Jobs (aggregation).

    Practice

    (1/5)
    1. In designing a TokenBucketRateLimiter class, should the RateLimiter be an abstract class or an interface? Consider that the rate limiter maintains internal token state and exposes tryConsume() behavior.
    easy
    A. Abstract class, because it can encapsulate shared token state and partial implementation
    B. Interface, because it only defines behavior and state is managed externally
    C. Interface, because Java interfaces can have private fields for token state
    D. Abstract class, because interfaces cannot define any methods

    Solution

    1. Step 1: Analyze state and behavior requirements

      RateLimiter needs to maintain internal token count state and provide tryConsume() behavior.
    2. Step 2: Abstract class vs interface for stateful behavior

      Abstract class can hold shared mutable state and provide partial implementation; interfaces cannot hold instance state.
    3. Final Answer:

      Option A -> Option A
    4. Quick Check:

      Abstract class supports shared token state and behavior [check]
    Hint: Stateful base class -> abstract class, not interface [check]
    Common Mistakes:
    2. In a vending machine design, a State interface defines methods like selectProduct(), insertMoney(), and dispense(). Concrete classes like IdleState, HasMoneyState, and SoldState implement this interface. Which design pattern is applied here and why is it the correct choice?
    easy
    A. Observer pattern, because states notify the vending machine when transitions occur.
    B. Strategy pattern, because it allows changing algorithms at runtime for product selection.
    C. Decorator pattern, because it adds responsibilities to states dynamically without altering their interface.
    D. State pattern, because it encapsulates state-specific behavior and transitions within state classes.

    Solution

    1. Step 1: Identify the role of State interface and concrete states

      The State interface defines behavior that varies depending on the vending machine's current state. Concrete classes implement these behaviors and handle transitions.
    2. Step 2: Match pattern intent to scenario

      The State pattern encapsulates state-specific behavior and transitions, allowing the vending machine to change behavior dynamically without complex conditionals.
    3. Final Answer:

      Option D -> Option D
    4. Quick Check:

      State pattern encapsulates state behavior and transitions [check]
    Hint: State pattern encapsulates behavior per state [check]
    Common Mistakes:
    3. You want to add a WeekendFeeStrategy to calculate parking fees differently on weekends. Given the current design uses a FeeStrategy interface, what is the minimum change to comply with SOLID principles?
    medium
    A. Modify the existing FeeStrategy implementation to add weekend logic with an if-else block.
    B. Merge all fee strategies into a single class with multiple methods for each pricing rule.
    C. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it where needed without modifying existing classes.
    D. Add a boolean flag isWeekend to the ParkingLot class and change fee calculation accordingly.

    Solution

    1. Step 1: Identify SOLID principle

      Open-Closed Principle (OCP) requires classes to be open for extension but closed for modification.
    2. Step 2: Apply OCP

      Creating a new WeekendFeeStrategy class extends functionality without modifying existing code, adhering to OCP and Dependency Inversion Principle.
    3. Final Answer:

      Option C -> Option C
    4. Quick Check:

      New strategy class -> extend without modifying existing -> OCP [check]
    Hint: Add new strategy class, don't modify existing code [check]
    Common Mistakes:
    4. In the ParkingLot class, if parkVehicle() is not synchronized, which exact sequence of Thread 1 and Thread 2 causes double-booking of the same spot?
    medium
    A. Thread 1 checks spot availability -> true, context switch; Thread 2 checks same spot -> true; both assign spot simultaneously.
    B. Thread 1 releases spot, Thread 2 immediately parks vehicle on it.
    C. Thread 1 parks vehicle, Thread 2 parks vehicle after Thread 1 finishes.
    D. Thread 1 and Thread 2 both wait on a lock, then park sequentially.

    Solution

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

      Without synchronization, Thread 1 reads spot as available but before marking it occupied, Thread 2 also reads it as available.
    2. Step 2: Both threads proceed to assign the same spot concurrently

      Because no lock protects the check and assignment, both parkVehicle calls succeed, causing double-booking.
    3. Final Answer:

      Option A -> Option A
    4. Quick Check:

      Unsynchronized check-then-act -> double-booking race [check]
    Hint: Check-then-act without lock -> double-booking race [check]
    Common Mistakes:
    5. You want to add a WeekendFeeStrategy to calculate weekend parking fees without modifying existing classes. Which SOLID principle guides the minimum change, and what is the correct approach?
    medium
    A. Modify the ParkingLot class to add weekend fee logic, enforcing Single Responsibility Principle.
    B. Add weekend fee logic inside existing FeeStrategy implementations, following Dependency Inversion Principle.
    C. Create a WeekendFeeStrategy subclass of ParkingLot to override fee calculation, following Liskov Substitution Principle.
    D. Create a new WeekendFeeStrategy class implementing FeeStrategy interface, and inject it where needed, following Open/Closed Principle.

    Solution

    1. Step 1: Identify the SOLID principle for extensibility

      Open/Closed Principle (OCP) requires adding new functionality without modifying existing code.
    2. Step 2: Evaluate approaches

      Creating a new FeeStrategy implementation and injecting it respects OCP and DIP; modifying ParkingLot or existing strategies violates OCP or mixes responsibilities.
    3. Final Answer:

      Option B -> Option B
    4. Quick Check:

      New FeeStrategy class + injection -> OCP and DIP respected [check]
    Hint: New strategy class + injection -> Open/Closed Principle [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