TypeScript is a strongly typed superset of JavaScript developed by Microsoft. First released in 2012, it adds optional static typing, interfaces, generics, enums, and other features to JavaScript while compiling down to plain JavaScript that runs in any browser or Node.js environment.
The key insight behind TypeScript is that type information exists only at compile time. When TypeScript code is compiled (or "transpiled") to JavaScript, all type-specific syntax is removed. The resulting JavaScript is valid, runnable code with the same runtime behavior -- the types simply disappear. This is known as "type erasure," and it is the fundamental principle behind TypeScript-to-JavaScript conversion.
TypeScript has grown enormously in popularity since its introduction. According to the 2025 Stack Overflow Developer Survey, TypeScript is the fourth most popular programming language overall and the most popular for web development among typed languages. Major frameworks like Angular, Next.js, and Deno use TypeScript as their primary language. Understanding how TypeScript maps to JavaScript is essential knowledge for any modern web developer.
At its core, every TypeScript program is a JavaScript program with additional type annotations layered on top. This means converting TypeScript to JavaScript is primarily a process of subtraction -- removing the type layer while preserving the runtime logic. The challenge lies in knowing exactly what to remove and what to keep.
While TypeScript is designed to be compiled to JavaScript as part of a build pipeline, there are several practical scenarios where direct conversion is useful.
When sharing code examples in blog posts, documentation, Stack Overflow answers, or tutorials, you may want to present the JavaScript version for a broader audience. Not all developers use TypeScript, and including type annotations can distract from the core logic you are trying to demonstrate. Converting to JavaScript makes the code accessible to everyone.
New TypeScript developers often benefit from seeing what their TypeScript code compiles to. Understanding the relationship between TypeScript constructs and their JavaScript equivalents deepens your understanding of both languages. For example, seeing how an enum becomes an Object.freeze() object or how generic type parameters simply disappear helps build intuition.
When debugging production issues, you sometimes need to read compiled JavaScript output. Understanding how TypeScript transforms your code helps you map stack traces and runtime errors back to your source code. This is especially important when source maps are not available or when debugging in environments that do not support source map resolution.
Sometimes you want to test a code snippet quickly in a browser console or Node.js REPL without setting up a TypeScript build pipeline. Stripping the types from a TypeScript snippet gives you immediately runnable JavaScript. This is faster than configuring tsconfig.json, installing dependencies, and running the compiler.
While uncommon, some teams decide to migrate away from TypeScript back to JavaScript. Reasons include reducing build complexity, eliminating type-related developer friction, or simplifying onboarding for new team members. In these cases, systematic conversion of TypeScript files to JavaScript is required.
Some projects use a mix of TypeScript and JavaScript files. When you need to use a TypeScript module in a JavaScript-only context without a build step (for example, in a legacy system or a simple script), converting the TypeScript module to JavaScript allows direct import without compilation.
Type annotations are the most common TypeScript-specific syntax. They appear on variable declarations, function parameters, return types, and property definitions. Removing them is the first and most frequent step in conversion.
TypeScript allows you to annotate variable declarations with a type using a colon after the variable name:
// TypeScript
const name: string = "Alice";
let count: number = 42;
const isActive: boolean = true;
const items: string[] = ["a", "b", "c"];
const data: Array<number> = [1, 2, 3];
const user: User = { name: "Bob", age: 30 };
// JavaScript (types removed)
const name = "Alice";
let count = 42;
const isActive = true;
const items = ["a", "b", "c"];
const data = [1, 2, 3];
const user = { name: "Bob", age: 30 };
The conversion is straightforward: remove everything between the colon and the equals sign. The runtime behavior is identical because JavaScript variables hold any type regardless of annotations.
Function parameters can have type annotations and optional markers:
// TypeScript
function greet(name: string, age: number): string {
return `Hello, ${name}! You are ${age} years old.`;
}
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
const add = (a: number, b: number): number => a + b;
// JavaScript (types removed)
function greet(name, age) {
return `Hello, ${name}! You are ${age} years old.`;
}
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
const add = (a, b) => a + b;
Each parameter's type annotation (: string, : number) and the function's return type annotation (: string, : Promise<User>) are stripped. Optional parameter markers (?) are also removed since JavaScript functions accept any number of arguments by default.
TypeScript supports union types (A | B) and intersection types (A & B) for more expressive type annotations:
// TypeScript
let value: string | number = "hello";
let data: User & Serializable = { ...user, serialize: () => "{}" };
// JavaScript
let value = "hello";
let data = { ...user, serialize: () => "{}" };
Union and intersection types are purely compile-time constructs. They have no runtime representation and are simply removed during conversion.
Interfaces and type aliases are entirely compile-time constructs in TypeScript. They define the shape of data but produce no JavaScript output whatsoever. During conversion, entire interface and type blocks are removed.
// TypeScript
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
interface ApiResponse<T> {
data: T;
error: string | null;
status: number;
}
// JavaScript
// (Both interfaces are completely removed -- they produce no output)
Interfaces exist solely for the type checker. They tell TypeScript what properties an object should have and what types those properties should be. At runtime, JavaScript objects are dynamic and do not enforce any interface contract, so the interface declarations are simply deleted.
// TypeScript
type ID = string | number;
type UserRole = "admin" | "user" | "guest";
type Callback = (error: Error | null, data: any) => void;
type Partial<T> = { [P in keyof T]?: T[P] };
// JavaScript
// (All type aliases are completely removed)
Type aliases use the type keyword to create named types. Like interfaces, they are purely compile-time and produce no JavaScript output. This includes complex types like mapped types, conditional types, and utility types.
It is important to distinguish interfaces from classes. While interfaces are removed during conversion, classes are valid JavaScript and must be preserved. A class in TypeScript compiles to a JavaScript class (or constructor function in older targets). Only the TypeScript-specific parts of a class (type annotations, access modifiers) are removed -- the class structure itself remains.
Unlike interfaces and type annotations, TypeScript enums produce runtime JavaScript code. They are one of the few TypeScript features that are not purely erased during compilation -- they are transformed into JavaScript objects.
// TypeScript
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING"
}
// JavaScript equivalent
const Status = Object.freeze({
Active: "ACTIVE",
Inactive: "INACTIVE",
Pending: "PENDING"
});
String enums are the simplest case. Each enum member maps to its string value. Using Object.freeze() prevents modification, matching TypeScript's expectation that enum values are immutable.
// TypeScript
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// Official tsc output (with reverse mapping)
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
// Simplified conversion
const Direction = Object.freeze({
Up: 0,
Down: 1,
Left: 2,
Right: 3
});
The official TypeScript compiler creates numeric enums with reverse mapping -- you can look up both the name from the value and the value from the name (Direction[0] === "Up"). The simplified conversion using Object.freeze omits reverse mapping but covers most use cases.
// TypeScript
const enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
const c = Color.Red;
// JavaScript (const enums are inlined)
const c = "RED";
Const enums are completely inlined at compile time. The enum declaration disappears entirely, and every usage is replaced with the literal value. This produces the most efficient JavaScript output but means the enum object does not exist at runtime.
Generics are TypeScript's mechanism for creating reusable components that work with multiple types. Generic type parameters appear inside angle brackets (<T>) and are purely a compile-time concept -- they are completely removed during conversion.
// TypeScript
function identity<T>(value: T): T {
return value;
}
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
return array.map(fn);
}
const result = identity<string>("hello");
// JavaScript
function identity(value) {
return value;
}
function map(array, fn) {
return array.map(fn);
}
const result = identity("hello");
The generic type parameters (<T>, <T, U>) are removed from function declarations. Type arguments at call sites (identity<string>) are also removed. The function logic remains identical because JavaScript functions already accept any type.
// TypeScript
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
// JavaScript
class Stack {
items = [];
push(item) {
this.items.push(item);
}
pop() {
return this.items.pop();
}
}
The class's generic parameter (<T>) and all type annotations within the class are removed. The private access modifier is also removed. The runtime behavior is unchanged.
// TypeScript
function getProperty<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// JavaScript
function getProperty(obj, key) {
return obj[key];
}
Generic constraints (extends object, extends keyof T) and complex type expressions (T[K]) are compile-time only. The JavaScript version is a simple two-parameter function.
TypeScript adds several class features that do not exist in standard JavaScript (or exist in different forms). These include access modifiers, parameter properties, and the readonly modifier.
// TypeScript
class UserService {
private apiUrl: string;
protected cache: Map<string, User>;
public name: string;
constructor(private baseUrl: string) {
this.apiUrl = baseUrl + "/api";
this.cache = new Map();
this.name = "UserService";
}
private fetchData(url: string): Promise<any> {
return fetch(url).then(res => res.json());
}
public getUser(id: number): Promise<User> {
return this.fetchData(`${this.apiUrl}/users/${id}`);
}
}
// JavaScript
class UserService {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.apiUrl = baseUrl + "/api";
this.cache = new Map();
this.name = "UserService";
}
fetchData(url) {
return fetch(url).then(res => res.json());
}
getUser(id) {
return this.fetchData(`${this.apiUrl}/users/${id}`);
}
}
The access modifiers (public, private, protected) are removed. Note the constructor parameter property syntax -- private baseUrl: string in the constructor both declares a parameter and creates a class property. In JavaScript, this must be converted to an explicit assignment: this.baseUrl = baseUrl.
It is worth noting that JavaScript now has native private fields using the # prefix (e.g., #apiUrl), but TypeScript's private keyword does not compile to # fields by default. TypeScript private is enforced only at compile time, while JavaScript # fields are enforced at runtime.
// TypeScript
class Config {
readonly maxRetries: number = 3;
readonly apiUrl: string;
constructor(url: string) {
this.apiUrl = url;
}
}
// JavaScript
class Config {
maxRetries = 3;
constructor(url) {
this.apiUrl = url;
}
}
The readonly modifier is removed. In TypeScript, readonly prevents reassignment after initialization. JavaScript has no direct equivalent for class properties (Object.freeze() works on objects but not individual properties). The runtime behavior differs -- without readonly, the property can be reassigned.
TypeScript provides two forms of type assertions and the non-null assertion operator. These tell the type checker to treat a value as a specific type without actually performing any runtime conversion.
// TypeScript
const element = document.getElementById("app") as HTMLDivElement;
const data = response.json() as Promise<User>;
const value = someValue as unknown as string;
// JavaScript
const element = document.getElementById("app");
const data = response.json();
const value = someValue;
The as Type syntax is purely a compile-time hint. It does not perform any runtime conversion or validation. The value is the same at runtime regardless of the assertion. During conversion, the as Type clause is simply removed.
// TypeScript (older syntax, not usable in JSX)
const element = <HTMLDivElement>document.getElementById("app");
// JavaScript
const element = document.getElementById("app");
The angle-bracket assertion syntax is an older form that predates the as keyword. It is less common in modern TypeScript because it conflicts with JSX syntax. Like as assertions, it is removed during conversion.
// TypeScript
const element = document.getElementById("app")!;
const name = user.profile!.name;
const first = items[0]!;
// JavaScript
const element = document.getElementById("app");
const name = user.profile.name;
const first = items[0];
The non-null assertion operator (!) tells TypeScript that a value is definitely not null or undefined, even though the type system thinks it might be. At runtime, it does nothing -- the value could still be null. Removing the ! during conversion has no effect on runtime behavior, but it means you lose the compile-time safety guarantee.
Several tools can convert TypeScript to JavaScript, ranging from the official compiler to lightweight type strippers.
The official TypeScript compiler (tsc) is the gold standard. It performs full type checking before emitting JavaScript:
npx tsc --outDir dist src/*.ts
# Or with a tsconfig.json
npx tsc
Advantages: full type checking, complete coverage of all TypeScript features, configurable output target (ES5/ES6/ESNext), source map support. Disadvantages: slower than alternatives because it performs full type analysis.
esbuild is an extremely fast JavaScript bundler written in Go. It strips TypeScript types without performing type checking:
npx esbuild src/app.ts --outfile=dist/app.js
# With bundling
npx esbuild src/app.ts --bundle --outfile=dist/bundle.js
esbuild is 10-100x faster than tsc because it only performs type stripping, not type checking. This makes it ideal for development servers and build pipelines where type checking is handled separately (e.g., in CI).
swc (Speedy Web Compiler) is a Rust-based JavaScript/TypeScript compiler:
npx swc src/app.ts -o dist/app.js
Like esbuild, swc focuses on speed by stripping types without checking them. It is used by Next.js, Deno, and other major tools as their TypeScript compilation backend.
Babel can strip TypeScript types using the @babel/plugin-transform-typescript or @babel/preset-typescript:
npm install --save-dev @babel/preset-typescript
// babel.config.json
{
"presets": ["@babel/preset-typescript"]
}
This integrates TypeScript stripping into existing Babel pipelines. Like esbuild and swc, it does not perform type checking.
For quick one-off conversions, online tools like our TypeScript to JavaScript Converter provide instant conversion without installing any tools. These are ideal for sharing code snippets, quick debugging, or learning.
When converting TypeScript to JavaScript, keep these best practices in mind to ensure a smooth transition.
Most TypeScript features are purely compile-time and their removal does not change runtime behavior. However, a few features do affect runtime: enums produce JavaScript objects, parameter properties create class fields, and decorators (experimental) generate wrapper code. Understanding which features are compile-time only and which produce runtime code helps you predict the conversion output.
Always test your JavaScript output after conversion. While type stripping should not change runtime behavior in theory, edge cases and complex patterns can sometimes produce unexpected results. Run your test suite against the JavaScript output to verify correctness.
If your code uses enums, verify that the converted JavaScript objects behave correctly in all usage patterns. Reverse mapping for numeric enums, enum member iteration, and enum comparison should all work as expected. Consider using Object.freeze() to preserve immutability.
When migrating from TypeScript, consider using native JavaScript features as replacements: use # prefix for private fields instead of the private keyword, use JSDoc comments for type hints, and use Object.freeze() for enum-like constants.
If you are removing TypeScript from a codebase, consider adding JSDoc type comments as a lightweight alternative. JSDoc provides type hints that editors like VS Code can use for IntelliSense without requiring a TypeScript build step:
/**
* @param {string} name
* @param {number} age
* @returns {string}
*/
function greet(name, age) {
return `Hello, ${name}! You are ${age} years old.`;
}
This approach gives you some of TypeScript's benefits (editor autocompletion, type checking with // @ts-check) while keeping your code as pure JavaScript.
For production builds, use established tools like tsc, esbuild, or swc. For quick conversions and learning, use online tools. For migration projects, combine automated conversion with manual review. No single approach fits all scenarios.
Try our free converter to strip TypeScript types and produce clean JavaScript in seconds. No setup required -- paste your code and get the result immediately.
Try the TypeScript to JavaScript ConverterMaster JSON formatting, validation, and best practices for working with JSON data.
Learn JavaScript minification techniques, tools, and best practices for production optimization.
Turn code snippets into stunning PNG images with customizable themes and backgrounds.