What is TypeScript?

TypeScript is a programming language that adds a static type system on top of JavaScript. You write .ts files instead of .js, and a compiler checks your code for type errors before it ever runs in a browser or on a server. The output is plain, readable JavaScript that any runtime can execute - the types themselves are erased.

The language was designed at Microsoft by Anders Hejlsberg, the same person behind Turbo Pascal, Delphi, and C#. It was first released in 2012 as an answer to a specific problem: JavaScript codebases were growing past the size where dynamic typing was an acceptable trade. TypeScript is open source under the Apache 2.0 license, developed on GitHub, and is the language of choice for most large frontend codebases shipped today.

TypeScript is JavaScript with syntax for types.- typescriptlang.org

Why teams adopt it

The answer is rarely a single feature. TypeScript pays off in five places at once:

  • Errors caught before the code runs. Typos, missing properties, wrong argument types, and unreachable code are surfaced by the compiler instead of by a user.
  • Editor tooling that actually understands the code. Autocomplete, jump-to-definition, find-all-references, and rename refactors all work because the editor has a real model of what your code means.
  • Self-documenting interfaces. A function's parameter and return types are part of its signature. New team members can read the API without running the code.
  • Confident refactoring. Renaming a field on an interface immediately surfaces every usage site that needs to update. Large codebases are tractable to change because the compiler tells you what you missed.
  • An ecosystem that assumes types are there. Most major libraries ship type definitions. Frameworks like Angular are TypeScript-first; React, Vue, Svelte, and the rest provide first-party type definitions.

The cumulative effect is what people mean when they say TypeScript "scales". The codebase grows past what any individual can hold in their head, and the type system becomes the shared memory of the team.

JavaScript with types

TypeScript is a typed superset of JavaScript. Any valid JavaScript file is also a valid TypeScript file. You can rename a .js file to .ts, and it will compile - though with no type information, the compiler can only check what JavaScript itself enforces.

Types are added gradually. A new project starts strict; an existing codebase can adopt TypeScript file-by-file, with mixed .js and .ts files coexisting during the migration. The any type is an explicit escape hatch for code the type system cannot yet describe, and the // @ts-expect-error comment lets you suppress a specific error without globally disabling the check.

This gradualism is why TypeScript adoption is rarely a rewrite. Teams introduce it on the edge - a new feature, a new service, a new file - and let the type coverage grow inward over time.

The type system in three minutes

Most everyday TypeScript uses a small subset of the language. The four building blocks below cover the majority of real code:

TypeScript primitives.ts
// primitive types and arrays const name: string = "Anders"; const age: number = 62; const active: boolean = true; const tags: string[] = ["compiler", "language"];

Interfaces describe the shape of an object. They are the most common way to give a name to a record type.

TypeScript order.ts
interface Order { id: string; total: number; status: "open" | "paid" | "cancelled"; shippedAt?: Date; // optional } function summarize(order: Order): string { return `Order ${order.id} - ${order.status}`; }

Union types (above, on status) let a value be exactly one of a closed set of possibilities - and the compiler will refuse code that mishandles any of them. Generics let a function or type work with a value whose specific type is decided by the caller:

TypeScript api.ts
// T is bound when the function is called async function fetchJson<T>(url: string): Promise<T> { const res = await fetch(url); return res.json() as T; } // caller decides what T is const orders = await fetchJson<Order[]>("/api/orders");

There is more - mapped types, conditional types, template literal types - but these four account for the bulk of code in real applications. Reach for the rest when a library type or a generic utility genuinely needs them.

Types disappear at runtime

This is the single most important thing to internalize about TypeScript: the type system has zero runtime cost. The compiler reads your code, checks the types, and then deletes the type annotations. What runs in the browser is plain JavaScript with no trace of the types ever having existed.

TS · before greet.ts
function greet(name: string): string { return `Hello, ${name}`; }
JS · after greet.js
function greet(name) { return `Hello, ${name}`; }

The practical consequence is that you cannot check a TypeScript type at runtime - typeof and instanceof still work, but the structural types you defined (like the Order interface above) are gone. If you need runtime validation - for data coming off the network, or from user input - reach for a schema library like Zod, Valibot, or io-ts. The type system catches what it can statically; the schema library catches what crosses an untrusted boundary.

Type-check vs transpile

For most of TypeScript's history, the compiler called tsc did both jobs at once: it type-checked your code and emitted the JavaScript that ran in production. Modern toolchains split those two jobs across two tools.

  • Type-checking is what tsc --noEmit does. It reads your code, validates types, and reports errors. It does not produce any output files. This is what you run in CI and what your editor's language server runs continuously while you type.
  • Transpilation is the production output - turning .ts into .js the browser can run. Fast bundlers like esbuild and SWC strip TypeScript types as if they were correct, without doing the heavy semantic analysis tsc does. The output ships in milliseconds.

The split exists because the two jobs have very different cost profiles. Full type-checking is slow and only needs to run on changed code or in CI. Stripping types is essentially free and can run on every keystroke. Vite, Next.js, and the Angular CLI all follow this model: esbuild or SWC for the transform, tsc for the type check.

The practical guidance: run tsc --noEmit as part of your CI pipeline. Trust your bundler for the output. Treat them as two separate questions and they both go fast.

TypeScript at scale

TypeScript is the language of large frontend codebases now. The reasons are practical, not ideological.

  • Refactors stay tractable. Renaming a property on an interface flags every call site that needs to change. The compiler is the team's memory of what depended on what.
  • Onboarding is faster. A new engineer can read function signatures and infer what the code does, rather than running it to find out.
  • The IDE is honest. Autocomplete reflects what is actually available on a value. Suggestions you accept are correct by construction, not just plausible.
  • Cross-team contracts are explicit. A type definition on a service or API client is a written agreement. Breaking it is a compiler error, not a runtime surprise.

The TypeScript team itself is the existence proof: the TypeScript compiler is written in TypeScript, has been under continuous development since 2012, and is one of the most-edited open source projects in the world.

When to use TypeScript

The honest answer for new projects in 2026 is: almost always. The cost of adoption has dropped to near zero - every major framework's project template uses TypeScript by default, every modern bundler handles it natively, and editor support is excellent everywhere. The benefits compound as the codebase grows.

Reach for plain JavaScript when the project is genuinely tiny, throwaway, or constrained to a runtime that does not support the type-erasure step. Everything else should be in TypeScript - and almost everything else already is.