It's a bit of both:
If execSync takes 2000ms, it means that no other code will run during those 2 seconds. After all, JavaScript is single-threaded, and you're blocking that thread.
After execSync finished (and the function call it's in finishes), the event loop just continues. Since 2s have already passed since the last time the interval happened, the next call will happen nearly instantly.
You can quickly validate this yourself (e.g. in the REPL):
execSync = require('child_process').execSync;
setInterval(() => { console.log('a', Date.now()); execSync('sleep 2'); console.log('b', Date.now()); }, 2000);
Also interesting to know: if you sleep for 5s instead, the result will be the same (if you ignore the freezes being longer). As in, after execSync finishes, it won't suddenly trigger two tasks at once. Validation:
execSync = require('child_process').execSync;
i = 0;
setInterval(() => { console.log('a', Date.now()); if (i++ > 5) return; execSync('sleep 5'); console.log('b', Date.now()); }, 2000)