# Convert Typescript Golang

> Convert TypeScript code to idiomatic Go. Use when migrating TypeScript projects to Go, translating TypeScript patterns to idiomatic Go, or refactoring TypeScript codebases into Go. Extends meta-convert-dev with TypeScript-to-Go specific patterns.

- Skill: `arustydev/convert-typescript-golang` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arustydev/convert-typescript-golang`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arustydev/convert-typescript-golang/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-typescript-golang

---


# Convert TypeScript to Go

Convert TypeScript code to idiomatic Go. This skill extends `meta-convert-dev` with TypeScript-to-Go 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**: TypeScript types → Go types
- **Idiom translations**: TypeScript patterns → idiomatic Go
- **Error handling**: Exceptions → error return values
- **Async patterns**: Promise/async → goroutines/channels
- **Interface patterns**: Structural typing → Go interfaces

## This Skill Does NOT Cover

- General conversion methodology - see `meta-convert-dev`
- TypeScript language fundamentals - see `lang-typescript-dev`
- Go language fundamentals - see `lang-golang-dev`
- Reverse conversion (Go → TypeScript) - see `convert-golang-typescript`

---

## Quick Reference

| TypeScript | Go | Notes |
|------------|-----|-------|
| `string` | `string` | Direct mapping |
| `number` | `int`, `int64`, `float64` | Specify precision |
| `boolean` | `bool` | Direct mapping |
| `null \| undefined` | `nil` or zero value | Go has nil for pointers/interfaces/slices/maps/channels |
| `T[]` | `[]T` | Slice (dynamic array) |
| `Array<T>` | `[]T` | Slice |
| `[T, U]` | `struct { First T; Second U }` | Named fields preferred over tuples |
| `Record<K, V>` | `map[K]V` | Map type |
| `Map<K, V>` | `map[K]V` | Map type |
| `Set<T>` | `map[T]struct{}` or `map[T]bool` | Set via map with empty struct |
| `T \| U` | `interface{}` or custom type | Use type switch or discriminated union pattern |
| `Promise<T>` | `chan T` or function with error return | Goroutines for async execution |
| `interface X` | `type X interface` | Behavior contracts |
| `class X` | `type X struct` + methods | Methods with receiver syntax |
| `enum` | `const` block with `iota` | Or string constants |
| `any` | `interface{}` or `any` (Go 1.18+) | Avoid when possible |
| `void` | No return value | Function signature omits return |
| `never` | No direct equivalent | Use panic or infinite loop |

## When Converting Code

1. **Analyze source thoroughly** before writing target
2. **Map types first** - create type equivalence table
3. **Preserve semantics** over syntax similarity
4. **Adopt target idioms** - don't write "TypeScript code in Go syntax"
5. **Handle edge cases** - null/nil/undefined, error paths, resource cleanup
6. **Test equivalence** - same inputs → same outputs

---

## Type System Mapping

### Primitive Types

| TypeScript | Go | Notes |
|------------|-----|-------|
| `string` | `string` | UTF-8 encoded in Go |
| `number` | `int` | Default for integers without decimal |
| `number` | `int8`, `int16`, `int32`, `int64` | Sized integers |
| `number` | `uint`, `uint8`, `uint16`, `uint32`, `uint64` | Unsigned integers |
| `number` | `float32`, `float64` | Floating point |
| `bigint` | `*big.Int` | From `math/big` package |
| `boolean` | `bool` | Direct mapping |
| `null` | `nil` | For pointers, interfaces, slices, maps, channels, functions |
| `undefined` | Zero value | Each type has a zero value (0, "", false, nil) |
| `symbol` | No direct equivalent | Use string or int constants |
| `any` | `interface{}` or `any` | `any` alias added in Go 1.18 |
| `unknown` | `interface{}` with type assertion | Requires type checking |
| `void` | (no return) | Function returns nothing |
| `never` | No direct equivalent | Functions that never return use panic |

### Collection Types

| TypeScript | Go | Notes |
|------------|-----|-------|
| `T[]` | `[]T` | Slice (resizable, passed by reference) |
| `Array<T>` | `[]T` | Same as T[] |
| `readonly T[]` | `[]T` | Go doesn't enforce readonly at compile time |
| `[number, number, number]` | `[3]T` | Fixed-size array |
| `[T, U]` | `struct { A T; B U }` | Named struct preferred |
| `[T, U, V]` | `struct { A T; B U; C V }` | Named struct preferred |
| `Map<K, V>` | `map[K]V` | Hash map |
| `Record<K, V>` | `map[K]V` | Hash map |
| `Set<T>` | `map[T]struct{}` | Empty struct uses zero memory |
| `Set<T>` | `map[T]bool` | Alternative using bool (1 byte per entry) |
| `WeakMap` | No direct equivalent | Use map with manual cleanup |
| `WeakSet` | No direct equivalent | Use map with manual cleanup |

### Composite Types

| TypeScript | Go | Notes |
|------------|-----|-------|
| `interface X { ... }` (data) | `type X struct { ... }` | Data structures |
| `interface X { method(): T }` | `type X interface { Method() T }` | Behavior contracts |
| `class X` | `type X struct` + methods | Struct with receiver methods |
| `type X = Y` | `type X = Y` | Type alias (Go 1.9+) |
| `type X = Y \| Z` | Custom type with methods | Discriminated union pattern |
| `T \| null` | `*T` | Pointer can be nil |
| `T \| undefined` | `*T` or zero value check | Pointer or explicit check |
| `Partial<T>` | Struct with pointer fields | Each field can be nil |
| `Required<T>` | Struct with value fields | All fields have values |
| `Pick<T, K>` | New struct type | Select fields manually |
| `Omit<T, K>` | New struct type | Exclude fields manually |
| `enum X` | `const` block with `iota` | Or typed constants |
| `namespace X` | `package X` | Package organization |

### Generic Type Mappings

| TypeScript | Go | Notes |
|------------|-----|-------|
| `<T>` | `[T any]` | Generic type parameter (Go 1.18+) |
| `<T extends U>` | `[T U]` | Type constraint using interface |
| `<T extends keyof U>` | No direct equivalent | Use reflection or code generation |
| `Array<T>` | `[]T` | Built-in generic slice |
| `Promise<T>` | `chan T` or function return | Channels for async communication |
| `Readonly<T>` | No language support | Convention and documentation |
| `Record<K, V>` | `map[K]V` | Built-in generic map |

---

## Idiom Translation

### Pattern: Null/Undefined Handling

**TypeScript:**
```typescript
const name = user?.name ?? "Anonymous";
const age = user?.age || 18;
```

**Go:**
```go
var name string
if user != nil && user.Name != "" {
    name = user.Name
} else {
    name = "Anonymous"
}

age := 18
if user != nil && user.Age > 0 {
    age = user.Age
}
```

**Why this translation:**
- Go doesn't have optional chaining or null coalescing operators
- Explicit nil checks are idiomatic and clear
- Zero values (0, "", false) should be considered in logic
- Pointers are used when nil is a meaningful state

### Pattern: Array/Slice Operations

**TypeScript:**
```typescript
const activeValues = items
  .filter(x => x.active)
  .map(x => x.value)
  .reduce((sum, val) => sum + val, 0);
```

**Go:**
```go
var activeValues int
for _, item := range items {
    if item.Active {
        activeValues += item.Value
    }
}
```

**Why this translation:**
- Go prefers explicit for loops over chaining methods
- More readable and maintainable for Go developers
- Better performance (single pass, no intermediate allocations)
- Can use generics (Go 1.18+) for reusable map/filter if needed

### Pattern: Object Destructuring

**TypeScript:**
```typescript
const { name, age, ...rest } = user;
const [first, second, ...others] = items;
```

**Go:**
```go
name := user.Name
age := user.Age
// rest requires manual field copying or reflection

first := items[0]
second := items[1]
others := items[2:]
```

**Why this translation:**
- Go doesn't support destructuring
- Direct field access is clear and explicit
- Slice operations provide array destructuring
- Manual copying ensures clarity about what's being used

### Pattern: Classes and Methods

**TypeScript:**
```typescript
class Calculator {
  private total: number = 0;

  add(value: number): void {
    this.total += value;
  }

  getTotal(): number {
    return this.total;
  }
}
```

**Go:**
```go
type Calculator struct {
    total int // unexported (lowercase) is private
}

func NewCalculator() *Calculator {
    return &Calculator{total: 0}
}

func (c *Calculator) Add(value int) {
    c.total += value
}

func (c *Calculator) GetTotal() int {
    return c.total
}
```

**Why this translation:**
- Go uses struct types instead of classes
- Methods have receiver syntax (c *Calculator)
- Constructor pattern uses New* functions
- Exported/unexported controlled by capitalization
- Pointer receivers allow mutation

### Pattern: Interfaces and Duck Typing

**TypeScript:**
```typescript
interface Drawable {
  draw(): void;
}

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

// Any object with draw() method satisfies interface
const circle = { draw: () => console.log("circle") };
render(circle);
```

**Go:**
```go
type Drawable interface {
    Draw()
}

func Render(item Drawable) {
    item.Draw()
}

// Explicit type must implement interface
type Circle struct{}

func (c Circle) Draw() {
    fmt.Println("circle")
}

// Usage
circle := Circle{}
Render(circle)
```

**Why this translation:**
- Both languages support structural typing for interfaces
- Go requires explicit types, not object literals
- Interface satisfaction is implicit (no implements keyword)
- Method names must match exactly (case-sensitive)

### Pattern: Default Parameters

**TypeScript:**
```typescript
function greet(name: string = "Guest", greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}
```

**Go:**
```go
func Greet(name, greeting string) string {
    if name == "" {
        name = "Guest"
    }
    if greeting == "" {
        greeting = "Hello"
    }
    return fmt.Sprintf("%s, %s!", greeting, name)
}

// Or use options pattern for complex cases
type GreetOptions struct {
    Name     string
    Greeting string
}

func GreetWithOptions(opts GreetOptions) string {
    if opts.Name == "" {
        opts.Name = "Guest"
    }
    if opts.Greeting == "" {
        opts.Greeting = "Hello"
    }
    return fmt.Sprintf("%s, %s!", opts.Greeting, opts.Name)
}
```

**Why this translation:**
- Go doesn't support default parameters
- Check zero values and provide defaults explicitly
- Options pattern for complex parameter sets
- Functional options pattern for even more flexibility

### Pattern: Spread Operator

**TypeScript:**
```typescript
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
```

**Go:**
```go
arr1 := []int{1, 2, 3}
arr2 := append(append([]int{}, arr1...), 4, 5)
// Or clearer:
arr2 := make([]int, len(arr1), len(arr1)+2)
copy(arr2, arr1)
arr2 = append(arr2, 4, 5)

// No built-in object spread; must copy manually
obj2 := struct{ A, B, C int }{
    A: obj1.A,
    B: obj1.B,
    C: 3,
}
```

**Why this translation:**
- Go uses append with ... for variadic slice expansion
- Pre-allocating capacity avoids reallocation
- No object spread; manual field copying required
- Reflection can help for generic copying but adds complexity

### Pattern: Optional Properties

**TypeScript:**
```typescript
interface User {
  name: string;
  email?: string;
  age?: number;
}
```

**Go:**
```go
type User struct {
    Name  string
    Email *string // pointer indicates optional
    Age   *int    // nil means not provided
}

// Helper to create pointer
func StringPtr(s string) *string { return &s }
func IntPtr(i int) *int { return &i }

// Usage
user := User{
    Name:  "Alice",
    Email: StringPtr("alice@example.com"),
}
```

**Why this translation:**
- Pointers distinguish between "not provided" (nil) and "zero value"
- Helper functions make pointer creation cleaner
- Alternative: use zero values and a separate "set" map
- Consider whether nil vs zero value distinction is needed

### Pattern: Union Types

**TypeScript:**
```typescript
type Result = { success: true; data: string } | { success: false; error: string };

function process(): Result {
  if (Math.random() > 0.5) {
    return { success: true, data: "OK" };
  }
  return { success: false, error: "Failed" };
}
```

**Go:**
```go
type Result struct {
    Success bool
    Data    string // only valid if Success == true
    Error   string // only valid if Success == false
}

func Process() Result {
    if rand.Float64() > 0.5 {
        return Result{Success: true, Data: "OK"}
    }
    return Result{Success: false, Error: "Failed"}
}

// Or use interface with type assertion
type ResultSuccess struct{ Data string }
type ResultError struct{ Error string }

func Process() interface{} {
    if rand.Float64() > 0.5 {
        return ResultSuccess{Data: "OK"}
    }
    return ResultError{Error: "Failed"}
}
```

**Why this translation:**
- Go doesn't have union types
- Use struct with discriminator field (Success bool)
- Interface{} with type assertion for true sum types
- Consider if error return pattern is more idiomatic

### Pattern: String Interpolation

**TypeScript:**
```typescript
const name = "Alice";
const age = 30;
const message = `Hello, ${name}! You are ${age} years old.`;
```

**Go:**
```go
name := "Alice"
age := 30
message := fmt.Sprintf("Hello, %s! You are %d years old.", name, age)
```

**Why this translation:**
- Go uses fmt.Sprintf for string formatting
- Printf-style format verbs (%s, %d, %v, etc.)
- Type-safe at runtime, not compile-time
- Alternative: strings.Builder for complex concatenation

---

## Error Handling

### TypeScript Exception Model → Go Error Return Model

**TypeScript:**
```typescript
function parseConfig(path: string): Config {
  if (!fs.existsSync(path)) {
    throw new Error(`Config file not found: ${path}`);
  }

  const content = fs.readFileSync(path, 'utf-8');

  try {
    return JSON.parse(content);
  } catch (e) {
    throw new Error(`Failed to parse config: ${e.message}`);
  }
}

// Usage
try {
  const config = parseConfig("config.json");
  console.log(config);
} catch (err) {
  console.error("Error:", err.message);
}
```

**Go:**
```go
func ParseConfig(path string) (*Config, error) {
    if _, err := os.Stat(path); os.IsNotExist(err) {
        return nil, fmt.Errorf("config file not found: %s", path)
    }

    content, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read config: %w", err)
    }

    var config Config
    if err := json.Unmarshal(content, &config); err != nil {
        return nil, fmt.Errorf("failed to parse config: %w", err)
    }

    return &config, nil
}

// Usage
config, err := ParseConfig("config.json")
if err != nil {
    log.Printf("Error: %v", err)
    return
}
fmt.Println(config)
```

**Why this translation:**
- Go returns errors as values, not exceptions
- Multiple return values: (result, error)
- fmt.Errorf with %w wraps errors (Go 1.13+)
- errors.Is and errors.As for error checking
- Caller checks err != nil after each call

### Custom Error Types

**TypeScript:**
```typescript
class ValidationError extends Error {
  constructor(public field: string, message: string) {
    super(message);
    this.name = "ValidationError";
  }
}

class NotFoundError extends Error {
  constructor(public resource: string) {
    super(`${resource} not found`);
    this.name = "NotFoundError";
  }
}

function validateUser(user: User): void {
  if (!user.email) {
    throw new ValidationError("email", "Email is required");
  }
}
```

**Go:**
```go
// Custom error types implement error interface
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on field %s: %s", e.Field, e.Message)
}

type NotFoundError struct {
    Resource string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s not found", e.Resource)
}

func ValidateUser(user *User) error {
    if user.Email == "" {
        return &ValidationError{Field: "email", Message: "Email is required"}
    }
    return nil
}

// Usage with type assertion
err := ValidateUser(user)
if err != nil {
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        log.Printf("Validation failed on field: %s", validationErr.Field)
    }
}
```

**Why this translation:**
- Go uses error interface (Error() string method)
- Custom error types are structs with Error() method
- errors.As for type-safe error checking
- errors.Is for sentinel error comparison
- Wrap errors with fmt.Errorf("%w", err) to preserve chain

### Panic vs Error Returns

**TypeScript:**
```typescript
// Exceptions for everything
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("division by zero");
  }
  return a / b;
}
```

**Go:**
```go
// Error returns for expected errors
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Panic only for programmer errors (bugs)
func DivideMustNotBeZero(a, b float64) float64 {
    if b == 0 {
        panic("division by zero - caller error")
    }
    return a / b
}
```

**Why this translation:**
- Go distinguishes expected errors (return) from bugs (panic)
- Use error returns for conditions caller should handle
- Use panic for programmer errors / assertions
- recover() can catch panics (similar to catch) but rarely used
- Idiomatic Go: errors are values, not exceptions

---

## Concurrency Patterns

### Promise → Goroutines with Channels

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

// Usage
const user = await fetchUser("123");
console.log(user);
```

**Go:**
```go
func FetchUser(id string) (*User, error) {
    resp, err := http.Get(fmt.Sprintf("/users/%s", id))
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var user User
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, err
    }

    return &user, nil
}

// Usage (synchronous)
user, err := FetchUser("123")
if err != nil {
    log.Fatal(err)
}
fmt.Println(user)

// Or asynchronous with goroutine
func FetchUserAsync(id string) <-chan *User {
    ch := make(chan *User, 1)
    go func() {
        user, err := FetchUser(id)
        if err != nil {
            log.Printf("Error: %v", err)
            close(ch)
            return
        }
        ch <- user
    }()
    return ch
}

// Usage
userChan := FetchUserAsync("123")
user := <-userChan // Wait for result
```

**Why this translation:**
- Go uses goroutines (lightweight threads) for concurrency
- Channels communicate between goroutines
- Synchronous by default; explicit goroutines for async
- defer ensures cleanup (like finally)
- Error handling remains explicit

### Promise.all → WaitGroup or Channels

**TypeScript:**
```typescript
const [users, posts, comments] = await Promise.all([
  fetchUsers(),
  fetchPosts(),
  fetchComments()
]);
```

**Go:**
```go
// Using WaitGroup
var wg sync.WaitGroup
var users []User
var posts []Post
var comments []Comment
var mu sync.Mutex // Protect shared state if needed
var errs []error

wg.Add(3)

go func() {
    defer wg.Done()
    u, err := FetchUsers()
    if err != nil {
        mu.Lock()
        errs = append(errs, err)
        mu.Unlock()
        return
    }
    mu.Lock()
    users = u
    mu.Unlock()
}()

go func() {
    defer wg.Done()
    p, err := FetchPosts()
    if err != nil {
        mu.Lock()
        errs = append(errs, err)
        mu.Unlock()
        return
    }
    mu.Lock()
    posts = p
    mu.Unlock()
}()

go func() {
    defer wg.Done()
    c, err := FetchComments()
    if err != nil {
        mu.Lock()
        errs = append(errs, err)
        mu.Unlock()
        return
    }
    mu.Lock()
    comments = c
    mu.Unlock()
}()

wg.Wait()

if len(errs) > 0 {
    // Handle errors
}

// Or using channels (cleaner)
type Result struct {
    Users    []User
    Posts    []Post
    Comments []Comment
}

func FetchAll() (*Result, error) {
    usersCh := make(chan []User, 1)
    postsCh := make(chan []Post, 1)
    commentsCh := make(chan []Comment, 1)
    errCh := make(chan error, 3)

    go func() {
        users, err := FetchUsers()
        if err != nil {
            errCh <- err
            return
        }
        usersCh <- users
    }()

    go func() {
        posts, err := FetchPosts()
        if err != nil {
            errCh <- err
            return
        }
        postsCh <- posts
    }()

    go func() {
        comments, err := FetchComments()
        if err != nil {
            errCh <- err
            return
        }
        commentsCh <- comments
    }()

    result := &Result{}
    for i := 0; i < 3; i++ {
        select {
        case users := <-usersCh:
            result.Users = users
        case posts := <-postsCh:
            result.Posts = posts
        case comments := <-commentsCh:
            result.Comments = comments
        case err := <-errCh:
            return nil, err
        }
    }

    return result, nil
}
```

**Why this translation:**
- sync.WaitGroup waits for multiple goroutines
- Channels communicate results between goroutines
- select statement waits on multiple channels
- Explicit error handling for each operation
- Consider errgroup package for cleaner error handling

### Async/Await → Goroutines

**TypeScript:**
```typescript
async function processItems(items: string[]): Promise<string[]> {
  const results: string[] = [];

  for (const item of items) {
    const result = await processItem(item);
    results.push(result);
  }

  return results;
}
```

**Go:**
```go
// Sequential (like await)
func ProcessItems(items []string) ([]string, error) {
    results := make([]string, 0, len(items))

    for _, item := range items {
        result, err := ProcessItem(item)
        if err != nil {
            return nil, err
        }
        results = append(results, result)
    }

    return results, nil
}

// Concurrent (parallel processing)
func ProcessItemsConcurrent(items []string) ([]string, error) {
    results := make([]string, len(items))
    errCh := make(chan error, len(items))
    var wg sync.WaitGroup

    for i, item := range items {
        wg.Add(1)
        go func(index int, item string) {
            defer wg.Done()
            result, err := ProcessItem(item)
            if err != nil {
                errCh <- err
                return
            }
            results[index] = result
        }(i, item)
    }

    wg.Wait()
    close(errCh)

    if len(errCh) > 0 {
        return nil, <-errCh
    }

    return results, nil
}
```

**Why this translation:**
- Go doesn't have async/await keywords
- Sequential code is default (like synchronous)
- Use goroutines explicitly for concurrency
- Channels or WaitGroup coordinate goroutines
- More control over concurrency patterns

### Event Emitters → Channels

**TypeScript:**
```typescript
import { EventEmitter } from 'events';

const emitter = new EventEmitter();

emitter.on('data', (value: number) => {
  console.log('Received:', value);
});

emitter.emit('data', 42);
```

**Go:**
```go
// Channel-based pub/sub
type EventBus struct {
    subscribers []chan int
    mu          sync.RWMutex
}

func NewEventBus() *EventBus {
    return &EventBus{
        subscribers: make([]chan int, 0),
    }
}

func (eb *EventBus) Subscribe() <-chan int {
    eb.mu.Lock()
    defer eb.mu.Unlock()

    ch := make(chan int, 10) // buffered
    eb.subscribers = append(eb.subscribers, ch)
    return ch
}

func (eb *EventBus) Publish(value int) {
    eb.mu.RLock()
    defer eb.mu.RUnlock()

    for _, ch := range eb.subscribers {
        ch <- value
    }
}

// Usage
bus := NewEventBus()
dataCh := bus.Subscribe()

go func() {
    for value := range dataCh {
        fmt.Println("Received:", value)
    }
}()

bus.Publish(42)
```

**Why this translation:**
- Go uses channels for message passing
- Explicit subscriber management
- Type-safe channels (type per channel)
- Consider existing packages (e.g., EventBus libraries)
- Goroutines handle async listeners

---

## Common Pitfalls

### 1. Ignoring Error Returns

**Problem:**
```go
// ❌ Ignoring error return
user, _ := FetchUser("123")
fmt.Println(user.Name) // Potential nil pointer dereference!
```

**Solution:**
```go
// ✓ Always check errors
user, err := FetchUser("123")
if err != nil {
    return fmt.Errorf("failed to fetch user: %w", err)
}
fmt.Println(user.Name)
```

**Why:**
- Go conventions require explicit error handling
- Ignoring errors leads to runtime panics
- Use linters (errcheck, golangci-lint) to catch

### 2. Using Pointers When Not Needed

**Problem:**
```go
// ❌ Unnecessary pointer
func Double(n *int) *int {
    result := *n * 2
    return &result
}
```

**Solution:**
```go
// ✓ Pass by value for small types
func Double(n int) int {
    return n * 2
}
```

**Why:**
- Small types (int, bool, small structs) are efficient by value
- Pointers add indirection and nil checks
- Use pointers for: large structs, mutation, or optional values

### 3. Modifying Loop Variables in Goroutines

**Problem:**
```go
// ❌ Loop variable capture bug
for _, item := range items {
    go func() {
        process(item) // All goroutines see last item!
    }()
}
```

**Solution:**
```go
// ✓ Pass variable as parameter or shadow it
for _, item := range items {
    item := item // shadow
    go func() {
        process(item)
    }()
}

// Or pass as parameter
for _, item := range items {
    go func(i string) {
        process(i)
    }(item)
}
```

**Why:**
- Loop variable is reused across iterations
- Goroutines capture variable reference, not value
- Fixed in Go 1.22+ with per-iteration variables

### 4. Not Closing Channels

**Problem:**
```go
// ❌ Channel never closed
ch := make(chan int)
go func() {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    // Never closes!
}()

for val := range ch {
    fmt.Println(val) // Hangs after 10 values
}
```

**Solution:**
```go
// ✓ Close channel when done
ch := make(chan int)
go func() {
    defer close(ch)
    for i := 0; i < 10; i++ {
        ch <- i
    }
}()

for val := range ch {
    fmt.Println(val)
}
```

**Why:**
- range on channel blocks until closed
- close() signals no more values coming
- Only sender should close (not receiver)

### 5. Misunderstanding Zero Values

**Problem:**
```typescript
// TypeScript: undefined check
if (user.age !== undefined) {
  // age was explicitly set
}
```

```go
// ❌ Go: can't distinguish zero value from explicit zero
if user.Age != 0 {
    // Could be unset OR explicitly set to 0!
}
```

**Solution:**
```go
// ✓ Use pointers for optional values
type User struct {
    Name string
    Age  *int // nil means not set, 0 means explicitly zero
}

if user.Age != nil {
    fmt.Println(*user.Age)
}
```

**Why:**
- Go initializes all variables to zero values
- Can't distinguish "not set" from "set to zero"
- Use pointers when distinction matters

### 6. Forgetting defer for Cleanup

**Problem:**
```go
// ❌ Manual cleanup easy to forget
file, err := os.Open("file.txt")
if err != nil {
    return err
}
// ... lots of code ...
if someError {
    return someError // Forgot to close file!
}
file.Close()
```

**Solution:**
```go
// ✓ defer ensures cleanup
file, err := os.Open("file.txt")
if err != nil {
    return err
}
defer file.Close() // Always runs before function returns

// ... code can return anywhere ...
```

**Why:**
- defer guarantees cleanup on all return paths
- Executes in LIFO order
- Common for: files, mutexes, database connections

### 7. Copying Mutexes

**Problem:**
```go
// ❌ Copying struct with mutex
type Counter struct {
    mu    sync.Mutex
    count int
}

func (c Counter) Inc() { // Value receiver copies mutex!
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}
```

**Solution:**
```go
// ✓ Pointer receiver for structs with mutexes
func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}
```

**Why:**
- Copying a locked mutex is undefined behavior
- sync types (Mutex, WaitGroup, etc.) must not be copied
- Use pointer receivers for types with sync primitives

### 8. Interface nil Confusion

**Problem:**
```go
// ❌ Interface containing nil pointer isn't nil!
var p *User = nil
var i interface{} = p
if i != nil {
    fmt.Println("Not nil!") // This prints!
}
```

**Solution:**
```go
// ✓ Check for nil before assigning to interface
var p *User = nil
if p != nil {
    i = p
} else {
    i = nil // Or don't assign
}

// Or use typed nil check
var i interface{} = (*User)(nil)
if i == nil || i.(*User) == nil {
    // Actually nil
}
```

**Why:**
- Interface stores (type, value) pair
- (type=*User, value=nil) != (type=nil, value=nil)
- Common source of bugs in error returns

---

## Tooling

### Conversion Tools

| Tool | Purpose | Notes |
|------|---------|-------|
| **Manual** | TypeScript → Go | No mature automated converter exists |
| AST analysis | Parse TypeScript | Use TypeScript compiler API |
| Code generation | Generate Go | Template-based or custom tooling |

### Go Development Tools

| Tool | Purpose | Notes |
|------|---------|-------|
| `go fmt` | Code formatting | Standardizes formatting |
| `goimports` | Import management | Adds/removes imports automatically |
| `golangci-lint` | Linting | Comprehensive linter aggregator |
| `go vet` | Static analysis | Detects common mistakes |
| `staticcheck` | Advanced static analysis | High-quality checks |
| `errcheck` | Error checking | Ensures errors are checked |
| `go test` | Testing | Built-in test framework |
| `go test -race` | Race detection | Detects race conditions |
| `pprof` | Profiling | CPU and memory profiling |
| `delve` | Debugging | Go debugger |

### Testing Tools

| Tool | Purpose | Notes |
|------|---------|-------|
| `testing` package | Unit tests | Built-in framework |
| `testify` | Assertions | Popular testing toolkit |
| `gomock` | Mocking | Interface mocking |
| `httptest` | HTTP testing | Test HTTP handlers |
| `go-cmp` | Deep comparison | Compare complex structures |

### Dependency Management

| TypeScript (npm) | Go Equivalent | Notes |
|------------------|---------------|-------|
| `axios` | `net/http`, `resty` | HTTP clients |
| `lodash` | Built-ins | Go has many built-in utilities |
| `express` | `net/http`, `gin`, `echo` | Web frameworks |
| `jest` | `testing` | Built-in testing |
| `dotenv` | `godotenv`, `viper` | Config management |
| `winston` | `log`, `zap`, `logrus` | Logging |
| `commander` | `flag`, `cobra`, `urfave/cli` | CLI parsing |
| `date-fns` | `time` | Date/time built-in |
| `uuid` | `google/uuid` | UUID generation |
| `zod` | `validator`, `ozzo-validation` | Validation |

---

## Examples

### Example 1: Simple - Type and Function Translation

**Before (TypeScript):**
```typescript
interface User {
  id: string;
  name: string;
  age: number;
  email?: string;
}

function findUserByID(users: User[], id: string): User | undefined {
  return users.find(u => u.id === id);
}

// Usage
const users: User[] = [
  { id: "1", name: "Alice", age: 30, email: "alice@example.com" },
  { id: "2", name: "Bob", age: 25 },
];

const user = findUserByID(users, "1");
if (user) {
  console.log(`Found: ${user.name}`);
}
```

**After (Go):**
```go
package main

import "fmt"

type User struct {
    ID    string
    Name  string
    Age   int
    Email *string // pointer for optional field
}

func FindUserByID(users []User, id string) *User {
    for i := range users {
        if users[i].ID == id {
            return &users[i]
        }
    }
    return nil
}

func main() {
    email := "alice@example.com"
    users := []User{
        {ID: "1", Name: "Alice", Age: 30, Email: &email},
        {ID: "2", Name: "Bob", Age: 25, Email: nil},
    }

    user := FindUserByID(users, "1")
    if user != nil {
        fmt.Printf("Found: %s\n", user.Name)
    }
}
```

### Example 2: Medium - Error Handling and JSON

**Before (TypeScript):**
```typescript
import * as fs from 'fs';

interface Config {
  host: string;
  port: number;
  debug: boolean;
}

class ConfigError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ConfigError";
  }
}

function loadConfig(path: string): Config {
  if (!fs.existsSync(path)) {
    throw new ConfigError(`Config file not found: ${path}`);
  }

  const content = fs.readFileSync(path, 'utf-8');

  try {
    const config = JSON.parse(content);

    if (!config.host || typeof config.port !== 'number') {
      throw new ConfigError("Invalid config format");
    }

    return config as Config;
  } catch (e) {
    if (e instanceof ConfigError) {
      throw e;
    }
    throw new ConfigError(`Failed to parse config: ${(e as Error).message}`);
  }
}

// Usage
try {
  const config = loadConfig("config.json");
  console.log(`Server running on ${config.host}:${config.port}`);
} catch (err) {
  if (err instanceof ConfigError) {
    console.error("Config error:", err.message);
    process.exit(1);
  }
}
```

**After (Go):**
```go
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type Config struct {
    Host  string `json:"host"`
    Port  int    `json:"port"`
    Debug bool   `json:"debug"`
}

type ConfigError struct {
    Message string
}

func (e *ConfigError) Error() string {
    return e.Message
}

func LoadConfig(path string) (*Config, error) {
    if _, err := os.Stat(path); os.IsNotExist(err) {
        return nil, &ConfigError{
            Message: fmt.Sprintf("config file not found: %s", path),
        }
    }

    content, err := os.ReadFile(path)
    if err != nil {
        return nil, &ConfigError{
            Message: fmt.Sprintf("failed to read config: %v", err),
        }
    }

    var config Config
    if err := json.Unmarshal(content, &config); err != nil {
        return nil, &ConfigError{
            Message: fmt.Sprintf("failed to parse config: %v", err),
        }
    }

    if config.Host == "" || config.Port == 0 {
        return nil, &ConfigError{
            Message: "invalid config format",
        }
    }

    return &config, nil
}

func main() {
    config, err := LoadConfig("config.json")
    if err != nil {
        var configErr *ConfigError
        if errors.As(err, &configErr) {
            fmt.Fprintf(os.Stderr, "Config error: %s\n", configErr.Message)
            os.Exit(1)
        }
    }

    fmt.Printf("Server running on %s:%d\n", config.Host, config.Port)
}
```

### Example 3: Complex - HTTP API with Async Operations

**Before (TypeScript):**
```typescript
import axios from 'axios';
import { EventEmitter } from 'events';

interface User {
  id: string;
  name: string;
  email: string;
}

interface Post {
  id: string;
  userId: string;
  title: string;
  content: string;
}

class APIClient extends EventEmitter {
  private baseURL: string;
  private cache: Map<string, any> = new Map();

  constructor(baseURL: string) {
    super();
    this.baseURL = baseURL;
  }

  async fetchUser(id: string): Promise<User> {
    const cacheKey = `user:${id}`;

    if (this.cache.has(cacheKey)) {
      this.emit('cache-hit', cacheKey);
      return this.cache.get(cacheKey);
    }

    try {
      const response = await axios.get<User>(`${this.baseURL}/users/${id}`);
      this.cache.set(cacheKey, response.data);
      this.emit('user-fetched', response.data);
      return response.data;
    } catch (error) {
      this.emit('error', error);
      throw new Error(`Failed to fetch user ${id}: ${error.message}`);
    }
  }

  async fetchUserPosts(userId: string): Promise<Post[]> {
    try {
      const response = await axios.get<Post[]>(
        `${this.baseURL}/users/${userId}/posts`
      );
      return response.data;
    } catch (error) {
      throw new Error(`Failed to fetch posts: ${error.message}`);
    }
  }

  async getUserWithPosts(userId: string): Promise<{ user: User; posts: Post[] }> {
    // Fetch in parallel
    const [user, posts] = await Promise.all([
      this.fetchUser(userId),
      this.fetchUserPosts(userId),
    ]);

    return { user, posts };
  }

  clearCache(): void {
    this.cache.clear();
    this.emit('cache-cleared');
  }
}

// Usage
async function main() {
  const client = new APIClient('https://api.example.com');

  client.on('user-fetched', (user: User) => {
    console.log('Fetched user:', user.name);
  });

  client.on('cache-hit', (key: string) => {
    console.log('Cache hit:', key);
  });

  client.on('error', (error: Error) => {
    console.error('API error:', error.message);
  });

  try {
    const result = await client.getUserWithPosts('123');
    console.log(`${result.user.name} has ${result.posts.length} posts`);

    // Second fetch will hit cache
    await client.fetchUser('123');
  } catch (error) {
    console.error('Failed:', error.message);
    process.exit(1);
  }
}

main();
```

**After (Go):**
```go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
)

type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

type Post struct {
    ID      string `json:"id"`
    UserID  string `json:"userId"`
    Title   string `json:"title"`
    Content string `json:"content"`
}

type Event struct {
    Type string
    Data interface{}
}

type APIClient struct {
    baseURL     string
    cache       map[string]interface{}
    cacheMu     sync.RWMutex
    eventCh     chan Event
    httpClient  *http.Client
}

func NewAPIClient(baseURL string) *APIClient {
    return &APIClient{
        baseURL:    baseURL,
        cache:      make(map[string]interface{}),
        eventCh:    make(chan Event, 100),
        httpClient: &http.Client{},
    }
}

func (c *APIClient) Events() <-chan Event {
    return c.eventCh
}

func (c *APIClient) emit(eventType string, data interface{}) {
    select {
    case c.eventCh <- Event{Type: eventType, Data: data}:
    default:
        // Don't block if no listeners
    }
}

func (c *APIClient) FetchUser(id string) (*User, error) {
    cacheKey := fmt.Sprintf("user:%s", id)

    // Check cache
    c.cacheMu.RLock()
    if cached, ok := c.cache[cacheKey]; ok {
        c.cacheMu.RUnlock()
        c.emit("cache-hit", cacheKey)
        return cached.(*User), nil
    }
    c.cacheMu.RUnlock()

    // Fetch from API
    url := fmt.Sprintf("%s/users/%s", c.baseURL, id)
    resp, err := c.httpClient.Get(url)
    if err != nil {
        c.emit("error", err)
        return nil, fmt.Errorf("failed to fetch user %s: %w", id, err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        err := fmt.Errorf("API returned status %d", resp.StatusCode)
        c.emit("error", err)
        return nil, err
    }

    var user User
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        c.emit("error", err)
        return nil, fmt.Errorf("failed to decode user: %w", err)
    }

    // Update cache
    c.cacheMu.Lock()
    c.cache[cacheKey] = &user
    c.cacheMu.Unlock()

    c.emit("user-fetched", &user)
    return &user, nil
}

func (c *APIClient) FetchUserPosts(userID string) ([]Post, error) {
    url := fmt.Sprintf("%s/users/%s/posts", c.baseURL, userID)
    resp, err := c.httpClient.Get(url)
    if err != nil {
        return nil, fmt.Errorf("failed to fetch posts: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
    }

    var posts []Post
    if err := json.NewDecoder(resp.Body).Decode(&posts); err != nil {
        return nil, fmt.Errorf("failed to decode posts: %w", err)
    }

    return posts, nil
}

type UserWithPosts struct {
    User  *User
    Posts []Post
}

func (c *APIClient) GetUserWithPosts(userID string) (*UserWithPosts, error) {
    // Fetch in parallel using goroutines
    type userResult struct {
        user *User
        err  error
    }
    type postsResult struct {
        posts []Post
        err   error
    }

    userCh := make(chan userResult, 1)
    postsCh := make(chan postsResult, 1)

    go func() {
        user, err := c.FetchUser(userID)
        userCh <- userResult{us

…(truncated)
