Typical setTimeout Error in TypeScript: window vs Node.js

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

"The Wrong setTimeout": How TypeScript Drives Developers Crazy

\n

Have you ever written a simple timer in TypeScript, and the code editor suddenly started underlining everything in red? A familiar scenario: you import setTimeout, write const timer = setTimeout(...), and TypeScript throws an error or suggests an unclear type. What's the matter? It turns out that setTimeout has two versions — one for the browser (window), and another for the server (Node.js). And TypeScript, being a perfectionist, forces us to choose.

\n

This topic is one of the most popular on Stack Overflow. Thousands of developers stumble over the same rake. But we won't just copy an answer. We'll get to the root of the problem, understand how typing works, and learn to write code that won't break in either the browser or Node.js.

\n\n

Why Two Versions of setTimeout?

\n

Historically, JavaScript lives in two worlds: client-side (browser) and server-side (Node.js). In the browser, setTimeout is a method of the global window object. In Node.js, it's a function from the global timers module. They do the same thing, but their type signatures differ.

\n

Here are the key differences:

\n
    \n
  • Return value: In the browser, window.setTimeout returns a number (timer identifier). In Node.js, it returns a NodeJS.Timeout object.
  • \n
  • Context: In the browser, this inside the callback is window (in non-strict mode). In Node.js, it's the timer object.
  • \n
  • Typing: TypeScript provides different declarations for different environments. If you write universal code (an isomorphic application), a conflict arises.
  • \n
\n\n

What the Problem Looks Like in Code

\n

Imagine you are developing a library that should work both on the client and the server. You write code like this:

\n
// utils.ts\nexport function delay(ms: number): Promise<void> {\n    return new Promise(resolve => {\n        const timer = setTimeout(resolve, ms);\n        // What type is timer? number or NodeJS.Timeout?\n    });\n}
\n

TypeScript will see that setTimeout can be of two kinds and will throw an error: "Type 'Timeout' is not assignable to type 'number'." Or vice versa, depending on the tsconfig.json settings and the included types.

\n

To make it clearer, here is what the types look like when viewed through ReturnType:

\n
// In the browser (lib.dom.d.ts)\ntype BrowserTimer = ReturnType<typeof window.setTimeout>; // number\n\n// In Node.js (@types/node)\ntype NodeTimer = ReturnType<typeof setTimeout>; // NodeJS.Timeout
\n\n

Correct Solutions for Different Scenarios

\n

Let's look at four approaches. Choose the one that fits your project.

\n\n

1. If you are writing only for the browser

\n

The simplest case. Just specify the type number and use window.setTimeout explicitly:

\n
// browser-timer.ts\nconst timerId: number = window.setTimeout(() => {\n    console.log('One second has passed');\n}, 1000);\n\n// For clearing\nwindow.clearTimeout(timerId);
\n

Why does this work? In a browser environment, window.setTimeout always returns a number. TypeScript knows this if you have lib: ["dom"] included.

\n\n

2. If you are writing only for Node.js

\n

Here, the type is NodeJS.Timeout. Use it directly:

\n
// node-timer.ts\nimport { setTimeout } from 'timers'; // explicit import for clarity\n\nconst timer: NodeJS.Timeout = setTimeout(() => {\n    console.log('Timer fired');\n}, 2000);\n\nclearTimeout(timer); // clearTimeout accepts NodeJS.Timeout
\n

Important: In Node.js, clearTimeout also accepts No

Blogs

Book Recommendations