Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Compare
  3. JavaScript vs TypeScript
  4. JavaScript to TypeScript Types

JavaScript to TypeScript Types

How TypeScript's type system formalizes patterns that JavaScript developers already use informally through duck typing and typeof checks.

Duck Typing vs Structural Typing

JavaScript uses duck typing at runtime: if an object has a .quack() method, you can call it regardless of how the object was constructed. TypeScript formalizes this with structural typing: if two types have the same shape (same properties and types), they are compatible even if they have different names. This is different from nominal typing (used by Java and C#), where two classes are only compatible if they share an inheritance relationship. Structural typing means TypeScript matches how JavaScript developers already think about objects. You pass objects to functions based on what properties they have, not what class they belong to. The migration benefit is significant: you do not need to refactor class hierarchies or implement interfaces to make existing code typecheck. If your JavaScript code passes an object with { name: string, age: number } to a function, TypeScript accepts any object with those properties. Extra properties are allowed when passing object references (but not when creating object literals inline, where excess property checks apply). This "shapes match shapes" approach makes TypeScript feel like JavaScript with guardrails rather than a different language.

Structural compatibility

JavaScript
function greet(user) {
  return "Hello " + user.name;
}
// works with any object that has .name
greet({ name: "Ada", age: 30 });
greet({ name: "Grace" });
TypeScript
function greet(user: { name: string }) {
  return `Hello ${user.name}`;
}
// works with any object that has .name
greet({ name: "Ada", age: 30 }); // excess OK via variable
const u = { name: "Ada", age: 30 };
greet(u); // 

Type Aliases

JavaScript has no type alias syntax -- you document object shapes in JSDoc comments or not at all. TypeScript introduces the type keyword: type User = { name: string; age: number }. A type alias is a name for any type expression, from primitives (type ID = string) to complex intersections and conditional types. Aliases do not create new runtime entities; they are purely a compile-time concept. The migration step is straightforward: identify the shapes your JavaScript code passes around and give them names. This alone improves readability because parameter types like (user: User) replace (user: any) or (user: { name: string, age: number, email: string, ... }). Type aliases also compose. You build larger types from smaller ones using intersection (&): type Admin = User & { permissions: string[] }. This additive composition mirrors how JavaScript developers extend objects with spread: { ...user, permissions: [...] }, but at the type level. Unlike interfaces (covered in the next topic), type aliases can represent unions, primitives, tuples, and mapped types -- making them the more general-purpose tool.

Type alias

JavaScript
// JSDoc (optional, unenforceable)
/** @typedef {{ name: string, age: number }} User */

const user = { name: "Ada", age: 30 };
TypeScript
type User = {
  name: string;
  age: number;
};

const user: User = { name: "Ada", age: 30 };

Composing types with intersection

JavaScript
const admin = {
  ...user,
  permissions: ["read", "write"],
};
TypeScript
type Admin = User & {
  permissions: string[];
};
const admin: Admin = {
  ...user,
  permissions: ["read", "write"],
};

Union Types and Literal Types

JavaScript developers handle multiple possible types with runtime checks: if (typeof input === "string"). TypeScript captures this pattern with union types: string | number means the value is one or the other. The compiler tracks which type is active based on control flow: after if (typeof input === "string"), the variable narrows to string inside the block. This is called control flow narrowing, and it makes union types practical rather than theoretical. Literal types are a refinement: instead of string, you specify exactly which strings are valid: type Direction = "north" | "south" | "east" | "west". Passing "northeast" triggers a compile error. JavaScript developers already use string constants this way (if (dir === "north")), but TypeScript turns those constants into a type that the compiler enforces. The combination of unions and literals replaces many uses of enums. Where JavaScript might define const DIRECTIONS = { NORTH: "north", SOUTH: "south" }, TypeScript can define type Direction = "north" | "south" and get compile-time checking without a runtime object. Both approaches work in TypeScript, but string literal unions are lighter weight and produce cleaner .d.ts files.

Union type with narrowing

JavaScript
function format(input) {
  if (typeof input === "string") {
    return input.toUpperCase();
  }
  return input.toFixed(2);
}
TypeScript
function format(input: string | number) {
  if (typeof input === "string") {
    return input.toUpperCase(); // narrowed to string
  }
  return input.toFixed(2); // narrowed to number
}

Literal types

JavaScript
function move(direction) {
  // no compile-time check on direction
  if (direction === "north") { /* ... */ }
}
move("nroth"); // typo, no error
TypeScript
type Direction = "north" | "south" | "east" | "west";
function move(direction: Direction) {
  if (direction === "north") { /* ... */ }
}
// move("nroth"); // compile error: typo caught
Warm-up1 / 1

Can you write this from memory?

Declare `id` that can be string or number

TypeScript: TypeScript Union & Intersection Types →

typeof, keyof, and Type Narrowing

JavaScript's typeof operator returns a runtime string ("string", "number", "object", etc.). TypeScript reuses typeof at the type level: type T = typeof myVariable extracts the type that the compiler inferred for myVariable. This is useful when you want to derive a type from an existing value without manually rewriting the shape. keyof extracts the keys of an object type as a union of string literals: keyof User produces "name" | "age". Combined with indexed access types (User["name"] resolves to string), keyof enables generic property access functions: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]. JavaScript developers write the same function without generics, but the TypeScript version catches typos in key names at compile time. Type narrowing goes beyond typeof. TypeScript narrows on instanceof, in checks ("name" in obj), equality checks, and custom type guard functions (function isUser(x: unknown): x is User). Each narrowing mechanism corresponds to a runtime check that JavaScript developers already write -- TypeScript just tracks the implications through the rest of the code block.

typeof at the type level

JavaScript
const config = {
  host: "localhost",
  port: 5432,
};
// no way to extract the type statically
TypeScript
const config = {
  host: "localhost",
  port: 5432,
} as const;

type Config = typeof config;
// { readonly host: "localhost"; readonly port: 5432 }

keyof and generic property access

JavaScript
function getProperty(obj, key) {
  return obj[key]; // no safety
}
TypeScript
function getProperty<T, K extends keyof T>(
  obj: T, key: K
): T[K] {
  return obj[key]; // type-safe
}

Practice Both Languages

10 free exercises a day. No credit card required. Build syntax muscle memory with spaced repetition.

Free forever. No credit card required.

← Back to JavaScript vs TypeScript
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.