Back to Blog
Node.jsTimersAsyncEvent Loop

Node.js Timers Guide

Master Node.js timers including setTimeout, setInterval, setImmediate, and process.nextTick.

B
Bootspring Team
Engineering
April 7, 2020
6 min read

Node.js provides several timer functions for scheduling code execution. Here's how they work.

setTimeout#

1// Basic timeout 2const timeoutId = setTimeout(() => { 3 console.log('Executed after 1 second'); 4}, 1000); 5 6// With arguments 7setTimeout((name, greeting) => { 8 console.log(`${greeting}, ${name}!`); 9}, 1000, 'Alice', 'Hello'); 10 11// Clear timeout 12clearTimeout(timeoutId); 13 14// Zero delay (not immediate!) 15setTimeout(() => { 16 console.log('After I/O'); 17}, 0); 18 19console.log('Before timeout'); 20// Output: 21// Before timeout 22// After I/O

setInterval#

1// Repeated execution 2let count = 0; 3const intervalId = setInterval(() => { 4 count++; 5 console.log(`Count: ${count}`); 6 7 if (count >= 5) { 8 clearInterval(intervalId); 9 console.log('Interval cleared'); 10 } 11}, 1000); 12 13// Polling pattern 14function pollStatus() { 15 const intervalId = setInterval(async () => { 16 const status = await checkStatus(); 17 18 if (status === 'complete') { 19 clearInterval(intervalId); 20 console.log('Task complete!'); 21 } 22 }, 2000); 23 24 return intervalId; 25} 26 27// Clear all intervals on shutdown 28const intervals = []; 29intervals.push(setInterval(() => {}, 1000)); 30intervals.push(setInterval(() => {}, 2000)); 31 32process.on('SIGINT', () => { 33 intervals.forEach(clearInterval); 34 process.exit(0); 35});

setImmediate#

1// Execute after I/O events 2setImmediate(() => { 3 console.log('Immediate callback'); 4}); 5 6console.log('Synchronous'); 7 8// Output: 9// Synchronous 10// Immediate callback 11 12// Compare with setTimeout(0) 13setTimeout(() => { 14 console.log('setTimeout'); 15}, 0); 16 17setImmediate(() => { 18 console.log('setImmediate'); 19}); 20 21// Order varies! But inside I/O callback: 22const fs = require('fs'); 23 24fs.readFile(__filename, () => { 25 setTimeout(() => { 26 console.log('timeout'); 27 }, 0); 28 29 setImmediate(() => { 30 console.log('immediate'); 31 }); 32}); 33// Inside I/O: immediate always before timeout 34 35// Clear immediate 36const immediateId = setImmediate(() => {}); 37clearImmediate(immediateId);

process.nextTick#

1// Execute before any I/O 2process.nextTick(() => { 3 console.log('nextTick callback'); 4}); 5 6console.log('Synchronous'); 7 8// Output: 9// Synchronous 10// nextTick callback 11 12// Priority order 13setTimeout(() => console.log('setTimeout'), 0); 14setImmediate(() => console.log('setImmediate')); 15process.nextTick(() => console.log('nextTick')); 16console.log('sync'); 17 18// Output: 19// sync 20// nextTick 21// setTimeout (or setImmediate) 22// setImmediate (or setTimeout) 23 24// Recursive nextTick can starve I/O! 25// Avoid this: 26function recursiveNextTick() { 27 process.nextTick(recursiveNextTick); 28} 29// This blocks the event loop! 30 31// Better: use setImmediate for recursion 32function recursiveImmediate() { 33 setImmediate(recursiveImmediate); 34}

Timer Promises (Node.js 16+)#

1const { setTimeout, setInterval, setImmediate } = require('timers/promises'); 2 3// Async setTimeout 4async function delay() { 5 await setTimeout(1000); 6 console.log('1 second passed'); 7} 8 9// With value 10async function delayedValue() { 11 const result = await setTimeout(1000, 'delayed result'); 12 console.log(result); // 'delayed result' 13} 14 15// Async setImmediate 16async function immediate() { 17 await setImmediate(); 18 console.log('After I/O events'); 19} 20 21// Async setInterval (returns async iterator) 22async function poll() { 23 let count = 0; 24 25 for await (const _ of setInterval(1000)) { 26 count++; 27 console.log(`Tick ${count}`); 28 29 if (count >= 5) { 30 break; 31 } 32 } 33} 34 35// With AbortController 36async function cancellableDelay() { 37 const controller = new AbortController(); 38 39 setTimeout(() => controller.abort(), 500); 40 41 try { 42 await setTimeout(1000, 'result', { signal: controller.signal }); 43 } catch (err) { 44 if (err.name === 'AbortError') { 45 console.log('Timeout was aborted'); 46 } 47 } 48}

Event Loop Phases#

1/* 2Event Loop Phases: 31. timers: setTimeout, setInterval callbacks 42. pending callbacks: I/O callbacks deferred to next loop 53. idle, prepare: internal use 64. poll: retrieve new I/O events 75. check: setImmediate callbacks 86. close callbacks: socket.on('close') 9 10process.nextTick runs between phases! 11*/ 12 13// Demonstrating phases 14const fs = require('fs'); 15 16fs.readFile(__filename, () => { 17 // We're in the poll phase 18 19 // Runs in check phase (after poll) 20 setImmediate(() => console.log('1. setImmediate')); 21 22 // Runs in next iteration's timers phase 23 setTimeout(() => console.log('2. setTimeout'), 0); 24 25 // Runs immediately after current operation 26 process.nextTick(() => console.log('0. nextTick')); 27}); 28 29// Output: 30// 0. nextTick 31// 1. setImmediate 32// 2. setTimeout

Patterns#

1// Debounce 2function debounce(fn, delay) { 3 let timeoutId; 4 5 return function (...args) { 6 clearTimeout(timeoutId); 7 timeoutId = setTimeout(() => fn.apply(this, args), delay); 8 }; 9} 10 11const debouncedSave = debounce(save, 300); 12 13// Throttle 14function throttle(fn, interval) { 15 let lastTime = 0; 16 17 return function (...args) { 18 const now = Date.now(); 19 20 if (now - lastTime >= interval) { 21 lastTime = now; 22 fn.apply(this, args); 23 } 24 }; 25} 26 27const throttledUpdate = throttle(update, 100); 28 29// Retry with exponential backoff 30async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) { 31 for (let attempt = 0; attempt <= maxRetries; attempt++) { 32 try { 33 return await fn(); 34 } catch (error) { 35 if (attempt === maxRetries) { 36 throw error; 37 } 38 39 const delay = baseDelay * Math.pow(2, attempt); 40 console.log(`Retry in ${delay}ms...`); 41 await new Promise(resolve => setTimeout(resolve, delay)); 42 } 43 } 44} 45 46// Timeout wrapper 47function withTimeout(promise, ms) { 48 const timeout = new Promise((_, reject) => { 49 setTimeout(() => reject(new Error('Timeout')), ms); 50 }); 51 52 return Promise.race([promise, timeout]); 53} 54 55// Usage 56const result = await withTimeout(fetchData(), 5000);

Timer References#

1// Timers keep process alive by default 2const timer = setTimeout(() => {}, 100000); 3// Process won't exit until timer fires or is cleared 4 5// unref() allows process to exit 6timer.unref(); 7// Process can exit even with timer pending 8 9// ref() restores default behavior 10timer.ref(); 11// Process waits for timer again 12 13// Common pattern: keep-alive with unref 14function keepAlive() { 15 const interval = setInterval(() => { 16 sendHeartbeat(); 17 }, 30000); 18 19 // Don't prevent shutdown 20 interval.unref(); 21 22 return interval; 23} 24 25// Check if timer is active 26const { hasRef } = timer; 27console.log(hasRef()); // true or false

Scheduling Patterns#

1// Run once after event loop clears 2setImmediate(() => { 3 console.log('All sync code done'); 4}); 5 6// Ensure async behavior 7function ensureAsync(callback) { 8 process.nextTick(callback); 9} 10 11// API that's always async 12function getData(callback) { 13 if (cache) { 14 // Don't call synchronously! 15 process.nextTick(() => callback(null, cache)); 16 return; 17 } 18 19 fetchFromDB((err, data) => { 20 cache = data; 21 callback(err, data); 22 }); 23} 24 25// Batch processing with setImmediate 26async function processBatch(items) { 27 const results = []; 28 29 for (let i = 0; i < items.length; i++) { 30 results.push(processItem(items[i])); 31 32 // Yield to event loop every 100 items 33 if (i % 100 === 0) { 34 await new Promise(setImmediate); 35 } 36 } 37 38 return results; 39}

Common Mistakes#

1// Wrong: Relying on timer precision 2// Timers are not precise, minimum delay varies 3 4// Wrong: setTimeout(0) vs setImmediate 5// Order is not guaranteed outside I/O callbacks 6 7// Wrong: Recursive nextTick 8// Can starve I/O, use setImmediate instead 9 10// Wrong: Not clearing timers 11// Memory leak if component unmounts/restarts 12 13// Wrong: Using this in timer callback 14const obj = { 15 value: 42, 16 wrong: function() { 17 setTimeout(function() { 18 console.log(this.value); // undefined! 19 }, 100); 20 }, 21 right: function() { 22 setTimeout(() => { 23 console.log(this.value); // 42 24 }, 100); 25 } 26};

Best Practices#

Choosing Timers: ✓ setTimeout for delayed execution ✓ setInterval for repeated tasks ✓ setImmediate after I/O ✓ process.nextTick for immediate async Memory Management: ✓ Always clear timers when done ✓ Use unref() for non-critical timers ✓ Store timer IDs for cleanup ✓ Clear on process exit Patterns: ✓ Use timer promises in Node 16+ ✓ Implement debounce/throttle ✓ Add timeout to async operations ✓ Yield with setImmediate in loops Avoid: ✗ Recursive process.nextTick ✗ Relying on timer order ✗ Forgetting to clear intervals ✗ Assuming timer precision

Conclusion#

Node.js timers provide flexible scheduling options. Use setTimeout for delays, setInterval for repetition, setImmediate for post-I/O execution, and process.nextTick for immediate callbacks. The timer promises API in Node.js 16+ makes async timer code cleaner. Always clean up timers to prevent memory leaks.

Share this article

Help spread the word about Bootspring