Definition
Task Scheduler computes the minimum schedule length using a formula driven by the most frequent task. The busiest task forces a skeleton of slots separated by the cooldown, and the answer is the maximum of that skeleton length and the total number of tasks. A greedy heap simulation is the alternative that reproduces the same count.
Imagine a batch processor that must run jobs but cannot run the same job type twice within N minutes, needing an idle gap otherwise. The job type that appears most often sets the rhythm: you lay out those copies with mandatory gaps between them, fill the gaps with other job types, and if there are more jobs than gaps you simply pack them in. The schedule length is whichever is larger: the gapped skeleton or the raw job count.
Understanding the Concepts
What is the problem?
Task Scheduler — most-frequent-first with cooldown. Given a list of tasks represented by characters and a cooldown value N, where the same task must be separated by at least N intervals, return the minimum number of intervals needed to finish all tasks (idle intervals count).
How does the approach work?
Count the frequency of each task and find the maximum frequency, plus how many distinct tasks share that maximum. For example: int maxCount = ...; int maxFreqTasks = ...; from the frequency table.
The most frequent task must appear maxCount times, each separated by N cooldown slots, forming maxCount minus one full blocks of size N plus one, then a final group holding every task tied at the maximum. The skeleton length is therefore the product of maxCount minus one and N plus one, plus the number of tasks tied at the maximum. For example: int skeleton = (maxCount - 1) * (n + 1) + maxFreqTasks;.
The answer is the larger of this skeleton and the total number of tasks, because if there are so many distinct tasks that the gaps are all filled, no idle time is needed and the schedule is simply the task count. For example: return Math.max(skeleton, tasks.length);.
This is constant time after counting, since it depends only on frequencies, not on simulating each interval. A senior nuance is understanding why the maximum with total tasks is correct: the skeleton bounds the length from below when one task dominates, while a dense, varied workload fills every idle slot so the raw count dominates; a max-heap simulation that always runs the currently most frequent available task yields the identical number and is useful to explain the greedy intuition.
What is the banking use case?
The mini-project builds a BatchScheduler service implementing the formula, a BankLogFormatter, and a runnable demo over a day's job list.
How do you implement this in Java?
// ─── File: src/main/java/com/bank/sched/BatchScheduler.java ───
package com.bank.sched;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
// Computes minimum intervals to run all jobs with a cooldown between repeats.
public class BatchScheduler {
private static final Logger log = Logger.getLogger(BatchScheduler.class.getName());
public int leastIntervals(char[] jobs, int cooldown) {
Map<Character, Integer> freq = new HashMap<>();
for (char j : jobs) {
freq.merge(j, 1, Integer::sum);
}
int maxCount = 0;
for (int c : freq.values()) {
maxCount = Math.max(maxCount, c);
}
int tiedAtMax = 0;
for (int c : freq.values()) {
if (c == maxCount) tiedAtMax++;
}
// skeleton forced by the busiest job type
int skeleton = (maxCount - 1) * (cooldown + 1) + tiedAtMax;
int result = Math.max(skeleton, jobs.length);
log.info("Jobs=" + jobs.length + " cooldown=" + cooldown
+ " maxCount=" + maxCount + " tiedAtMax=" + tiedAtMax
+ " -> intervals=" + result);
return result;
}
}
// ─── File: src/main/java/com/bank/sched/BankLogFormatter.java ───
package com.bank.sched;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.time.Instant;
public class BankLogFormatter extends Formatter {
@Override
public String format(LogRecord record) {
return String.format("%s [%s] %s%n",
Instant.ofEpochMilli(record.getMillis()),
record.getLevel(),
record.getMessage());
}
}
// ─── File: src/main/java/com/bank/sched/SchedulerDemo.java ───
package com.bank.sched;
import java.util.logging.ConsoleHandler;
import java.util.logging.Logger;
public class SchedulerDemo {
private static final Logger log = Logger.getLogger(SchedulerDemo.class.getName());
static {
Logger root = Logger.getLogger("");
for (var h : root.getHandlers()) {
root.removeHandler(h);
}
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new BankLogFormatter());
root.addHandler(handler);
}
public static void main(String[] args) {
log.info("National Bank of India — Batch Job Scheduler");
// Job types: A=settlement, B=reconcile, C=report
char[] jobs = {'A', 'A', 'A', 'B', 'B', 'C'};
int cooldown = 2; // same job type must be 2 intervals apart
BatchScheduler scheduler = new BatchScheduler();
scheduler.leastIntervals(jobs, cooldown); // expect 8
}
}
What is the project structure?
batch-scheduler-service/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/com/bank/sched/ │ │ │ ├── SchedulerDemo.java (main runnable demo) │ │ │ ├── BatchScheduler.java (frequency-formula scheduler) │ │ │ └── BankLogFormatter.java (clean log formatter) │ │ └── resources/ │ │ └── application.yml │ └── test/ │ └── java/com/bank/sched/ │ └── BatchSchedulerTest.java
How do you build and run?
# Compile
javac -d target/classes src/main/java/com/bank/sched/*.java
# Run
java -cp target/classes com.bank.sched.SchedulerDemo
What is the step-by-step walkthrough?
The input jobs are A, A, A, B, B, C with cooldown two.
2026-10-26T21:50:00Z [INFO] National Bank of India — Batch Job Scheduler
2026-10-26T21:50:00Z [INFO] Jobs=6 cooldown=2 maxCount=3 tiedAtMax=1 -> intervals=7
What is the high-level design?
═══ HLD — Batch Job Scheduler ═══
┌────────────┐ job list ┌──────────────┐ count ┌────────────────────┐
│ Job Queue │─────────→│ Scheduler Svc│──────→│ frequency table │
└────────────┘ │ (Spring ×N) │ └────────────────────┘
└──────┬───────┘
│ formula: max(skeleton, total)
▼
┌──────────────────────┐
│ min intervals needed │ → capacity plan
└──────────────────────┘
What is the low-level design?
═══ LLD — Task Scheduler ═══ ┌─ Design Patterns Used ───────────────────────────────────┐ │ Frequency-driven closed-form formula (no simulation) │ │ Alternative: greedy max-heap running most-frequent first │ └──────────────────────────────────────────────────────────┘ ┌─ SOLID ──────────────────────────────────────────────────┐ │ S: scheduler only computes interval count │ │ O: cooldown is a parameter; logic unchanged │ └──────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────┐ │ BatchScheduler │ │ + leastIntervals(jobs, cd): int │ O(n) count, O(1) formula └──────────────────────────────────────┘ intervals = max( (maxCount-1)*(cd+1)+tiedAtMax , totalJobs )
What is the time and space complexity?
Analyze the solution based on the algorithm used. Consider both the time to process input and the space required for any data structures.
What are common mistakes?
Common mistakes include off-by-one errors, not handling edge cases like empty input, and not considering the constraints of the problem.
What are the edge cases?
Edge cases include empty input, single element input, all identical elements, and boundary values.
What should a senior engineer know?
A senior engineer should understand the time-space tradeoffs, be able to explain the algorithm clearly, and know when this technique applies versus alternatives.
Visual Overview
jobs A,A,A,B,B,C cooldown=2
freq: A:3 B:2 C:1 maxCount=3 tiedAtMax=1
skeleton driven by busiest (A):
[A _ _][A _ _][A] blocks of size (cooldown+1)=3
A B C A B . A fill gaps with B,C; one idle
skeleton = (3-1)*(2+1)+1 = 7
answer = max(skeleton=7, totalJobs=6) = 7
Key Takeaway
Task Scheduler asks for the minimum number of intervals, including idles, to run all tasks when identical tasks must be separated by a cooldown. The elegant solution is a closed-form formula driven by the most frequent task, avoiding any interval-by-interval simulation. I count frequencies, find the maximum count, and count how many tasks are tied at that maximum.
The busiest task must appear its full count of times, each separated by cooldown slots, which forms a skeleton of maxCount minus one blocks, each of size cooldown plus one, followed by a final group containing every task tied at the maximum. So the skeleton length is maxCount minus one, times cooldown plus one, plus the number tied at the maximum. The answer is the larger of this skeleton and the total number of tasks, because a dense, varied workload fills all the idle gaps and then the raw task count dominates, while a single dominating task leaves gaps and the skeleton dominates.
What interviewers check is that I can justify the maximum with total tasks rather than treating the skeleton as always correct, and that I understand the greedy intuition: a max-heap that always runs the currently most frequent available task produces the identical count, which is the simulation view of the same idea.
In my project at National Bank of India, a batch engine ran job types that could not repeat within a cooldown to avoid resource contention, and I needed the minimum number of intervals to plan capacity for the day. The frequency formula gave the schedule length instantly from the job counts, so capacity planning did not require stepping through every interval, and the most-frequent job correctly set the rhythm of the schedule.
Remember one sentence: understand the core algorithm and apply it to banking domain problems.
---