Skip to main content

Introduction to TypeScript Playground

You hit a tricky type error, strip your code down to a minimal example, and want to share it with a teammate or post it to Stack Overflow. Setting up a whole repo just to demonstrate 20 lines of TypeScript is overkill. That's where the TypeScript Playground comes in.

It's a browser-based editor maintained by the TypeScript team where you can write, run, and share TypeScript without installing anything. No npm install, no tsconfig.json, no local setup. Just open the URL and start typing.

const greeting: string = "Hello, TypeScript Playground!";
console.log(greeting);

You'll use it constantly, whether you're learning a new TypeScript feature, isolating a bug, or sharing a reproducible example. Let's look at everything it can do.

When you first open the playground, the layout is split into two panes: the editor on the left and the output panel on the right. The right panel has several tabs worth knowing about.

JS Output shows the compiled JavaScript. This is useful for understanding how TypeScript features like decorators, enums, or async/await get transpiled, especially when targeting older ECMAScript versions.

DTS shows the generated .d.ts declaration file for your code. If you're building a library, this tab tells you what types you're actually exposing to consumers.

Errors lists all type errors and compiler diagnostics in one place. When you have a lot of code, this is faster than scanning the editor for red underlines.

Plugins lets you install community-built playground extensions. The TypeScript team has an official plugin API, so you can add tools for AST visualization, Twoslash annotations, and more.

At the top, the Config menu is where you control compiler options, and the Examples menu gives you curated snippets for TypeScript features.

Exploring TypeScript Features in the Playground

What makes the playground useful for learning isn't just that you can run code. It's that the editor shows you what TypeScript is thinking in real time. Hover over any expression and you'll see its inferred type in a tooltip. Introduce a type mismatch and the red underline appears immediately, before you run anything.

Here's a scenario that shows all of this at once. Try this in the playground and hover over each variable to watch the types narrow as you move through the conditionals:

type ApiStatus = "loading" | "success" | "error";

interface ApiResult {
status: ApiStatus;
data?: string;
retryAfter?: number;
}

function handleResult(result: ApiResult) {
// Hover over result.status: ApiStatus
if (result.status === "success") {
// Hover over result.data here: string | undefined
console.log(result.data?.toUpperCase());
} else if (result.status === "error") {
// Hover over result.retryAfter: number | undefined
const delay = result.retryAfter ?? 5000;
console.log(`Retrying in ${delay}ms`);
}
}

// Try passing "pending" here and watch the Errors tab light up
handleResult({ status: "loading" });

The Errors tab catches the invalid value before you run a line. The hover tooltips show you the narrowed type inside each branch. This feedback loop is what makes the playground so effective for learning how TypeScript union types and type narrowing actually work.

Testing TypeScript Code Online

The playground is great for quick prototyping without spinning up a local environment. Check out our TypeScript best practices when testing Convex functions.

Starting a New Playground

Visit the TypeScript Playground and start typing. The Examples menu has templates if you want a starting point. Here's a realistic snippet to try out, a typed shopping cart calculator:

interface CartItem {
name: string;
price: number;
quantity: number;
}

function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}

const cart: CartItem[] = [
{ name: "Keyboard", price: 89, quantity: 1 },
{ name: "USB Hub", price: 29, quantity: 2 },
];

console.log(`Total: $${calculateTotal(cart)}`); // Total: $147

Using npm Packages in the Playground

Most developers don't realize you can import npm packages directly in the playground. The playground resolves bare module specifiers from unpkg.com automatically, so no install step is needed.

import _ from "lodash";

const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 25 },
];

const grouped = _.groupBy(users, "age");
console.log(grouped);

The playground also pulls in @types/* packages automatically for type checking, so you'll get full IntelliSense on third-party libraries.

To pin a specific package version, add a package.json file to your playground project with a dependencies field:

{
"dependencies": {
"lodash": "4.17.21"
}
}

This makes the playground much more useful for testing real integration code, not just isolated type puzzles.

Sharing Code Examples

The TypeScript Playground makes sharing simple. You can create a shareable link or export your code as a TypeScript file.

Click the Share button and copy the link. The playground encodes your code, compiler settings, and tab state into the URL so the recipient sees exactly what you see. Learn how we handle TypeScript at Convex to write more shareable code.

// A reusable, shareable generic sort utility
function sortByProperty<T>(array: T[], property: keyof T): T[] {
return [...array].sort((a, b) =>
a[property] < b[property] ? -1 :
a[property] > b[property] ? 1 : 0
);
}

const users = [
{ name: "Charlie", age: 30 },
{ name: "Alice", age: 25 },
{ name: "Bob", age: 35 },
];

console.log(sortByProperty(users, "name"));
console.log(sortByProperty(users, "age"));

Creating Minimal Reproductions for Bug Reports

When you're asking for help on Stack Overflow, GitHub, or in a team Slack channel, a playground link is worth a thousand words. The key is keeping the reproduction minimal: strip out everything that isn't directly related to the problem.

Here's what a good minimal reproduction looks like:

// Demonstrating a narrowing issue with discriminated unions
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number };

function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
}
// TypeScript correctly narrows to rectangle here
return shape.width * shape.height;
}

Keep it under 30-40 lines, include only the types and logic that demonstrate the issue, and make sure the error is visible in the Errors tab. A focused playground link gets answers much faster than a wall of production code.

Debugging Code Snippets

The playground shows type errors inline as you type, but you can also step through runtime behavior using the browser's built-in dev tools. Open the Console tab in DevTools alongside the playground to inspect console.log output in real time.

Using the Debugger

Set breakpoints by clicking line numbers in the JS Output tab (not the TypeScript editor), then use the debug controls in your browser's DevTools to step through the compiled JavaScript. This helps when you want to verify that runtime behavior matches your type assumptions.

class TaskManager {
private tasks: string[] = [];

addTask(task: string): number {
this.tasks.push(task);
return this.tasks.length;
}

getTask(index: number): string | undefined {
return this.tasks[index];
}

listTasks(): string[] {
return [...this.tasks];
}
}

const manager = new TaskManager();
manager.addTask("Write documentation");
manager.addTask("Review PR");
console.log(manager.listTasks());

Experimenting with Compiler Options Live

The real value of the playground's Config menu isn't reading about what each option does. It's being able to flip a switch and immediately see what changes. That's a different experience from editing a tsconfig.json locally and rerunning the compiler.

Live Experiments Worth Trying

Toggle strict mode on and off. Paste in a function with an optional property and watch errors appear and disappear as you flip strict:

function getUserName(user: { name?: string }): string {
// With strict on: "Object is possibly undefined"
// With strict off: no error, but now a runtime risk
return user.name.toUpperCase();
}

// The fix TypeScript nudges you toward:
function getUserNameSafe(user: { name?: string }): string {
return user.name?.toUpperCase() ?? "Anonymous";
}

Change the compilation target and read the JS Output tab. Set target to ES5 and watch optional chaining (?.) and nullish coalescing (??) get transformed into verbose if checks. Set it to ESNext and the output is nearly identical to your source. This is the fastest way to understand what TypeScript is actually generating for older environments.

Switch module systems. Toggle between CommonJS and ESNext in the Config menu and check the JS Output tab. You'll see import statements become require() calls instantly. Handy when you're debugging a module resolution issue and want to understand what the compiler is emitting.

Testing JavaScript with JSDoc Types

Most articles about converting JavaScript to TypeScript focus on renaming files and adding annotations. The playground lets you do something more interesting: type-check JavaScript files without converting them at all.

In the Config menu, switch the file language from TypeScript to JavaScript. Now you're working in a .js file, but the playground still runs type inference based on how you use your code. Add JSDoc comments and TypeScript will enforce them:

/**
* @param {number} id
* @returns {Promise<{id: number, name: string, email: string}>}
*/
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch user ${id}`);
}
return response.json();
}

// The playground catches this even in JS mode:
fetchUser("not-a-number"); // Argument of type 'string' is not assignable to 'number'

Try it when you're working with a legacy JavaScript codebase and want to understand what adding JSDoc types would actually give you before committing to a full .ts migration. You can even paste the same logic in both modes and compare the feedback side by side.

Visualizing Type Checking

The fastest way to build intuition for TypeScript's type system is to poke at it. Write something invalid and watch the compiler react. Hover over a generic and see what TypeScript infers. The playground makes this frictionless.

Understanding Type Checking in Practice

Try this: call a generic function with the wrong type argument and watch the error appear before you run anything.

interface ApiResponse<T> {
data: T;
status: number;
error?: string;
}

function parseResponse<T>(response: ApiResponse<T>): T {
if (response.error) {
throw new Error(response.error);
}
return response.data;
}

// Hover over result: TypeScript infers the type from the generic
const result = parseResponse<{ userId: number }>({
data: { userId: 42 },
status: 200,
});

// Try assigning a wrong type and watch the error appear immediately
// const bad = parseResponse<string>({ data: 123, status: 200 });

TypeScript Playground vs. Other Online Editors

The official playground isn't always the right tool. Here's when to use it and when to reach for something else.

ToolBest forFull project supportnpm support
TypeScript PlaygroundType exploration, bug reports, sharing snippetsNoLimited
StackBlitzFull Node.js projects, backend frameworksYesYes
CodeSandboxFrontend projects, React/Vue/Svelte appsYesYes
PlayCodeFast prototyping with live previewPartialYes

Use the TypeScript Playground when you need a single-file, zero-config environment to test a type, share a snippet, or create a minimal reproduction. Reach for StackBlitz or CodeSandbox when you need a full project with multiple files, a build tool, or a framework.

Get More Out of the Playground in 10 Minutes

Most developers use the TypeScript Playground as a scratch pad and stop there. Here's what else is worth exploring:

  • Open the JS Output tab and change the compilation target. Watch ?. and ?? turn into multi-line if checks when you target ES5. This builds an intuition for what TypeScript is actually doing under the hood.
  • Import a package with a bare specifier (import _ from "lodash") and hover over the imported functions. The playground pulls in type definitions automatically.
  • Hit the Share button before you close a session. The URL encodes your code and compiler settings, so it's a lossless snapshot. Paste it anywhere you'd otherwise describe a type problem in prose.
  • Switch the language to JavaScript in the Config menu and add a JSDoc @param comment. You'll see the playground enforce it just like a TypeScript annotation.
  • Open the Errors tab when your editor has a lot of red. It lists every diagnostic in one place, which is faster than hunting through the editor for squiggles.

These aren't obscure features. They're just the ones that make the playground useful beyond a first read.