TypeScript

Fix tsc SyntaxError: Unexpected token '?' on old Node.js

Find which Node actually runs tsc, move it to a supported runtime or pin a compiler that matches it, and avoid the different error a plain npm install -g typescript now gives on Node 12 and 14.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

SyntaxError: Unexpected token '?' pointing at startIndex ?? 0 inside typescript/lib/tsc.js means your tsc is a TypeScript release that needs a newer Node.js than the one running it. TypeScript 5.1 started shipping ECMAScript 2020 syntax and declares node >=14.17; Node 12 and older cannot parse the ?? operator, so the compiler dies while loading, before it reads your .ts file.

The fix is to run tsc on a current Node LTS, then reinstall TypeScript under that Node. If you cannot leave Node 12 yet, pin typescript@4.9.5. Do not just run npm install -g typescript on the old Node: the current latest is 7.0.2, which needs Node 16.20.0 and fails on Node 12 and 14 with a different error.

The error, reproduced#

The question on Stack Overflow shows a Windows machine using Nodist, running tsc filename.ts. The same failure reproduces on Linux with the official node:12.17.0-alpine Docker image and a global typescript@5.1.6, on 13 October 2026:

text
/usr/local/lib/node_modules/typescript/lib/tsc.js:93
  for (let i = startIndex ?? 0; i < array.length; i++) {
                           ^
 
SyntaxError: Unexpected token '?'
    at wrapSafe (internal/modules/cjs/loader.js:1054:16)
    at Module._compile (internal/modules/cjs/loader.js:1102:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)

Line 93 is the first ?? in that file; the same expression appears again at line 115. On Node 10.24.1 and Node 12.6.0 the message has no quotes around the question mark, which is the exact form in the Stack Overflow question:

text
SyntaxError: Unexpected token ?
    at Module._compile (internal/modules/cjs/loader.js:723:23)

Two details tell you this is a runtime problem and not a problem in your code. The trace points into node_modules/typescript/lib/tsc.js, never into your file. And tsc -v fails the same way: in the reproduction it printed the same SyntaxError instead of a version number, because Node has to parse the whole compiler before it can answer anything.

Why the compiler no longer loads on Node 12#

The TypeScript 5.1 release notes list this under breaking changes:

TypeScript 5.1 now ships JavaScript functionality that was introduced in ECMAScript 2020. As a result, at minimum TypeScript must be run in a reasonably modern runtime. For most users, this means TypeScript now only runs on Node.js 14.17 and later.

The same section shows this exact startIndex ?? 0 frame as the expected failure "under an older version of Node.js such as Node 10 or 12", and links the change in microsoft/TypeScript#53291. The ?? (nullish coalescing) operator is what Node trips on: the Node.js 14.0.0 release notes list "Enables Nullish Coalescing by default" as part of the V8 8.1 update. Any Node before 14.0.0 reads ?? as a stray token.

The floor is also written in each release's engines field on the npm registry. Read with npm view typescript@<version> engines:

TypeScriptengines.nodeTested here
4.9.5>=4.2.0compiles on Node 12.17.0
5.0.4>=12.20outside its range on 12.17.0
5.1.6, 6.0.3>=14.17compile on Node 14.21.3; 5.1.6 also on Node 18.20.8
7.0.2 (latest)>=16.20.0runs on Node 20.20.2, fails on 12.17.0 and 14.21.3

npm does not refuse an install that breaks this range; it only warns. With npm 6.14.4, bundled with Node 12.17.0, the warning reads:

text
npm WARN notsup Unsupported engine for typescript@5.1.6: wanted: {"node":">=14.17"} (current: {"node":"12.17.0","npm":"6.14.4"})
npm WARN notsup Not compatible with your version of node/npm: typescript@5.1.6

The release notes show the newer npm wording, npm WARN EBADENGINE Unsupported engine, from npm 8.19.2. Either way the package is installed, and the error only appears the first time you run tsc.

Fix: run tsc on a supported Node#

  1. Check which Node your shell runs. The tsc launcher starts with #!/usr/bin/env node, so it uses the first node on the PATH of the terminal you type in. Run this in that same terminal:

    bash
    node -v

    On Windows, where node lists the node executables found on the PATH, which shows when a version manager such as Nodist and a separate installer both put one there. If node -v prints a version below 14, this is your cause; anything below 14.17 is still outside TypeScript 5.1's supported range, so upgrade either way. If it prints 14 or later, the node running tsc is a different copy (see the last section).

  2. Install and select a current LTS. With nvm on macOS or Linux, the README documents the LTS shortcuts:

    bash
    nvm install --lts
    nvm use --lts

    On Windows, use the same step in your version manager, or the installer from nodejs.org. The top-voted answer on the question did exactly this with nvm and Node 18.

  3. Reinstall TypeScript under the new Node. Run the global install again from the terminal where node -v now shows the new version, so the compiler is installed for that Node:

    bash
    npm install -g typescript
    tsc -v

    For a project, prefer a local compiler in devDependencies and an npm script; the page on running the project's local tsc instead of a global one shows that setup. If npm install -g then fails with EACCES, fix the global prefix rather than reaching for sudo.

If you must stay on Node 12 for now#

Pin a compiler whose range includes your runtime. TypeScript 4.9.5 compiled a test file on Node 12.17.0:

bash
npm install -g typescript@4.9.5
tsc -v
text
Version 4.9.5

This is the fix the second answer on the question describes. TypeScript 5.0.4 also compiled the same two-line file on Node 12.17.0, but its declared range is >=12.20, so 12.17 is outside what it supports; do not rely on it. For a project, write the pin into package.json ("typescript": "4.9.5" in devDependencies) so a fresh npm install cannot pull a 5.1+ compiler back in.

Verify the fix#

Compile a small file and run its output. On Node 14.21.3 with TypeScript 5.1.6, the reproduction gave:

text
Version 5.1.6
+ rm -f hello.js
+ tsc hello.ts
+ echo exit=0
+ node hello.js
exit=0
Hello from London

tsc -v printing a version is the real signal: under the broken pairing it never gets that far. Then run your usual build, such as npx tsc -p . or your build script, from the same terminal.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Plain npm install -g typescript on Node 12 or 14 now fails differently#

Many readers follow "install the latest TypeScript" on the old Node. Since TypeScript 7.0.2 became latest, that no longer produces the ?? error. On Node 12.17.0 and on Node 14.21.3, the install succeeds with warnings, then tsc -v fails like this (Node 14.21.3):

text
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /usr/local/lib/node_modules/typescript/bin/tsc
    at new NodeError (internal/errors.js:322:7)
    at Loader.defaultGetFormat [as _getFormat] (internal/modules/esm/get_format.js:71:15)

In 7.0.2 the package declares "type": "module" and its bin/tsc file, which has no extension, contains import "../lib/tsc.js";. Node 12.17.0 and 14.21.3 refuse to load that file; Node 20.20.2 ran the same install and printed Version 7.0.2. TypeScript 7.0.2 declares node >=16.20.0 anyway, so the fix is the same: move to a current Node LTS. On Node 14.17 or later, TypeScript 6.0.3 is the newest major whose range still includes Node 14, and it compiled the test file on Node 14.21.3.

Through npx, npm 6 hides the stack#

When TypeScript 5.1.6 is a local devDependency on Node 12.17.0, npx tsc hello.ts with npm 6.14.4 printed only one line:

text
Unexpected token '?'

There is no file name or line number, so it is easy to mistake for a syntax error in your own code. The cause and the fix are the same as above.

"I upgraded Node and it still fails"#

The question's author had tried installing the latest Node and still got the error, and the third answer reports the same with Node 18. As a control, TypeScript 5.1.6 on Node 18.20.8 compiled the test file and printed Version 5.1.6. A Node 18 process does not raise this error. If you see it after an upgrade, the node that runs tsc is still an old copy: a second installation earlier in PATH, a version manager that was not switched in this terminal, or an editor or CI job with its own Node. Running node -v in the same context as the failing command settles it. In CI, set the Node version in the workflow itself; the same habit avoids Next.js refusing to build on an old Node.

Once tsc runs on a supported Node, any remaining errors are about your code and configuration. Readers moving all the way to TypeScript 6 should check the compiler option changes in the TypeScript 6.0 migration notes, since strict mode becomes the default and classic module resolution is removed there. In short, the ?? error is never fixed inside the .ts file: the Node that runs tsc and the TypeScript version you install have to support each other.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote