Cron vs setInterval in Node.js: Which One?
When you need to run code on a schedule in Node.js you have two main options: JavaScript's built-in setInterval or a cron-based library like node-cron. Both work, but they solve different problems. Picking the wrong one leads to subtle bugs around drift, missed runs, and time zone handling. If you're new to cron syntax itself, our cron expression complete guide covers the five fields this whole comparison assumes you know.
setInterval — the quick option
setInterval runs a function every N milliseconds from the moment it is called. It does not know about wall-clock time.
// Run every 5 minutes
setInterval(() => {
console.log('tick:', new Date().toISOString());
doWork();
}, 5 * 60 * 1000);Pros: zero dependencies, simple, good for fixed intervals.
Cons: drifts over time (each callback adds a tiny delay), restarts reset the clock, can't express "every Monday at 9 AM", no timezone awareness.
node-cron — wall-clock scheduling
node-cron fires tasks at exact calendar times using cron expressions.
import cron from 'node-cron';
// Every weekday at 9:00 AM in Asia/Kolkata timezone
cron.schedule('0 9 * * 1-5', () => {
console.log('Morning job running:', new Date().toISOString());
doWork();
}, {
timezone: 'Asia/Kolkata'
});Pros: expressive schedules, timezone support, survives daylight saving changes, aligns to wall-clock minutes.
Cons: adds a dependency, slightly more setup.
The timezone: 'Asia/Kolkata' option above is doing real work — it is what lets 0 9 * * 1-5 mean 9 AM IST specifically, regardless of what timezone the host server is set to. See how cron handles timezones for how that plays out across servers, containers, and cloud schedulers.
The cron package (alternative)
The cron npm package offers a similar API with CronJob objects that can be started and stopped programmatically:
import { CronJob } from 'cron';
const job = new CronJob(
'0 0 * * 0', // every Sunday at midnight
() => { weeklyReport(); },
null, // onComplete
true, // start immediately
'Asia/Kolkata'
);
// Stop after first run
job.stop();Decision guide
- Use setInterval for simple, interval-based tasks where exact wall-clock time does not matter (e.g., polling an API every 30 seconds).
- Use node-cron or cron when the task must run at a specific time of day, day of week, or on a calendar-based schedule. See our cron expression examples for every 5, 10, 15, and 30 minutes for ready-to-use interval patterns, or build one with the free Cron Expression Generator.
- Use an external scheduler (GitHub Actions, AWS EventBridge, Render cron jobs) when your Node.js server may restart or scale to multiple instances — you don't want duplicate job runs.
- Building the equivalent in Python instead of Node? See cron jobs with Python's schedule library and APScheduler for the same setInterval-vs-cron trade-off in that ecosystem.
Handling overlapping runs
If your task takes longer than the schedule interval, you can end up with overlapping executions. Use a simple flag to prevent this:
let running = false;
cron.schedule('*/5 * * * *', async () => {
if (running) return; // skip if previous run hasn't finished
running = true;
try {
await doWork();
} finally {
running = false;
}
});The */5 * * * * expression above means every 5 minutes — see our cron expression examples for every 5, 10, 15, and 30 minutes if you need a different interval.
Common Mistakes
- Assuming setInterval fires at exactly N milliseconds. Node only guarantees the callback runs no earlier than N ms — if the event loop is busy, the tick is delayed, and that delay is never made up.
- Believing setInterval self-corrects for drift. It does not. Each tick's delay compounds on top of the last, so a "every 5 minutes" timer can be noticeably off from wall-clock time after running for a day.
- Forgetting a process crash or exit silently kills every setInterval. There's no OS-level guarantee like system cron has — if the Node process dies, the "schedule" is just gone until something restarts it.
- Running the same node-cron or setInterval schedule on multiple scaled instances. Each instance runs the job independently and on its own clock, causing duplicate executions unless you add an external lock or move the schedule to a single dedicated worker.
- Not clearing intervals on graceful shutdown. A stray
setIntervalstill ticking during shutdown can fire against a half-closed database connection or a server that's already stopped accepting requests. - Expecting setInterval to understand calendar concepts. It only knows elapsed milliseconds — there's no way to express "every Monday at 9 AM" without cron syntax or manual date math.
Putting It Together: A Self-Correcting Interval
If you genuinely can't use a cron library — say, a short-lived script — you can reduce drift by measuring against a fixed reference time instead of trusting the interval itself, and by cleaning up on shutdown so the timer doesn't outlive the process:
const INTERVAL_MS = 5 * 60 * 1000;
const start = Date.now();
let ticks = 0;
let timer;
function tick() {
doWork();
ticks += 1;
// Schedule the next tick against the original reference time,
// not "5 minutes from now" — this stops delays from compounding.
const nextRun = start + ticks * INTERVAL_MS;
const delay = Math.max(0, nextRun - Date.now());
timer = setTimeout(tick, delay);
}
timer = setTimeout(tick, INTERVAL_MS);
process.on('SIGTERM', () => {
clearTimeout(timer);
process.exit(0);
});This is still not a real substitute for wall-clock alignment — it drifts less than a naive setInterval, but it still resets to zero on every restart, unlike cron. Use it only when adding a dependency genuinely isn't an option.
Build your cron expression
Use the Dev Brains AI Cron Expression Generator to convert plain English like "every 5 minutes" or "every weekday at 9am" into the correct cron string for node-cron.
Frequently Asked Questions
Yes. setInterval does not guarantee the callback fires at exactly N milliseconds — if the event loop is busy, the tick is delayed, and Node does not "catch up" for lost time. Those small delays compound, so a job scheduled every 5 minutes can be several seconds or more off from wall-clock time after running for hours. node-cron and other cron-based schedulers avoid this because they check the actual clock instead of counting elapsed milliseconds.
No. Node.js has no built-in cron parser — setInterval and setTimeout only understand milliseconds, not calendar expressions like "every weekday at 9 AM." You need a package like node-cron or cron to parse a standard 5-field cron expression and schedule against wall-clock time.