Skip to content
TypeScript logo

TypeScript

First appeared 2012 · Anders Hejlsberg

JavaScript with a type system bolted on top — all the reach, far fewer runtime surprises.

Overview

TypeScript is a strongly typed, object-oriented programming language that builds on JavaScript, giving you better tooling, error detection, and code quality at any scale. Developed and maintained by Microsoft since 2012, TypeScript is a syntactic superset of JavaScript, meaning that any valid JavaScript code is also valid TypeScript code. The language adds optional static type definitions to JavaScript, enabling developers to catch errors early during development through a sophisticated type system, making JavaScript development more efficient, maintainable, and scalable. TypeScript code compiles down to clean, readable, standards-compliant JavaScript that runs anywhere JavaScript runs: in web browsers, on Node.js servers, in mobile applications, or in any JavaScript engine. It fully supports all modern JavaScript features from ECMAScript standards while providing additional powerful capabilities like interfaces for defining contracts, generics for reusable type-safe code, enums for named constants, decorators for meta-programming, advanced type inference that understands complex patterns, union and intersection types, type guards, and much more. TypeScript's type system is structural rather than nominal, meaning types are compatible based on their structure rather than explicit declarations, which aligns well with JavaScript's dynamic nature. The language provides excellent IDE support with intelligent code completion, refactoring tools, inline documentation, and real-time error detection, dramatically improving developer productivity and reducing bugs. TypeScript is particularly valuable for large-scale applications, team collaboration, and long-term code maintenance where type safety, clear interfaces, and self-documenting code are crucial. It has become the de facto standard for enterprise JavaScript development and is widely adopted across the industry, from startups to Fortune 500 companies. TypeScript's gradual typing system allows developers to adopt it incrementally, adding types to existing JavaScript codebases at their own pace without requiring a complete rewrite. The language has a vibrant ecosystem with extensive community support, comprehensive documentation, and integration with virtually every major JavaScript framework, library, and build tool. TypeScript's influence extends beyond just adding types; it has shaped modern JavaScript development practices, influenced ECMAScript proposals, and set standards for how large-scale JavaScript applications should be structured and maintained.

Key facts

The reference details, without the paragraph.

First appeared
2012
Designed by
Anders Hejlsberg at Microsoft (also behind Turbo Pascal, Delphi and C#)
Typing
Static, structural and gradual — types are checked at compile time and erased at runtime
Execution
Compiles to plain JavaScript; runs anywhere JavaScript runs
Relationship to JavaScript
A strict superset — every valid `.js` file is a valid `.ts` file
Package manager
npm, pnpm, yarn or bun
File extensions
.ts, .tsx, .d.ts
Release cadence
A new minor version roughly every three months
Licence
Apache 2.0

History

How the language got here — the decisions that still shape how you write it.

TypeScript was developed by Microsoft and first released in October 2012, created by Anders Hejlsberg, the renowned language designer who also created C#, Delphi, and Turbo Pascal. The motivation behind TypeScript emerged from Microsoft's own struggles with building and maintaining large-scale JavaScript applications, particularly for projects like Office 365 and Visual Studio Online. The lack of static typing in JavaScript made code maintenance, refactoring, and collaboration across large teams extremely challenging, leading to runtime errors that could have been caught at compile time. Microsoft recognized that while JavaScript was ubiquitous and powerful, it lacked the tooling support and type safety that developers needed for enterprise-scale applications. TypeScript was designed as a pragmatic solution that would provide the benefits of static typing while maintaining full backward compatibility with existing JavaScript code and the entire JavaScript ecosystem. The language was open-sourced from the beginning, with development happening publicly on GitHub, which helped build community trust and adoption. TypeScript's design philosophy emphasized gradual adoption, allowing developers to add types incrementally to existing JavaScript codebases without requiring a complete rewrite. The first major breakthrough came in 2014 when the Angular team at Google announced that Angular 2 would be built with TypeScript, providing significant validation and visibility for the language. This decision was driven by Angular's need for better tooling, maintainability, and developer experience for large applications. Following Angular's adoption, other major frameworks and libraries began embracing TypeScript, including React (with extensive TypeScript support), Vue 3 (rewritten in TypeScript), and countless others. The language gained momentum in the open-source community, with major projects like Visual Studio Code (itself written in TypeScript), Slack's desktop application, Asana, and Airbnb's frontend infrastructure adopting it. TypeScript's type system has evolved significantly over the years, introducing advanced features like conditional types, mapped types, template literal types, and sophisticated type inference that can understand complex JavaScript patterns. The language has maintained its commitment to tracking ECMAScript standards, ensuring that every new JavaScript feature is supported in TypeScript, often with enhanced type safety. Microsoft's investment in TypeScript has been substantial, with a dedicated team continuously improving the compiler's performance, error messages, and type-checking capabilities. The TypeScript compiler has become remarkably fast, capable of type-checking millions of lines of code efficiently. The language's impact extends beyond just adding types to JavaScript; it has influenced how developers think about code organization, API design, and software architecture. TypeScript's success has inspired similar efforts in other dynamic languages and has become a model for how to add static typing to an existing language ecosystem. Today, TypeScript is one of the most loved and widely used programming languages, consistently ranking high in developer satisfaction surveys. Its adoption continues to grow across startups, enterprises, and open-source projects, cementing its position as an essential tool for modern web development. The language continues to evolve with regular releases every few months, adding new features, improving type inference, and maintaining compatibility with the ever-evolving JavaScript ecosystem.

  1. 2012

    Microsoft announces TypeScript

    Anders Hejlsberg presents a typed superset of JavaScript aimed at the problem Microsoft had internally: large JavaScript applications that no one could safely refactor.

  2. 2013

    DefinitelyTyped

    A community repository of type definitions for existing JavaScript libraries launches. It solves the bootstrapping problem — you can adopt TypeScript without waiting for every dependency to convert.

  3. 2016

    Angular 2 adopts it as the default

    A major framework choosing TypeScript as its primary language moves it from a Microsoft project to an ecosystem standard.

  4. 2018

    Types get powerful

    Conditional types, mapped types and `infer` arrive. The type system becomes expressive enough to model real JavaScript APIs rather than approximating them.

  5. 2020

    Template literal types

    Types can be computed from string literals, enabling precisely typed routes, event names and CSS units — a level of precision most static languages do not offer.

  6. 2023

    `satisfies` and const type parameters

    `satisfies` lets you check a value against a type without widening it, closing a long-standing gap between inference and validation.

  7. 2025

    A native compiler

    Microsoft begins porting the compiler to Go for a roughly tenfold speed-up on large codebases, addressing the single most common complaint about TypeScript at scale.

What it is good at

The reasons teams pick it, stated concretely.

  • Refactoring stops being frightening

    Rename a field, change a signature, or delete a branch and the compiler lists every site that must change. On a codebase of any size this is the single largest productivity difference from plain JavaScript.

  • Your editor becomes genuinely useful

    Accurate autocomplete, inline documentation, go-to-definition and find-all-references all fall out of having types. Much of the value arrives before you have written a single annotation, through inference.

  • Structural typing fits JavaScript's habits

    Types match on shape, not on declared inheritance. An object literal with the right fields satisfies an interface without announcing it, so typing existing JavaScript patterns rarely requires restructuring them.

  • Gradual adoption is real

    Rename a file to `.ts`, leave `strict` off, and fix errors at your own pace. `any` is an escape hatch that lets a migration proceed incrementally instead of as a big-bang rewrite.

  • Types shared across the whole stack

    One definition of a request payload can be enforced in the API handler, the client that calls it and the tests for both. Tools such as tRPC and Zod push this further, deriving runtime validation and types from a single source.

Trade-offs

Every language costs you something. Knowing what, before you commit, is the whole point.

  • Types vanish at runtime

    The compiler erases everything. Data crossing a boundary — a network response, `JSON.parse`, a form — is unchecked at runtime no matter how precisely it is typed. Validate at the edges with something like Zod or Valibot.

  • A build step you cannot skip

    Plain JavaScript runs as written. TypeScript needs compilation, source maps and configuration. Newer runtimes reduce the friction by executing `.ts` directly, but the type-check is still a separate job.

  • The type system has a deep end

    Conditional types, mapped types and recursive generics can express remarkable things and produce error messages dozens of lines long. Knowing when to stop and write `as` is a skill in itself.

  • Compile times on large codebases

    A million-line project can take minutes to type-check. Project references and `skipLibCheck` help, and the native compiler port is aimed squarely at this.

  • `any` quietly disables everything

    One `any` propagates through every expression that touches it, silently switching off checking in places you did not intend. Prefer `unknown` and narrow explicitly.

Code examples

Not syntax tours — the idioms that make code read like the language rather than a translation of another one.

Discriminated unions make invalid states unrepresentable
type RequestState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: User[] }
  | { status: 'error'; message: string };

function render(state: RequestState): string {
  switch (state.status) {
    case 'idle':    return 'Nothing requested yet';
    case 'loading': return 'Loading…';
    case 'success': return `${state.data.length} users`;  // data exists here
    case 'error':   return state.message;                 // and message here
  }
}
The compiler narrows the type inside each branch, so `state.data` is only reachable when the request actually succeeded. Add a fifth state and every `switch` that does not handle it becomes a compile error — this pattern removes far more bugs than annotating function arguments does.
Validate at the boundary, infer the type from the validator
import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  role: z.enum(['admin', 'member']),
});

// One definition produces both the runtime check and the static type.
type User = z.infer<typeof UserSchema>;

async function loadUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return UserSchema.parse(await response.json());  // throws on bad shape
}
`response.json()` is typed `any` for a reason — nobody can know what the server actually sent. Parsing through a schema turns an unverified payload into a value the type system can trust, and `z.infer` keeps the type and the check from drifting apart.
Generics that keep the caller's precision
function pick<T extends object, K extends keyof T>(source: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>;
  for (const key of keys) result[key] = source[key];
  return result;
}

const user = { id: 1, name: 'Ada', email: 'ada@example.com', password: 'secret' };
const safe = pick(user, ['id', 'name']);

safe.name;      // string — known
// safe.password  // compile error: property does not exist
`K extends keyof T` constrains the keys to ones that actually exist, so a typo is caught at the call site, and the return type carries exactly the fields you asked for rather than collapsing to a generic object.
`unknown` instead of `any`
function parseConfig(raw: unknown): { port: number } {
  if (typeof raw !== 'object' || raw === null) {
    throw new Error('config must be an object');
  }
  if (!('port' in raw) || typeof raw.port !== 'number') {
    throw new Error('config.port must be a number');
  }
  return { port: raw.port };   // narrowed to number by the checks above
}
`unknown` accepts anything but permits nothing until you prove what it is. The checks that convince the compiler are the same checks that would have prevented a runtime crash — which is the point.

Common pitfalls

The mistakes that cost everyone an afternoon at least once.

  • Believing types exist at runtime

    You cannot check a type with `if`, and a cast does not convert anything. `as User` is a promise you make to the compiler, not a verification — if the data is wrong, it stays wrong.

  • Reaching for `as` to silence errors

    An assertion turns a compile error into a runtime crash later. When a type does not fit, the error is usually correct; fix the shape or narrow properly instead.

  • Running with `strict` disabled

    Without `strictNullChecks`, `undefined` is assignable to everything and the compiler cannot catch the most common bug in JavaScript. New projects should start strict; existing ones should migrate one flag at a time.

  • `enum` where a union would do

    TypeScript enums generate runtime code and have surprising numeric behaviour. A union of string literals is simpler, erasable and usually a better fit.

  • Typing what inference already knows

    `const count: number = 0` adds noise without adding information. Annotate function parameters, return types at module boundaries, and little else.

  • Trusting `JSON.parse`

    It returns `any`, so everything downstream silently loses checking. Type it as `unknown` and validate, or parse through a schema.

In production

Where it is running at scale, and what it is doing there.

  • Microsoft

    Office 365, Visual Studio Code, and various web applications.

  • Slack

    Desktop and web applications for better type safety.

  • Airbnb

    Frontend applications and internal tools.

  • Asana

    Web application development with improved developer experience.

Learning path

A realistic order to learn things in, with something to build at each step.

  1. 1

    Prerequisite

    Know JavaScript first

    Closures, `this`, promises and the module system. TypeScript adds types to JavaScript semantics; if the semantics are shaky the type errors will be baffling.

    Build this: Be comfortable reading and writing modern JavaScript without a framework.

  2. 2

    Week 1

    Annotations and inference

    Primitive types, arrays, objects, function signatures, union types, and — most importantly — where inference already does the job so you do not have to annotate.

    Build this: Convert a small JavaScript project by renaming files and fixing errors one at a time.

  3. 3

    Week 2

    Narrowing and unions

    `typeof`, `instanceof` and `in` guards, discriminated unions, optional properties, and `strictNullChecks` — the setting responsible for most of TypeScript's real-world value.

    Build this: Model an async operation's states as a discriminated union and render each one.

  4. 4

    Weeks 3–4

    Generics and tsconfig

    Type parameters, constraints, the built-in utility types (`Partial`, `Pick`, `Omit`, `Record`, `ReturnType`), and what each `tsconfig` flag actually changes. Turn on `strict` and leave it on.

    Build this: Write a typed wrapper around `fetch` that infers the response type from a schema.

  5. 5

    Ongoing

    The advanced type system, used sparingly

    Conditional and mapped types, template literal types, `satisfies`, declaration files, and runtime validation. Learn them so you can read library types — reach for them in your own code only when they earn their complexity.

    Build this: Read the type definitions of a library you use daily and work out how they achieve their autocomplete.

Ecosystem and tooling

The tools you will end up installing whichever project you join.

ToolWhat it does
tscThe official compiler and type checker; the reference for what TypeScript means
ts-node / tsxRun TypeScript directly without a separate build step during development
esbuild / SWCExtremely fast transpilers that strip types for builds, leaving type-checking to `tsc`
Zod / ValibotRuntime schema validation that infers static types, closing the gap at I/O boundaries
typescript-eslintLint rules that use type information — catches issues a syntax-only linter cannot
DefinitelyTypedCommunity type definitions (`@types/*`) for JavaScript libraries that ship none
tRPCEnd-to-end typed client-server calls with no code generation or schema files
VitestTest runner with native TypeScript support and no configuration

TypeScript libraries

28 catalogued, each with installation, worked examples and best practices.

Frequently asked

Does TypeScript make my code slower?

No. Types are erased during compilation, so the JavaScript that runs is the JavaScript you would have written. The cost is at build time, not runtime.

Is it worth it for a small project?

For a throwaway script, probably not. For anything you will return to in three months, or that anyone else will touch, the break-even point arrives much sooner than people expect — usually within a few hundred lines.

What does 'structural typing' mean in practice?

Compatibility is decided by shape, not by name. Any object with `id: number` and `name: string` satisfies an interface requiring those fields, whether or not it was declared to implement it. This is why typing existing JavaScript works so well, and why two unrelated types with identical fields are interchangeable.

Do I still need runtime validation?

Yes, for anything crossing a boundary: HTTP responses, form input, environment variables, files, message queues. The compiler can only reason about code it sees; everything else is a promise you should verify.

`interface` or `type`?

Either. `interface` supports declaration merging and gives marginally better errors for object shapes; `type` handles unions, intersections and mapped types. Pick one as the default for object shapes, use `type` where `interface` cannot, and stop debating it.

Will TypeScript become part of JavaScript?

There is a stage-1 proposal to let JavaScript engines ignore type annotations as comments, which would remove the build step for type stripping. It is genuinely early, and it would standardise the syntax rather than the checking.