# Convert Python Typescript

> Bidirectional conversion between Python and Typescript. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Python↔Typescript specific patterns. Use when migrating Python projects to TypeScript, translating Pythonic patterns to TypeScript idioms, or refactoring Python codebases into TypeScript. Extends meta-convert-dev with Python-to-TypeScript specific patterns.

- Skill: `arustydev/convert-python-typescript` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arustydev/convert-python-typescript`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arustydev/convert-python-typescript/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aRustyDev (https://skillmd.com/u/arustydev)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/arustydev/convert-python-typescript

---


# Python ↔ Typescript Conversion

Bidirectional conversion between Python and Typescript. This skill extends `meta-convert-dev` with Python↔Typescript specific type mappings, idiom translations, and tooling.

## This Skill Extends

- `meta-convert-dev` - Foundational conversion patterns (APTV workflow, testing strategies)

For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.

## This Skill Adds

- **Type mappings**: Python types → TypeScript types (with runtime to static typing)
- **Idiom translations**: Pythonic patterns → TypeScript idioms
- **Error handling**: Python exceptions → TypeScript error patterns
- **Async patterns**: asyncio/async-await → Promise/async-await
- **Type system differences**: Dynamic duck typing → static structural typing

## This Skill Does NOT Cover

- General conversion methodology - see `meta-convert-dev`
- Python language fundamentals - see `lang-python-dev`
- TypeScript language fundamentals - see `lang-typescript-patterns-dev`

---

## Quick Reference

| Python | TypeScript | Notes |
|--------|------------|-------|
| `str` | `string` | Unicode strings |
| `int` | `number` / `bigint` | Regular numbers or large integers |
| `float` | `number` | Floating point |
| `bool` | `boolean` | Same concept |
| `list[T]` | `T[]` / `Array<T>` | Mutable array |
| `tuple[T, U]` | `[T, U]` / `readonly [T, U]` | Fixed-size tuple |
| `dict[K, V]` | `Record<K, V>` / `Map<K, V>` | Key-value mapping |
| `T \| None` / `Optional[T]` | `T \| null` / `T \| undefined` | Nullable types |
| `Coroutine[Any, Any, T]` | `Promise<T>` | Async operations |
| `class X` / `Protocol` | `interface X` / `type X` | Structure definition |
| `Enum` / `Literal` | `enum X` / `const` object | Enumerated values |

## When Converting Code

1. **Analyze source thoroughly** before writing TypeScript
2. **Map types first** - Python type hints → TypeScript types
3. **Preserve semantics** over syntax similarity
4. **Adopt TypeScript idioms** - don't write "Python code in TypeScript syntax"
5. **Handle edge cases** - None → null/undefined, falsy values
6. **Test equivalence** - same inputs → same outputs

---

## Type System Mapping

### Primitive Types

| Python | TypeScript | Notes |
|--------|------------|-------|
| `str` | `string` | UTF-16 in TS, but similar usage |
| `int` | `number` | For safe integers (-2^53 to 2^53) |
| `int` | `bigint` | For arbitrary precision (use when needed) |
| `float` | `number` | IEEE 754 double precision |
| `bool` | `boolean` | Lowercase (true/false) in TS |
| `None` | `null` | Explicit null |
| `None` | `undefined` | Implicit absence (default) |
| - | `void` | Function return type (no value) |
| `Any` | `any` | Escape hatch (avoid when possible) |
| `object` | `unknown` | Type-safe any |
| `NoReturn` | `never` | Function never returns |

### Collection Types

| Python | TypeScript | Notes |
|--------|------------|-------|
| `list[T]` | `T[]` / `Array<T>` | Mutable, ordered |
| `tuple[T, ...]` | `readonly T[]` | Immutable sequence |
| `tuple[T, U]` | `[T, U]` | Fixed-size tuple |
| `set[T]` | `Set<T>` | Unique values, unordered |
| `dict[K, V]` | `Map<K, V>` | Key-value with object keys |
| `dict[str, V]` | `Record<string, V>` | String-keyed object |
| `dict[str, V]` | `{ [key: string]: V }` | Index signature |
| `frozenset[T]` | `ReadonlySet<T>` | Immutable set |
| `Mapping[K, V]` | `ReadonlyMap<K, V>` | Immutable map view |

### Composite Types

| Python | TypeScript | Notes |
|--------|------------|-------|
| `class X:` | `interface X { ... }` | Data structure |
| `class X:` | `class X { ... }` | With methods/behavior |
| `Protocol` | `interface X { ... }` | Structural typing |
| `dataclass` | `interface X { ... }` | Data-only classes |
| `TypedDict` | `interface X { ... }` | Typed dict structure |
| `T \| U` | `T \| U` | Union types (3.10+) |
| `Union[T, U]` | `T \| U` | Union types (pre-3.10) |
| - | `T & U` | Intersection (combine fields) |
| `Optional[T]` | `T \| null` / `T \| undefined` | Nullable |
| `Literal["a", "b"]` | `"a" \| "b"` | String literal union |

### Generic Types

| Python | TypeScript | Notes |
|--------|------------|-------|
| `[T]` (3.12+) / `TypeVar('T')` | `<T>` | Generic type parameter |
| `TypeVar('T', bound=U)` | `<T extends U>` | Bounded type variable |
| `TypeVar('T', A, B)` | `T extends A \| B` | Constrained type |
| `Callable[[A, B], R]` | `(a: A, b: B) => R` | Function signature |
| `Callable[..., R]` | `(...args: any[]) => R` | Variadic function |
| `Type[T]` | `typeof T` | Type of class |
| - | `keyof T` | Union of property keys |
| - | `T[K]` | Index access type |

---

## Module System Translation

Python uses a straightforward import system, while TypeScript has ES modules with explicit imports/exports.

### Import and Export Patterns

**Python:**
```python
# math_utils.py - Python module
def add(a: float, b: float) -> float:
    return a + b

def multiply(a: float, b: float) -> float:
    return a * b

# Explicit public API (optional)
__all__ = ['add', 'multiply']

# Private function (convention)
def _internal_helper():
    pass
```

**TypeScript:**
```typescript
// mathUtils.ts - TypeScript module
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

// Private function (not exported)
function internalHelper() {
  // ...
}

// Default export (Python has no equivalent)
export default class Calculator {
  // ...
}
```

### Import Patterns

**Python:**
```python
# Named imports
from math_utils import add, multiply

# Module import
import math_utils

# Import everything (not recommended)
from math_utils import *

# Import with alias
from math_utils import add as addition

# Relative imports
from . import sibling_module
from .. import parent_module
from ..utils import helper
```

**TypeScript:**
```typescript
// Named imports
import { add, multiply } from './mathUtils';

// Default import
import Calculator from './mathUtils';

// Import with alias
import { add as addition } from './mathUtils';

// Namespace import
import * as MathUtils from './mathUtils';

// Side-effect only import
import './polyfills';

// Type-only imports (erased at runtime)
import type { User, Config } from './types';
```

### Package Structure

**Python:**
```python
# mypackage/
# ├── __init__.py           # Package initialization
# ├── core.py
# └── utils.py

# mypackage/__init__.py
"""Package docstring."""

__version__ = "1.0.0"
__all__ = ["CoreClass", "utility_function"]

from .core import CoreClass
from .utils import utility_function
```

**TypeScript:**
```typescript
// mypackage/
// ├── index.ts              // Barrel export (like __init__.py)
// ├── core.ts
// └── utils.ts

// mypackage/index.ts
export { CoreClass } from './core';
export { utilityFunction } from './utils';
export type { Config } from './types';

// Or re-export all
export * from './core';
export * from './utils';
```

### Dynamic Imports

**Python:**
```python
# Dynamic import
import importlib

module_name = "json"
json_module = importlib.import_module(module_name)

# Lazy imports
def expensive_operation():
    import heavy_module  # Only loaded when function called
    return heavy_module.process()

# Conditional imports (type checking)
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from expensive_module import ExpensiveClass
```

**TypeScript:**
```typescript
// Dynamic import (returns Promise)
async function loadModule() {
  const module = await import('./heavyModule');
  return module.process();
}

// Conditional type import
import type { ExpensiveClass } from './expensiveModule';

// Dynamic import with type
type MathUtils = typeof import('./mathUtils');
```

---

## Error Handling Translation

### Python Exception Model → TypeScript Error Patterns

Both languages use exception-based error handling, but TypeScript also supports Result-like patterns.

| Aspect | Python | TypeScript |
|--------|--------|------------|
| Base class | `Exception` | `Error` |
| Try-catch | `try/except/else/finally` | `try/catch/finally` |
| Throwing | `raise Exception()` | `throw new Error()` |
| Re-throwing | `raise` (without args) | `throw` (without args) |
| Type checking | `isinstance()` or specific except | `instanceof` |

### Exception Translation

**Python:**
```python
class AppError(Exception):
    """Base exception for application errors."""
    def __init__(self, message: str, code: str):
        super().__init__(message)
        self.code = code

class NotFoundError(AppError):
    """Raised when a resource is not found."""
    def __init__(self, resource: str):
        super().__init__(f"{resource} not found", "NOT_FOUND")
        self.resource = resource

class ValidationError(AppError):
    """Raised when validation fails."""
    def __init__(self, message: str, errors: list[str]):
        super().__init__(message, "VALIDATION_ERROR")
        self.errors = errors
```

**TypeScript:**
```typescript
class AppError extends Error {
  constructor(message: string, public code: string) {
    super(message);
    this.name = this.constructor.name;
    // Maintain proper prototype chain
    Object.setPrototypeOf(this, new.target.prototype);
  }
}

class NotFoundError extends AppError {
  constructor(public resource: string) {
    super(`${resource} not found`, "NOT_FOUND");
  }
}

class ValidationError extends AppError {
  constructor(message: string, public errors: string[]) {
    super(message, "VALIDATION_ERROR");
  }
}
```

### Error Handling Patterns

**Python:**
```python
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            content = f.read()
        data = json.loads(content)
        return Config(**data)
    except FileNotFoundError:
        raise NotFoundError(path) from None
    except json.JSONDecodeError as e:
        raise ValidationError(f"Invalid JSON in {path}") from e
    except Exception:
        # Re-raise unexpected errors
        raise

# Usage
try:
    config = load_config("config.json")
except NotFoundError as e:
    print(f"Config not found: {e.resource}")
except ValidationError as e:
    print(f"Invalid config: {e.errors}")
```

**TypeScript:**
```typescript
function loadConfig(path: string): Config {
  try {
    const content = fs.readFileSync(path, 'utf-8');
    const data = JSON.parse(content);
    return data as Config;
  } catch (error) {
    if (error.code === 'ENOENT') {
      throw new NotFoundError(path);
    } else if (error instanceof SyntaxError) {
      throw new ValidationError(`Invalid JSON in ${path}`, [error.message]);
    } else {
      // Re-throw unexpected errors
      throw error;
    }
  }
}

// Usage
try {
  const config = loadConfig('config.json');
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error(`Config not found: ${error.resource}`);
  } else if (error instanceof ValidationError) {
    console.error(`Invalid config: ${error.errors}`);
  } else {
    throw error;
  }
}
```

### Result Pattern (Alternative to Exceptions)

**Python:**
```python
from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')
E = TypeVar('E')

@dataclass
class Ok(Generic[T]):
    value: T

@dataclass
class Err(Generic[E]):
    error: E

Result = Ok[T] | Err[E]

def divide(a: float, b: float) -> Result[float, str]:
    if b == 0:
        return Err("Division by zero")
    return Ok(a / b)

# Usage
result = divide(10, 2)
match result:
    case Ok(value):
        print(f"Result: {value}")
    case Err(error):
        print(f"Error: {error}")
```

**TypeScript:**
```typescript
// Result type pattern
type Result<T, E> =
  | { success: true; data: T }
  | { success: false; error: E };

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { success: false, error: "Division by zero" };
  }
  return { success: true, data: a / b };
}

// Usage
const result = divide(10, 2);
if (result.success) {
  console.log(`Result: ${result.data}`);
} else {
  console.error(`Error: ${result.error}`);
}
```

---

## Concurrency Patterns

### Python Async/Await → TypeScript Promise/Async-Await

Both languages support async/await, but with different runtimes and patterns.

| Aspect | Python | TypeScript |
|--------|--------|------------|
| Runtime | asyncio event loop (explicit) | V8 event loop (built-in) |
| Promise/Future | `Coroutine[Any, Any, T]` | `Promise<T>` |
| Concurrent | `asyncio.gather()` | `Promise.all()` |
| Race | `asyncio.wait(..., FIRST_COMPLETED)` | `Promise.race()` |
| Timeout | `asyncio.wait_for()` | `Promise.race()` + timeout |

### Basic Async Functions

**Python:**
```python
import asyncio
import aiohttp

async def fetch_user(id: str) -> User:
    async with aiohttp.ClientSession() as session:
        async with session.get(f'/api/users/{id}') as response:
            if not response.ok:
                raise Exception(f"Failed to fetch user {id}")
            data = await response.json()
            return User(**data)

# Entry point requires event loop
async def main():
    user = await fetch_user('123')
    print(user.name)

if __name__ == '__main__':
    asyncio.run(main())
```

**TypeScript:**
```typescript
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);

  if (!response.ok) {
    throw new Error(`Failed to fetch user ${id}`);
  }

  return await response.json();
}

// Can await at top level (in modules)
const user = await fetchUser('123');
console.log(user.name);

// Or in async function
async function main() {
  const user = await fetchUser('123');
  console.log(user.name);
}

main();
```

### Concurrent Execution

**Python:**
```python
# Sequential (slow)
user1 = await fetch_user('1')
user2 = await fetch_user('2')

# Concurrent with gather
users = await asyncio.gather(
    fetch_user('1'),
    fetch_user('2'),
    fetch_user('3')
)

# With TaskGroup (Python 3.11+)
async with asyncio.TaskGroup() as tg:
    task1 = tg.create_task(fetch_user('1'))
    task2 = tg.create_task(fetch_user('2'))
    task3 = tg.create_task(fetch_user('3'))

users = [task1.result(), task2.result(), task3.result()]

# Handle errors separately
results = await asyncio.gather(
    fetch_user('1'),
    fetch_user('2'),
    return_exceptions=True
)
for result in results:
    if isinstance(result, Exception):
        print(f"Error: {result}")
```

**TypeScript:**
```typescript
// Sequential (slow)
const user1 = await fetchUser('1');
const user2 = await fetchUser('2');

// Concurrent with Promise.all
const users = await Promise.all([
  fetchUser('1'),
  fetchUser('2'),
  fetchUser('3')
]);

// Handle errors separately with allSettled
const results = await Promise.allSettled([
  fetchUser('1'),
  fetchUser('2'),
  fetchUser('3')
]);

for (const result of results) {
  if (result.status === 'fulfilled') {
    console.log('User:', result.value);
  } else {
    console.error('Error:', result.reason);
  }
}
```

### Async Iteration

**Python:**
```python
from typing import AsyncIterator

async def generate_pages(start: int, end: int) -> AsyncIterator[Page]:
    for i in range(start, end + 1):
        yield await fetch_page(i)

# Async iteration
async for page in generate_pages(1, 10):
    process(page)

# Async comprehension
pages = [page async for page in generate_pages(1, 10)]
```

**TypeScript:**
```typescript
async function* generatePages(start: number, end: number): AsyncIterable<Page> {
  for (let i = start; i <= end; i++) {
    yield await fetchPage(i);
  }
}

// Async iteration
for await (const page of generatePages(1, 10)) {
  process(page);
}

// Convert to array
const pages: Page[] = [];
for await (const page of generatePages(1, 10)) {
  pages.push(page);
}
```

### Timeout and Cancellation

**Python:**
```python
# Timeout with wait_for
try:
    user = await asyncio.wait_for(fetch_user('1'), timeout=5.0)
except asyncio.TimeoutError:
    print("Request timed out")

# Timeout with timeout context manager (3.11+)
try:
    async with asyncio.timeout(5.0):
        user = await fetch_user('1')
except asyncio.TimeoutError:
    print("Request timed out")

# Cancellation
task = asyncio.create_task(fetch_user('1'))
# Later...
task.cancel()
try:
    await task
except asyncio.CancelledError:
    print("Task was cancelled")
```

**TypeScript:**
```typescript
// Timeout with Promise.race
async function fetchWithTimeout<T>(
  promise: Promise<T>,
  ms: number
): Promise<T> {
  const timeout = new Promise<never>((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
}

try {
  const user = await fetchWithTimeout(fetchUser('1'), 5000);
} catch (error) {
  if (error.message === 'Timeout') {
    console.error('Request timed out');
  }
}

// Cancellation with AbortController
const controller = new AbortController();
const signal = controller.signal;

// Later...
controller.abort();

// Use with fetch
const response = await fetch(url, { signal });
```

---

## Metaprogramming Translation

### Decorators

**Python:**
```python
from functools import wraps
from typing import Callable, TypeVar, ParamSpec

P = ParamSpec('P')
T = TypeVar('T')

# Function decorator
def log_calls(func: Callable[P, T]) -> Callable[P, T]:
    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Finished {func.__name__}")
        return result
    return wrapper

@log_calls
def process_data(data: list) -> int:
    return len(data)

# Class decorator
def singleton(cls):
    instances = {}
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class Database:
    pass
```

**TypeScript:**
```typescript
// Function decorator (experimental)
function logCalls(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey}`);
    const result = original.apply(this, args);
    console.log(`Finished ${propertyKey}`);
    return result;
  };
}

class Service {
  @logCalls
  processData(data: any[]): number {
    return data.length;
  }
}

// Class decorator
function singleton<T extends { new(...args: any[]): {} }>(constructor: T) {
  let instance: T;
  return class extends constructor {
    constructor(...args: any[]) {
      if (instance) {
        return instance;
      }
      super(...args);
      instance = this as any;
    }
  };
}

@singleton
class Database {
  // ...
}

// Higher-order function (alternative to decorators)
function logCalls<T extends (...args: any[]) => any>(fn: T): T {
  return ((...args: any[]) => {
    console.log(`Calling ${fn.name}`);
    const result = fn(...args);
    console.log(`Finished ${fn.name}`);
    return result;
  }) as T;
}

const processData = logCalls((data: any[]) => data.length);
```

### Property Access

**Python:**
```python
class Circle:
    def __init__(self, radius: float):
        self._radius = radius

    @property
    def area(self) -> float:
        return 3.14159 * self._radius ** 2

    @property
    def radius(self) -> float:
        return self._radius

    @radius.setter
    def radius(self, value: float) -> None:
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

# Usage
circle = Circle(5)
print(circle.area)  # Computed property
circle.radius = 10  # Setter validation
```

**TypeScript:**
```typescript
class Circle {
  private _radius: number;

  constructor(radius: number) {
    this._radius = radius;
  }

  get area(): number {
    return Math.PI * this._radius ** 2;
  }

  get radius(): number {
    return this._radius;
  }

  set radius(value: number) {
    if (value < 0) {
      throw new Error("Radius cannot be negative");
    }
    this._radius = value;
  }
}

// Usage
const circle = new Circle(5);
console.log(circle.area);  // Computed property
circle.radius = 10;        // Setter validation
```

### Dynamic Attribute Access

**Python:**
```python
class DynamicObject:
    def __getattr__(self, name: str):
        return f"Dynamic: {name}"

    def __setattr__(self, name: str, value):
        super().__setattr__(name, value)

    def __delattr__(self, name: str):
        super().__delattr__(name)

# Usage
obj = DynamicObject()
print(obj.anything)  # "Dynamic: anything"
```

**TypeScript:**
```typescript
// Using Proxy for dynamic properties
function createDynamicObject() {
  return new Proxy({}, {
    get(target, prop) {
      if (prop in target) {
        return target[prop];
      }
      return `Dynamic: ${String(prop)}`;
    },
    set(target, prop, value) {
      target[prop] = value;
      return true;
    },
    deleteProperty(target, prop) {
      delete target[prop];
      return true;
    }
  });
}

// Usage
const obj = createDynamicObject();
console.log(obj.anything);  // "Dynamic: anything"
```

---

## Zero and Default Values

### Null/None Handling

| Python | TypeScript | Notes |
|--------|------------|-------|
| `None` | `null` | Explicit null |
| `None` | `undefined` | Default absence |
| `Optional[T]` | `T \| null` | Nullable value |
| `T \| None` | `T \| undefined` | Optional value |
| `Union[T, None]` | `T \| null \| undefined` | Fully optional |

**Python:**
```python
from typing import Optional

def find_user(id: str) -> Optional[User]:
    user = database.get(id)
    return user  # Can be None

# Usage - explicit None check
user = find_user('123')
if user is not None:
    print(user.name)
else:
    print("User not found")

# Or with walrus operator
if (user := find_user('123')) is not None:
    print(user.name)
```

**TypeScript:**
```typescript
function findUser(id: string): User | null {
  const user = database.get(id);
  return user ?? null;  // Convert undefined to null
}

// Usage - explicit null check
const user = findUser('123');
if (user !== null) {
  console.log(user.name);
} else {
  console.log('User not found');
}

// Optional chaining
const name = findUser('123')?.name ?? 'Unknown';

// Nullish coalescing
const user = findUser('123') ?? defaultUser;
```

### Default Values

**Python:**
```python
# Default parameter values
def greet(name: str = "World") -> str:
    return f"Hello, {name}!"

# Mutable defaults (AVOID)
def bad_append(item: str, items: list[str] = []) -> list[str]:
    items.append(item)  # Same list reused!
    return items

# Correct mutable defaults
def good_append(item: str, items: list[str] | None = None) -> list[str]:
    if items is None:
        items = []
    items.append(item)
    return items

# Dataclass defaults
from dataclasses import dataclass, field

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    tags: list[str] = field(default_factory=list)  # Mutable default
```

**TypeScript:**
```typescript
// Default parameter values
function greet(name: string = "World"): string {
  return `Hello, ${name}!`;
}

// Default object/array parameters (creates new instance each time)
function append(item: string, items: string[] = []): string[] {
  items.push(item);
  return items;
}

// Interface with optional properties
interface Config {
  host?: string;
  port?: number;
  tags?: string[];
}

// Default values when destructuring
function createConfig({
  host = "localhost",
  port = 8080,
  tags = []
}: Partial<Config> = {}): Config {
  return { host, port, tags };
}

// Class with defaults
class Config {
  host: string = "localhost";
  port: number = 8080;
  tags: string[] = [];  // Creates new array per instance
}
```

---

## Serialization

### JSON Serialization

**Python:**
```python
import json
from dataclasses import dataclass, asdict
from datetime import datetime
from enum import Enum

@dataclass
class User:
    id: str
    name: str
    email: str
    created_at: datetime

# Custom encoder for non-JSON types
class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, Enum):
            return obj.value
        return super().default(obj)

# Serialization
user = User("1", "Alice", "alice@example.com", datetime.now())
json_str = json.dumps(asdict(user), cls=CustomEncoder)

# Deserialization with validation
data = json.loads(json_str)
user = User(
    id=data['id'],
    name=data['name'],
    email=data['email'],
    created_at=datetime.fromisoformat(data['created_at'])
)
```

**TypeScript:**
```typescript
interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

// Serialization (custom replacer for Date)
const user: User = {
  id: "1",
  name: "Alice",
  email: "alice@example.com",
  createdAt: new Date()
};

const jsonStr = JSON.stringify(user, (key, value) => {
  if (value instanceof Date) {
    return value.toISOString();
  }
  return value;
});

// Deserialization (custom reviver for Date)
const data = JSON.parse(jsonStr, (key, value) => {
  if (key === 'createdAt' && typeof value === 'string') {
    return new Date(value);
  }
  return value;
}) as User;

// Better: Use a library like zod for validation
import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
  createdAt: z.coerce.date()
});

const user = UserSchema.parse(data);  // Validates and transforms
```

### Pydantic → Zod

**Python (Pydantic):**
```python
from pydantic import BaseModel, EmailStr, validator
from datetime import datetime

class User(BaseModel):
    id: str
    name: str
    email: EmailStr
    age: int
    created_at: datetime

    @validator('age')
    def validate_age(cls, v):
        if v < 0:
            raise ValueError('Age must be positive')
        return v

# Validation and serialization
user = User(
    id="1",
    name="Alice",
    email="alice@example.com",
    age=30,
    created_at=datetime.now()
)

json_str = user.model_dump_json()  # Serialize
user2 = User.model_validate_json(json_str)  # Deserialize with validation
```

**TypeScript (Zod):**
```typescript
import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
  age: z.number().int().positive(),
  createdAt: z.coerce.date()
});

type User = z.infer<typeof UserSchema>;

// Validation and serialization
const user: User = {
  id: "1",
  name: "Alice",
  email: "alice@example.com",
  age: 30,
  createdAt: new Date()
};

// Validate
const validated = UserSchema.parse(user);

// Safe parse (doesn't throw)
const result = UserSchema.safeParse(user);
if (result.success) {
  console.log(result.data);
} else {
  console.error(result.error);
}

// JSON round-trip
const jsonStr = JSON.stringify(user);
const parsed = UserSchema.parse(JSON.parse(jsonStr));
```

---

## Build and Dependencies

### Package Manager Comparison

| Aspect | Python | TypeScript |
|--------|--------|------------|
| Package manager | pip, uv, poetry | npm, yarn, pnpm |
| Manifest file | `pyproject.toml`, `requirements.txt` | `package.json` |
| Lock file | `uv.lock`, `poetry.lock` | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` |
| Virtual environment | `venv`, `virtualenv` | `node_modules` (per-project) |
| Global packages | Discouraged | Allowed with `-g` flag |

### Project Configuration

**Python (pyproject.toml):**
```toml
[project]
name = "myproject"
version = "1.0.0"
description = "A sample Python project"
readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
dependencies = [
    "requests>=2.31.0",
    "pydantic>=2.0.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "ruff>=0.1.0",
    "mypy>=1.6.0",
]

[projectcli]
mytool = "myproject.cli:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.mypy]
python_version = "3.11"
strict = true
```

**TypeScript (package.json + tsconfig.json):**
```json
// package.json
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "A sample TypeScript project",
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "test": "vitest",
    "lint": "eslint .",
    "format": "prettier --write ."
  },
  "dependencies": {
    "axios": "^1.6.0"
  },
  "devDependencies": {
    "typescript": "^5.3.0",
    "vitest": "^1.0.0",
    "eslint": "^8.54.0",
    "@typescript-eslint/eslint-plugin": "^6.13.0",
    "@typescript-eslint/parser": "^6.13.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}
```

```json
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022"],
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

### Dependency Installation

**Python:**
```bash
# Using pip
pip install requests pytest

# Using uv (faster)
uv add requests pytest
uv add --dev ruff mypy

# Install from requirements.txt
pip install -r requirements.txt

# Install project in editable mode
pip install -e .
```

**TypeScript:**
```bash
# Using npm
npm install axios
npm install --save-dev vitest

# Using pnpm (faster)
pnpm add axios
pnpm add -D vitest

# Install from package.json
npm install

# Global install (for CLI tools)
npm install -g typescript
```

---

## Testing

### Testing Framework Comparison

| Aspect | Python (pytest) | TypeScript (vitest/jest) |
|--------|----------------|-------------------------|
| Runner | pytest | vitest / jest |
| Assertion | assert statement | expect().toBe() |
| Fixtures | @pytest.fixture | beforeEach / fixtures |
| Mocking | unittest.mock | vi.mock() / jest.mock() |
| Coverage | pytest-cov | vitest --coverage |
| Parametrize | @pytest.mark.parametrize | test.each() |

### Basic Tests

**Python (pytest):**
```python
# test_calculator.py
import pytest

def test_addition():
    assert 1 + 1 == 2

def test_division_by_zero():
    with pytest.raises(ZeroDivisionError):
        1 / 0

# Parametrized tests
@pytest.mark.parametrize("a,b,expected", [
    (1, 1, 2),
    (2, 3, 5),
    (10, 5, 15),
])
def test_addition_parametrized(a, b, expected):
    assert a + b == expected

# Fixtures
@pytest.fixture
def sample_user():
    return {"name": "Alice", "age": 30}

def test_user_data(sample_user):
    assert sample_user["name"] == "Alice"
    assert sample_user["age"] == 30
```

**TypeScript (vitest):**
```typescript
// calculator.test.ts
import { describe, it, expect, beforeEach } from 'vitest';

describe('Calculator', () => {
  it('should add numbers', () => {
    expect(1 + 1).toBe(2);
  });

  it('should throw on division by zero', () => {
    expect(() => 1 / 0).toThrow();
  });

  // Parametrized tests
  it.each([
    [1, 1, 2],
    [2, 3, 5],
    [10, 5, 15],
  ])('should add %i + %i = %i', (a, b, expected) => {
    expect(a + b).toBe(expected);
  });

  // Fixtures with beforeEach
  let sampleUser: { name: string; age: number };

  beforeEach(() => {
    sampleUser = { name: "Alice", age: 30 };
  });

  it('should have user data', () => {
    expect(sampleUser.name).toBe("Alice");
    expect(sampleUser.age).toBe(30);
  });
});
```

### Async Tests

**Python:**
```python
import pytest
import asyncio

@pytest.mark.asyncio
async def test_async_fetch():
    result = await fetch_user('123')
    assert result.name == "Alice"

# With timeout
@pytest.mark.asyncio
@pytest.mark.timeout(5)
async def test_slow_operation():
    result = await slow_operation()
    assert result is not None
```

**TypeScript:**
```typescript
import { describe, it, expect } from 'vitest';

describe('Async operations', () => {
  it('should fetch user', async () => {
    const result = await fetchUser('123');
    expect(result.name).toBe("Alice");
  });

  // With timeout
  it('should handle slow operation', async () => {
    const result = await slowOperation();
    expect(result).not.toBeNull();
  }, { timeout: 5000 });
});
```

---

## Idiom Translation

### Pattern 1: List Comprehensions → Array Methods

**Python:**
```python
# List comprehension
squares = [x**2 for x in range(10)]

# With filter
evens = [x for x in range(10) if x % 2 == 0]

# Nested
matrix = [[i+j for j in range(3)] for i in range(3)]

# Dict comprehension
word_lengths = {word: len(word) for word in ["hello", "world"]}

# Set comprehension
unique_lengths = {len(word) for word in ["hello", "world", "hi"]}
```

**TypeScript:**
```typescript
// Array map
const squares = Array.from({ length: 10 }, (_, i) => i ** 2);
// Or
const squares = [...Array(10).keys()].map(x => x ** 2);

// With filter
const evens = [...Array(10).keys()].filter(x => x % 2 === 0);

// Nested
const matrix = Array.from({ length: 3 }, (_, i) =>
  Array.from({ length: 3 }, (_, j) => i + j)
);

// Object from entries
const wordLengths = Object.fromEntries(
  ["hello", "world"].map(word => [word, word.length])
);

// Set
const uniqueLengths = new Set(["hello", "world", "hi"].map(w => w.length));
```

### Pattern 2: With Statement → Try-Finally / Using

**Python:**
```python
# Context manager
with open("file.txt") as f:
    content = f.read()
# File automatically closed

# Custom context manager
from contextlib import contextmanager

@contextmanager
def timer():
    start = time.time()
    yield
    end = time.time()
    print(f"Elapsed: {end - start:.2f}s")

with timer():
    # Code to time
    time.sleep(1)
```

**TypeScript:**
```typescript
// Try-finally pattern
let file: fs.FileHandle | null = null;
try {
  file = await fs.open("file.txt");
  const content = await file.readFile('utf-8');
} finally {
  await file?.close();
}

// Using Disposable pattern (TypeScript 5.2+)
class Timer implements Disposable {
  private start = Date.now();

  [Symbol.dispose]() {
    const end = Date.now();
    console.log(`Elapsed: ${(end - this.start) / 1000}s`);
  }
}

{
  using timer = new Timer();
  // Code to time
  await sleep(1000);
}  // Timer automatically disposed

// Async disposable
class FileHandle implements AsyncDisposable {
  async [Symbol.asyncDispose]() {
    await this.close();
  }
}

{
  await using file = await fs.open("file.txt");
  // Use file
}  // Automatically closed
```

### Pattern 3: Multiple Return Values → Tuples/Objects

**Python:**
```python
# Tuple unpacking
def get_user_stats(user_id: str) -> tuple[str, int, float]:
    name = "Alice"
    count = 42
    average = 3.14
    return name, count, average

name, count, average = get_user_stats("123")

# Named tuple
from typing import NamedTuple

class UserStats(NamedTuple):
    name: str
    count: int
    average: float

def get_user_stats(user_id: str) -> UserStats:
    return UserStats("Alice", 42, 3.14)

stats = get_user_stats("123")
print(stats.name, stats.count)
```

**TypeScript:**
```typescript
// Tuple return
function getUserStats(userId: string): [string, number, number] {
  const name = "Alice";
  const count = 42;
  const average = 3.14;
  return [name, count, average];
}

const [name, count, average] = getUserStats("123");

// Object return (preferred)
interface UserStats {
  name: string;
  count: number;
  average: number;
}

function getUserStats(userId: string): UserStats {
  return {
    name: "Alice",
    count: 42,
    average: 3.14
  };
}

const stats = getUserStats("123");
console.log(stats.name, stats.count);

// With destructuring
const { name, count } = getUserStats("123");
```

### Pattern 4: Dataclass → Interface/Type

**Python:**
```python
from dataclasses import dataclass, field

@dataclass
class User:
    id: str
    name: str
    email: str
    age: int = 0
    tags: list[str] = field(default_factory=list)

    def greet(self) -> str:
        return f"Hello, {self.name}!"

# Usage
user = User(id="1", name="Alice", email="alice@example.com")
print(user.greet())
```

**TypeScript:**
```typescript
// Interface (data only)
interface User {
  id: string;
  name: string;
  email: string;
  age?: number;
  tags?: string[];
}

// Class (with methods)
class User {
  id: string;
  name: string;
  email: string;
  age: number = 0;
  tags: string[] = [];

  constructor(data: Omit<User, 'age' | 'tags'>) {
    this.id = data.id;
    this.name = data.name;
    this.email = data.email;
  }

  greet(): string {
    return `Hello, ${this.name}!`;
  }
}

// Usage
const user = new User({
  id: "1",
  name: "Alice",
  email: "alice@example.com"
});
console.log(user.greet());

// Factory function (alternative)
function createUser(data: Omit<User, 'age' | 'tags'>): User {
  return {
    age: 0,
    tags: [],
    ...data,
    greet() {
      return `Hello, ${this.name}!`;
    }
  };
}
```

### Pattern 5: Protocol → Interface

**Python:**
```python
from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...

class Closeable(Protocol):
    def close(self) -> None: ...

# Structural typing - no explicit inheritance needed
class Window:
    def draw(self) -> None:
        print("Drawing window")

    def close(self) -> None:
        print("Closing window")

def render(obj: Drawable) -> None:
    obj.draw()

window = Window()
render(window)  # Works without explicit inheritance
```

**TypeScript:**
```typescript
// Interface (structural typing built-in)
interface Drawable {
  draw(): void;
}

interface Closeable {
  close(): void;
}

// Structural typing - no explicit implements needed (but recommended)
class Window implements Drawable, Closeable {
  draw(): void {
    console.log("Drawing window");
  }

  close(): void {
    console.log("Closing window");
  }
}

function render(obj: Drawable): void {
  obj.draw();
}

const window = new Window();
render(window);  // Works due to structural typing
```

### Pattern 6: Enum → Const Object or String Literal Union

**Python:**
```python
from enum import Enum, StrEnum

# Enum class
class Status(Enum):
    PENDING = "pending"
    ACTIVE = "active"
    COMPLETED = "completed"

# Or StrEnum (Python 3.11+)
class Status(StrEnum):
    PENDING = "pending"
    ACTIVE = "active"
    COMPLETED = "completed"

# Usage
def process(status: Status) -> None:
    if status == Status.PENDING:
        print("Pending...")
    elif status is Status.ACTIVE:
        print("Active!")

# With Literal
from typing import Literal

StatusType = Literal["pending", "active", "completed"]

def handle(status: StatusType) -> None:
    if status == "pending":
        print("Pending...")
```

**TypeScript:**
```typescript
// String enum
enum Status {
  Pending = "pending",
  Active = "active",
  Completed = "completed"
}

function process(status: Status): void {
  if (status === Status.Pending) {
    console.log("Pending...");
  } else if (status === Status.Active) {
    console.log("Active!");
  }
}

// Const object (preferred - no runtime overhead)
const Status = {
  Pending: "pending",
  Active: "active",
  Completed: "completed"
} as const;

type Status = typeof Status[keyof typeof Status];

function handle(status: Status): void {
  if (status === Status.Pending) {
    console.log("Pending...");
  }
}

// String literal union (most TypeScript-like)
type Status = "pending" | "active" | "completed";

function handleSimple(status: Status): void {
  switch (status) {
    case "pending":
      console.log("Pending...");
      break;
    case "active":
      console.log("Active!");
      break;
    case "completed":
      console.log("Completed!");
      break;
  }
}
```

### Pattern 7: Generat

…(truncated)
