Syntax
TypeScript is a strict syntactic superset of JavaScript: every valid JavaScript program is a valid TypeScript program. The grammar adds type annotations, type declarations, generics, decorators, and several constructs not present in plain JavaScript (enum, namespace, parameter properties, as assertions). The compiler (tsc) checks types and emits JavaScript suitable for any conforming ECMAScript runtime; the type information is erased during emission. The conventional contemporary configuration uses ES module syntax, target: ES2022 or later, and strict: true for the full type-discipline surface.
This page covers the surface a working programmer encounters routinely.
A complete program
The classical hello world:
const greeting: string = "Hello, world!";
console.log(greeting);
A more substantial example:
import { readFile } from "node:fs/promises";
interface Person {
name: string;
age: number;
email?: string;
}
async function loadPeople(path: string): Promise<Person[]> {
const data = await readFile(path, "utf8");
return JSON.parse(data) as Person[];
}
function greet(p: Person): string {
return `Hello, ${p.name}.`;
}
const people = await loadPeople("people.json");
for (const p of people) {
console.log(greet(p));
}
The principal features visible:
import { ... } from "..."— ES module import.interface Person— structural type declaration.name: string— type annotation on field.email?: string— optional field.Promise<Person[]>— generic type.- Template literal
`Hello, ${p.name}.`. as Person[]— type assertion.for (const p of people)— iteration.- Top-level
await(admitted in modules).
Compilation and execution:
tsc hello.ts && node hello.js
For development, direct execution via tsx, ts-node, Bun, or Deno is conventional:
tsx hello.ts
bun run hello.ts
deno run hello.ts
File extensions
The principal forms:
| Extension | Use |
|---|---|
.ts | TypeScript module |
.tsx | TypeScript with JSX (React, Solid, etc.) |
.mts | ES module (explicit) |
.cts | CommonJS module (explicit) |
.d.ts | Declaration file — types only, no runtime code |
.d.mts, .d.cts | Declaration files for the explicit module forms |
The .mts/.cts extensions are conventional in projects that mix ES module and CommonJS code; the .d.ts files are the type-only declarations consumed by TypeScript.
Source character set
TypeScript source is interpreted as UTF-8. Identifiers may use Unicode letters; the conventional code uses ASCII identifiers.
Identifiers and keywords
The convention follows the JavaScript style:
camelCase— variables, functions, parameters, methods.PascalCase— classes, interfaces, type aliases, enums, constructors.SCREAMING_SNAKE_CASE— constants representing static configuration values;camelCaseis conventional for ordinaryconstbindings.- A leading
_is conventional for “intentionally unused”;_unused.
The reserved keywords inherit from JavaScript. TypeScript adds contextual keywords that are reserved only in specific positions:
abstract any as asserts bigint boolean
constructor declare enum from get implements
infer interface is keyof let module
namespace never null number object of
out override package private protected public
readonly require satisfies set static string
symbol type undefined unique unknown void
The form r#name is not admitted in TypeScript (unlike Rust); reserved words cannot be identifiers.
Comments
Three comment forms:
// A line comment, terminated by the end of the line.
/* A block comment.
Block comments do NOT nest. */
/**
* A JSDoc comment for the next item.
* @param name the name to greet
* @returns a greeting string
*/
function greet(name: string): string {
return `Hello, ${name}.`;
}
The /** ... */ JSDoc form admits structured documentation; the conventional tooling (TypeDoc, IDE integrations) consumes JSDoc and produces HTML documentation.
JSDoc may also be used for type information in .js files:
/**
* @param {string} name
* @returns {string}
*/
function greet(name) {
return `Hello, ${name}.`;
}
The mechanism admits gradual TypeScript adoption in JavaScript projects.
Statements vs expressions
TypeScript inherits JavaScript’s statement-vs-expression distinction. The principal expression-oriented constructs:
const x = a + b; // arithmetic
const max = a > b ? a : b; // ternary
const items = [1, 2, 3]; // array literal
const obj = { x: 1, y: 2 }; // object literal
const f = () => 42; // arrow function
const result = (() => { // IIFE
let s = 0;
for (let i = 0; i < 10; i++) s += i;
return s;
})();
The principal statement forms:
let x = 5; // declaration
if (cond) { /* ... */ } else { /* ... */ } // selection
for (let i = 0; i < n; i++) { /* ... */ } // iteration
return value;
throw new Error("...");
if, for, while, switch are statements — they do not produce values. The conventional substitutes for value-returning conditionals are the ternary operator (?:) and the IIFE ((() => { ... })()).
Variable declarations
Three forms:
const x = 5; // immutable binding (preferred)
let y = 10; // mutable binding
var z = 15; // legacy; rarely used
The conventional discipline:
- Use
constby default. - Use
letwhen reassignment is required. - Avoid
var— its function-scoped semantics are surprising.
Type annotations are optional; the compiler infers types from initialisers:
const x = 5; // inferred as number
const x: number = 5; // explicit
const arr = [1, 2, 3]; // inferred as number[]
const arr: number[] = [1, 2, 3]; // explicit
let user: { name: string; age: number };
user = { name: "Alice", age: 30 };
For inferred types that should be the literal type, the as const assertion:
const ROLES = ["admin", "user", "guest"] as const;
// type: readonly ["admin", "user", "guest"]
const config = {
host: "localhost",
port: 8080,
} as const;
// type: { readonly host: "localhost"; readonly port: 8080 }
The as const admits substantial type precision for configuration data and lookup tables.
Block syntax
Control-flow constructs admit either a block (preferred) or a single statement:
if (cond) {
doSomething();
}
if (cond) doSomething(); // OK; one-liner
if (cond)
doSomething(); // OK; admitted
if (cond) doFirst();
else doSecond();
The conventional discipline always uses braces for multi-statement bodies; one-liners are admitted but inconsistent style is discouraged.
Functions
The function keyword introduces a function declaration:
function add(a: number, b: number): number {
return a + b;
}
function greet(name: string): void {
console.log(`Hello, ${name}`);
}
Arrow functions admit a more compact form, with subtly different this binding:
const add = (a: number, b: number): number => a + b;
const greet = (name: string): void => console.log(`Hello, ${name}`);
const square = (n: number) => n * n; // return type inferred
Arrow functions are expressions; function declarations are statements (and are hoisted to the enclosing scope).
Treated in Functions.
Type annotations
Type annotations follow the colon convention:
let n: number;
let s: string = "hello";
const arr: number[] = [1, 2, 3];
const tup: [string, number] = ["alice", 30];
function f(x: number, y: number): number {
return x + y;
}
interface Point {
x: number;
y: number;
}
type Status = "active" | "inactive" | "banned";
The compiler infers types when possible; explicit annotations are conventional for:
- Function parameters — always.
- Function return types — for exported APIs.
- Public class members — always.
- Local variables — only when the inferred type is wrong.
Modules and imports
The conventional contemporary form is ES modules:
// math.ts:
export function add(a: number, b: number): number { return a + b; }
export const PI = 3.14159;
export default function multiply(a: number, b: number): number { return a * b; }
// main.ts:
import multiply, { add, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(PI);
console.log(multiply(2, 3));
Note the .js extension in the import path — TypeScript imports refer to the emitted JavaScript path, not the source .ts path. (Some configurations admit .ts imports; the conventional setting is to write .js.)
Treated in Scope and modules.
The tsconfig.json
The compiler is configured by tsconfig.json at the project root:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
The conventional contemporary recommendations:
strict: true— enables the full type-discipline surface.target: ES2022(or later) — modern output without unnecessary down-levelling.moduleResolution: "Bundler"— for projects using bundlers (Vite, esbuild, etc.).noUncheckedIndexedAccess: true— array indexing returnsT | undefined.
Treated in Tooling and packaging.
JSX
The .tsx extension admits JSX syntax — XML-like elements producing JavaScript:
function Greeting({ name }: { name: string }): JSX.Element {
return <h1>Hello, {name}!</h1>;
}
const element = <Greeting name="Alice" />;
JSX is principally used with React; alternative libraries (Solid, Preact, Vue’s JSX form) consume the same syntax with different runtime behaviour.
A note on what TypeScript admits
TypeScript adds substantial type-system features over JavaScript:
- Static type checking — at compile time only; types are erased.
- Type annotations on variables, parameters, return values.
- Interfaces and type aliases.
- Generics — type parameters on functions, classes, types.
- Enums — including
const enumfor compile-time constants. - Decorators — Stage 3 ECMAScript decorators (TS 5.0+).
- Access modifiers on class members (
public,private,protected,readonly). - Abstract classes and abstract members.
- Namespaces — internal modules, conventionally replaced by ES modules.
- Mapped types, conditional types, template literal types.
- Type assertions and type predicates.
Treated in subsequent pages.
A note on what is not in TypeScript
- Runtime type checking — TypeScript does not check types at runtime; types are erased.
- Operator overloading — JavaScript-level only.
- Multiple inheritance — single inheritance plus interfaces.
- Pointers — JavaScript-level only.
- Manual memory management — garbage-collected.
- Macros — none; code generation via build tools or
decorators.
The combination — superset of JavaScript, structural type system, type erasure, conventional tooling — is the substance of TypeScript’s identity. The discipline is largely about expressing the program’s intent through the type system while producing standard-compliant JavaScript.