TypeScriptThe Compiler (tsc)

The TypeScript Compiler (tsc)

tsc is the TypeScript compiler. It reads your .ts source files, performs type-checking, and emits plain JavaScript. Understanding what tsc does — and what it does not do — is essential for working with TypeScript effectively.

What tsc Actually Does

tsc does two distinct jobs in a single pass:

  1. Type-checking — reads your code, builds a type model, and reports any inconsistencies as errors
  2. Transpilation — converts TypeScript syntax to JavaScript that a runtime can execute

These two jobs are related but separable. You can ask tsc to only type-check (no emit), or to emit without failing on errors. Most of the time you want both.

Note
tsc is a compiler, not a bundler. It does not bundle files together, tree-shake dead code, or minify output. For those tasks use webpack, Vite, esbuild, or Rollup — usually alongside or instead of running tsc directly.
The Compilation Pipeline

When you run tsc, it executes three stages internally:

  1. Parsing — reads the .ts files and builds an Abstract Syntax Tree (AST). Syntax errors are caught here.

  2. Type checking — walks the AST, resolves names and types, and reports type errors. This is the heart of TypeScript.

  3. Emit — walks the type-checked AST and writes out .js (and optionally .d.ts declaration files). Type annotations are erased.

Only the third stage produces files. The first two stages are pure analysis. If you pass --noEmit, the compiler skips stage 3 entirely — useful in CI to validate types without generating output.

Basic Usage — Single File

The simplest way to use tsc is to pass a file directly:

Bash
npx tsc hello.ts

This compiles hello.ts to hello.js in the same directory. When you pass a file directly on the command line, tsc ignores any tsconfig.json in the project — all settings come from CLI flags only.

Warning
Passing a file directly to tsc bypasses tsconfig.json. In a real project you almost always want to run npx tsc with no arguments so it picks up the config file.
Basic Usage — With tsconfig.json

Run tsc with no arguments from the project root to use tsconfig.json:

Bash
npx tsc

tsc searches for tsconfig.json starting from the current directory and walking up the directory tree. You can also specify the config file explicitly:

Bash
npx tsc --project tsconfig.prod.json
# or the short form:
npx tsc -p tsconfig.prod.json
Important CLI Flags

Flag

What it does

Example

--target

JavaScript version to emit (ES3, ES5, ES2015...ES2022, ESNext)

--target ES2020

--module

Module system for output (commonjs, esm, node16, nodenext)

--module commonjs

--outDir

Where to put emitted .js files

--outDir dist

--rootDir

Root of input .ts files (mirrors structure in outDir)

--rootDir src

--strict

Enable all strict type-checking flags at once

--strict

--watch

Recompile when files change

--watch or -w

--noEmit

Type-check only; do not emit any files

--noEmit

--declaration

Also emit .d.ts type declaration files

--declaration

--sourceMap

Generate .js.map source map files

--sourceMap

--allowJs

Also process .js files (for migrating JS codebases)

--allowJs

--checkJs

Type-check .js files too (use with --allowJs)

--checkJs

--esModuleInterop

Allow default imports from CommonJS modules

--esModuleInterop

--skipLibCheck

Skip type checking of all .d.ts files

--skipLibCheck

--project / -p

Path to a tsconfig.json file

-p tsconfig.prod.json

--target: Controlling Output JavaScript

The --target flag tells tsc which JavaScript version to emit. TypeScript will downlevel (polyfill) syntax features not available in the target:

TS
// Source (TypeScript)
const greet = (name: string): string => `Hello, ${name}!`;

Bash
# Target ES3 (very old browsers)
npx tsc --target ES3 hello.ts

JS
// Emitted with --target ES3
var greet = function (name) { return "Hello, " + name + "!"; };

Bash
# Target ES2020 (modern Node / browsers)
npx tsc --target ES2020 hello.ts

JS
// Emitted with --target ES2020
const greet = (name) => `Hello, ${name}!`;
Tip
For Node.js 18+ projects, use --target ES2022 or--target ESNext. For libraries that must support older browsers, use --target ES2015 or --target ES5.
--module: Module System

The --module flag controls how TypeScript emits import / export statements:

Value

Use case

commonjs

Node.js CJS (require / module.exports). Most common for Node.

esm

Pure ESM output. Use with --target ES2015+.

node16 / nodenext

Node.js 12+ dual-mode (detects .mjs/.cjs). Recommended for modern Node projects.

preserve

Keep import/export as-is (let a bundler handle them). Good for Vite/webpack.

none

No module system — for simple scripts.

Bash
# For a Node.js backend
npx tsc --target ES2022 --module node16

# For a Vite/webpack frontend (let the bundler handle modules)
npx tsc --target ES2020 --module preserve --noEmit
--outDir and --rootDir

In a real project you want source files separate from compiled output. Use --outDir and --rootDir together:

Bash
npx tsc --rootDir src --outDir dist

With this configuration, src/index.ts compiles to dist/index.js, and src/utils/helpers.ts compiles to dist/utils/helpers.js — the directory structure is mirrored.

--strict: The Most Important Flag

--strict is a shorthand that enables a bundle of strict checking options. Without it, TypeScript lets many unsafe patterns slide silently. Always use it on new projects:

  • strictNullChecks — null and undefined are not assignable to other types

  • noImplicitAny — error when a type would be implicitly inferred as any

  • strictFunctionTypes — stricter checking of function parameter types

  • strictBindCallApply — stricter checking of bind, call, apply

  • strictPropertyInitialization — class properties must be initialized in the constructor

  • noImplicitThis — error when this has an implicit any type

  • alwaysStrict — emits "use strict" in all output files

Bash
# Always use --strict in new projects
npx tsc --strict
--watch: Development Mode

During development, you do not want to run tsc manually after every change. --watch mode keeps tsc running and recompiles whenever a file changes:

Bash
npx tsc --watch
# or the short form
npx tsc -w
[12:34:56] Starting compilation in watch mode...
[12:34:57] Found 0 errors. Watching for file changes.

[12:35:12] File change detected. Starting incremental compilation...
[12:35:12] Found 0 errors. Watching for file changes.

In watch mode, tsc does incremental compilation — it only recompiles files that changed and their dependents, making each rebuild fast.

--noEmit: Type Checking Without Output

--noEmit tells tsc to type-check everything but produce no output files. This is extremely useful in two scenarios:

  • CI pipelines — validate types without needing the build output

  • Projects using a bundler (Vite, webpack, esbuild) that handles transpilation — use tsc only for type checking

Bash
# Type-check the whole project, emit nothing
npx tsc --noEmit

# Type-check in watch mode (great during development with Vite)
npx tsc --noEmit --watch
Tip
In a Vite or Next.js project, the bundler handles transpilation and you usually run tsc --noEmit as a separate type-check step. This gives you the fastest possible dev server plus full type safety on demand.
Reading Error Messages

TypeScript error messages follow a consistent format. Learning to read them quickly is a core skill:

src/utils/format.ts:12:5 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

12     formatName(userId);
       ~~~~~~~~~

Breaking this down:

  • src/utils/format.ts — file where the error occurs
  • 12:5 — line 12, column 5
  • error — severity (also warning or message)
  • TS2345 — error code, useful for searching the TypeScript documentation
  • Description — plain English explanation of the type mismatch
  • The squiggly line — points at the exact token causing the error
Note
When searching for a TypeScript error, include the error code (e.g. "TS2345") in your search. The code is stable across versions and gets you directly to relevant explanations.
Multiple Errors — Triage Tips

In a large project tsc can emit dozens of errors at once. Here is how to triage:

  1. Fix errors in dependency order — a type error in a utility function cascades to every caller. Fix the root cause first.

  2. Errors in .d.ts files often mean a missing type package — run npm install @types/node (or the relevant package).

  3. TS2304 ("Cannot find name X") usually means a missing import or missing type declaration.

  4. TS2307 ("Cannot find module X") means the module path is wrong or the package is not installed.

  5. Count the unique error codes — 50 errors might be just 2 root causes.

Exit Codes

tsc follows standard Unix exit code conventions:

Exit code

Meaning

0

Success — no errors found, output emitted (or --noEmit passed)

1

Configuration error — bad tsconfig, missing input files, invalid flags

2

Type errors — compilation failed due to type-checking errors

Bash
npx tsc --noEmit
echo "Exit code: $?"

# Use in CI to fail the build on type errors
npx tsc --noEmit || { echo "Type errors found"; exit 1; }
Type Checking vs Transpilation

These two things are related but different, and many developers conflate them:

Type Checking

Transpilation

What it does

Analyzes types and reports errors

Converts .ts syntax to .js syntax

Output

Error messages

.js files (and optionally .d.ts files)

Controlled by

Type annotation correctness

--target, --module flags

Can be skipped?

With --transpileOnly (ts-node)

With --noEmit

Runtime effect

None — types are erased

Determines what JavaScript engines receive

Tools like esbuild and tsx only transpile — they strip type annotations but do not check types at all. They are fast but give you no safety guarantee. tsc does both. In many modern setups, transpilation is handled by a fast tool (esbuild/SWC) and type checking is a separate tsc --noEmit step.

Managing tsc Versions

Keep TypeScript pinned in your package.json and use the local version via npx rather than a globally installed tsc:

Bash
# Check which version is installed locally
npx tsc --version

# Install a specific version
npm install --save-dev typescript@5.4.5

JSON
{
  "scripts": {
    "type-check": "tsc --noEmit",
    "build": "tsc",
    "dev": "tsc --watch"
  },
  "devDependencies": {
    "typescript": "^5.5.0"
  }
}
Tip
In VS Code, look in the bottom status bar for the TypeScript version and click it to switch between the workspace version (fromnode_modules) and VS Code's built-in version. Always prefer the workspace version so editor and CLI use the same compiler.
Practical tsc Commands Cheatsheet

Bash
# Compile a single file
npx tsc hello.ts

# Compile the whole project (uses tsconfig.json)
npx tsc

# Type-check only, no output
npx tsc --noEmit

# Watch mode for development
npx tsc --watch

# Watch + no emit (type-check as you type)
npx tsc --noEmit --watch

# Compile with specific options
npx tsc --target ES2020 --module commonjs --outDir dist

# Use a custom config file
npx tsc -p tsconfig.prod.json

# Show compiler version
npx tsc --version

# Show all available compiler options
npx tsc --help --all
Success
You now understand what tsc does, how to read its error messages, and which flags matter most. The key insight: type checking and transpilation are two separate jobs that tsc does together — but you can split them apart for faster development workflows.