Skip to content

TypeScript In-Depth Guide

Preface

You already know JavaScript, but you may have encountered these problems:

  • Assigning the wrong type to a variable, only discovering it at runtime
  • Misspelling an object property name and spending ages debugging
  • Function parameter types being wrong, leading to constant rewrites

TypeScript is the tool that helps you catch these issues before your code runs. After reading this, you'll understand why TypeScript improves code quality, how to read type annotations, interfaces, generics, and other core concepts, and how to better leverage AI-generated code in vibecoding.

What will this article teach you?

ChapterContentWhat you'll be able to do
Chapter 1What TypeScript isUnderstand its relationship with JavaScript
Chapter 2Basic type annotationsKnow how to annotate types for variables
Chapter 3Object types and interfacesDefine types for data structures
Chapter 4Function typesAnnotate types for function parameters and return values
Chapter 5GenericsWrite reusable, type-safe code
Chapter 6Type inference and practical tipsKnow when explicit annotations are needed

1. What Is TypeScript

🤔 Core Question

JavaScript already works — why do we need TypeScript? Is learning an extra syntax worth it?

1.1 From "Runtime Errors" to "Compile-Time Discovery"

🔴 JavaScript's Pain Points

  • Type errors only caught at runtime
  • Typos are hard to spot
  • Refactoring is error-prone
  • IDE hints are not accurate enough

Like a document editor without spell check

✅ TypeScript's Advantages

  • Errors found while writing code
  • Smarter, more accurate IntelliSense
  • Safer refactoring
  • Easier code maintenance

Like an editor with spell check and syntax highlighting

Understand their relationship in one sentence:

TechnologyAnalogyRole
JavaScriptRaw materialCode that can run directly
TypeScriptBlueprint + quality checkAdds type checking to JavaScript, then compiles to JavaScript

1.2 Why Does Vibecoding Need TypeScript Too?

AI-Generated Code Can Also Have Bugs

A developer used AI to generate a user management feature. The AI-written JavaScript code ran, but there was a problem: the user's age was supposed to be a number, but sometimes it was mistakenly assigned as a string.

As a result, when checking "is the user an adult," the string "25" was treated as a string, causing the check to fail. This bug was hidden for a long time until a user entered a non-numeric character and it surfaced.

With TypeScript, this code would have errored at write time: Type 'string' is not assignable to type 'number'.

This is the value of TypeScript — when AI gets a type wrong, you can catch it immediately.

1.3 TypeScript Actually Looks Like This

TypeScript is not a brand-new language; it's simply a "superset" of JavaScript:

typescript
// This is valid JavaScript, and also valid TypeScript
const name = "Zhang San"
const age = 25
function greet(user) {
  return `Hello ${user}`
}

// This is TypeScript-specific type annotation
const name2: string = "Li Si"
const age2: number = 30
function greet2(user: string): string {
  return `Hello ${user}`
}

Key takeaways:

  • All JavaScript code is valid TypeScript code
  • TypeScript adds optional type annotations
  • TypeScript ultimately compiles to JavaScript for execution

💡 Core Insight

TypeScript doesn't change how your code runs; it just checks that types are correct at compile time. You can adopt TypeScript incrementally — start by adding types to critical variables.


2. Basic Type Annotations

🤔 Core Question

How do you tell TypeScript what type a variable should be? What is the syntax for type annotations?

2.1 Type Annotation Syntax

A type annotation is simply adding : type after the variable name:

typescript
// Syntax: variableName: type = value
const name: string = "Zhang San"
let age: number = 25
let isStudent: boolean = true

👇 Try it yourself: Add type annotations to variables

📝 TypeScript Type Annotation Demo

stringname
Alice
const name: string = "Alice"
numberage
25
const age: number = 25
booleanisActive
true
const isActive: boolean = true
JavaScript (no type checking)
let name = "Alice"
name = 123  // Runtime error may appear much later
TypeScript (compile-time checking)
let name: string = "Alice"
name = 123  // Compile-time error appears immediately
🔍 Why don't some places need type annotations?

TypeScript can automatically infer types based on assignment:

typescript
// These don't need type annotations — TypeScript can infer them
const name = "Zhang San"      // inferred as string
const age = 25                // inferred as number
const isActive = true         // inferred as boolean

// These cases need explicit annotations
let data  // ❌ Error: cannot infer type
let data: any  // ✅ Works, but loses the benefit of type checking

function add(a, b) {  // ❌ Parameter types are unclear
  return a + b
}

function add2(a: number, b: number): number {  // ✅ Types are explicit
  return a + b
}

2.2 Basic Types

TypeScript supports all of JavaScript's basic types:

TypeDescriptionExample
stringString"hello", '你好'
numberNumber (integers and decimals)42, 3.14
booleanBooleantrue, false
null / undefinedNull valuenull, undefined
arrayArraynumber[], string[]
objectObject{ name: string; age: number }

Two ways to write array types:

typescript
// Method 1: type[] (more common)
const numbers: number[] = [1, 2, 3, 4, 5]
const names: string[] = ["Zhang San", "Li Si", "Wang Wu"]

// Method 2: Array<type>
const numbers2: Array<number> = [1, 2, 3, 4, 5]
const names2: Array<string> = ["Zhang San", "Li Si", "Wang Wu"]

Special types:

typescript
// any: any type (use sparingly — effectively disables type checking)
let data: any = 42
data = "now it can be a string"
data = { name: "Zhang San" }  // can also be an object

// unknown: type-safe any
let value: unknown = 42
// if (typeof value === "number") {
//   console.log(value + 10)  // must check type first before using
// }

// void: no return value
function log(message: string): void {
  console.log(message)
}

// never: never returns
function error(message: string): never {
  throw new Error(message)
}

💡 Recognition Tips

  • See : string → this is a string type annotation
  • See : number[] → this is a number array annotation
  • See : void → this function has no return value

3. Object Types and Interfaces

🤔 Core Question

How do you define the type of an object? What types should the object's properties be?

3.1 Interface: Defining an Object's "Shape"

Interfaces are the primary way to define object types in TypeScript:

typescript
// Define a User interface
interface User {
  id: number
  name: string
  email: string
  age?: number  // optional property
}

// Use the interface
const user: User = {
  id: 1,
  name: "Zhang San",
  email: "zhangsan@example.com",
  age: 25
}

// age is optional, so it can be omitted
const user2: User = {
  id: 2,
  name: "Li Si",
  email: "lisi@example.com"
}

👇 Try it yourself: Create objects that conform to an interface definition

🎯 Interface Demo

User Interface Definition
interface User {
  id: number
  name: string
  email: string
  age: number
}
👤
ID:1number
Age:25number
✅ Correct usage
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  age: 25
} // fully matches the type
❌ Incorrect usage
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  age: "25"  // error: age should be number, not string
}
🔍 Other Interface Features
typescript
// Readonly properties
interface User {
  readonly id: number  // id cannot be modified after creation
  name: string
}

const user: User = {
  id: 1,
  name: "Zhang San"
}

user.id = 2  // ❌ Error: cannot modify a readonly property
user.name = "Li Si"  // ✅ Can modify

// Function types
interface User {
  name: string
  greet: () => string  // greet is a function that returns string
}

const user: User = {
  name: "Zhang San",
  greet: () => "Hello"
}

// Interface inheritance
interface Admin extends User {
  permissions: string[]
}

const admin: Admin = {
  name: "Admin",
  greet: () => "Hello Admin",
  permissions: ["read", "write", "delete"]
}

3.2 Type Alias

Besides interfaces, you can also use type to define type aliases:

typescript
// Type alias
type User = {
  id: number
  name: string
  email: string
}

// Union types
type Status = "pending" | "success" | "error"

const status: Status = "success"  // ✅
// const status2: Status = "failed"  // ❌ Error: not in the union type

// Intersection types (merge multiple types)
type User = {
  id: number
  name: string
}

type Timestamp = {
  createdAt: Date
  updatedAt: Date
}

type UserWithTimestamp = User & Timestamp

const user: UserWithTimestamp = {
  id: 1,
  name: "Zhang San",
  createdAt: new Date(),
  updatedAt: new Date()
}

Interface vs Type Alias:

Featureinterfacetype
Extensionextends& intersection
Duplicate declarationAuto-mergesErrors
Use casesObject shapes, classesUnion types, intersection types, primitive type aliases

💡 Recognition Tips

  • See interface → defining an object type
  • See type → creating a type alias
  • See ? → optional property
  • See readonly → readonly property

4. Function Types

🤔 Core Question

How do you annotate types for function parameters and return values?

4.1 Parameter Types and Return Types

typescript
// Complete function type annotation
function add(a: number, b: number): number {
  return a + b
}

// Arrow function
const multiply = (a: number, b: number): number => {
  return a * b
}

// No return value
function log(message: string): void {
  console.log(message)
}

// Returning multiple types (union type)
function parseInput(input: string): number | string {
  const num = parseFloat(input)
  return isNaN(num) ? input : num
}

4.2 Optional and Default Parameters

typescript
// Optional parameter (marked with ?)
function greet(name: string, title?: string): string {
  return title ? `${title} ${name}` : name
}

greet("Zhang San")  // "Zhang San"
greet("Zhang San", "Mr.")  // "Mr. Zhang San"

// Default parameter
function greet2(name: string, title: string = "friend"): string {
  return `${title} ${name}`
}

greet2("Li Si")  // "friend Li Si"
greet2("Li Si", "Dr.")  // "Dr. Li Si"

4.3 Function Types as Parameters

typescript
// Accepting a function as a parameter
function calculate(
  a: number,
  b: number,
  operation: (x: number, y: number) => number
): number {
  return operation(a, b)
}

calculate(10, 5, (x, y) => x + y)  // 15
calculate(10, 5, (x, y) => x * y)  // 50

// Cleaner approach: define the function type first
type Operation = (x: number, y: number) => number

function calculate2(
  a: number,
  b: number,
  operation: Operation
): number {
  return operation(a, b)
}

💡 Recognition Tips

  • See (a: number, b: number) => number → this is a function type, describing parameters and return value
  • See : void → the function has no return value
  • See ? → the parameter is optional

5. Generics

🤔 Core Question

How do you write code that handles multiple types while staying type-safe?

5.1 Basic Concepts of Generics

Generics let you define functions, interfaces, or classes without specifying concrete types upfront — you specify them at usage time:

typescript
// Generic function: T is a type variable
function identity<T>(arg: T): T {
  return arg
}

// Explicitly specify the type at usage
const num1 = identity<number>(42)  // type is number
const str1 = identity<string>("hello")  // type is string

// Type inference: TypeScript can infer automatically
const num2 = identity(42)  // inferred as number
const str2 = identity("hello")  // inferred as string

👇 Try it yourself: Use generics to handle different types of data

🔄 Generics Demo

💡
Generics are reusable templates - They handle different data types while preserving type safety
Generic Function Definition
// T is a type variable decided when the function is used
function identity<T>(arg: T): T {
  return arg
}

// Generic array reversal
function reverseArray<T>(arr: T[]): T[] {
  return [...arr].reverse()
}

📝 Generic Usage Examples

Number array
const nums = [1, 2, 3, 4, 5]
const reversed = reverseArray<number>(nums)
// result: [5, 4, 3, 2, 1]
// type: number[]
String array
const strs = ["a", "b", "c"]
const reversed = reverseArray<string>(strs)
// result: ["c", "b", "a"]
// type: string[]

5.2 Generic Constraints

Restrict generics to meet certain conditions:

typescript
// Constrain T to have a length property
interface HasLength {
  length: number
}

function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length)
}

logLength("hello")  // ✅ strings have length
logLength([1, 2, 3])  // ✅ arrays have length
// logLength(42)  // ❌ numbers don't have a length property

5.3 Generic Interfaces and Classes

typescript
// Generic interface
interface Box<T> {
  value: T
  getValue(): T
}

const numberBox: Box<number> = {
  value: 42,
  getValue: () => 42
}

const stringBox: Box<string> = {
  value: "hello",
  getValue: () => "hello"
}

// Generic class
class Storage<T> {
  private items: T[] = []

  add(item: T): void {
    this.items.push(item)
  }

  get(index: number): T {
    return this.items[index]
  }
}

const numberStorage = new Storage<number>()
numberStorage.add(1)
numberStorage.add(2)
// numberStorage.add("string")  // ❌ Error

const stringStorage = new Storage<string>()
stringStorage.add("hello")
// stringStorage.add(1)  // ❌ Error

💡 Recognition Tips

  • See <T> → this is a generic type variable
  • See <T extends SomeType> → generic constraint
  • See Array<T> or Promise<T> → built-in generic types

6. Type Inference and Practical Tips

🤔 Core Question

When do you need explicit type annotations? When can you rely on inference?

6.1 Type Inference

TypeScript can automatically infer types from context:

typescript
// Inference during variable initialization
const name = "Zhang San"  // inferred as string
const age = 25  // inferred as number
const isActive = true  // inferred as boolean

// Array inference
const numbers = [1, 2, 3]  // inferred as number[]
const mixed = [1, "hello", true]  // inferred as (number | string | boolean)[]

// Function return value inference
function add(a: number, b: number) {
  return a + b  // return value inferred as number
}

👇 Try it yourself: Observe how TypeScript infers types

🔮 Type Inference Demo

🧠

What is type inference?

TypeScript can infer variable types from your code, so you do not need to annotate every value manually.

Choose an example to see how type inference works:

let name = "Alice"
→ string
let age = 25
→ number
let isActive = true
→ boolean
let numbers = [1, 2, 3]
→ number[]
💻Code
let name = "Alice"
🏷️Inferred type
string
💡
TypeScript infers that name has type string from the assigned string value.

📚 Best Practices

When to use inference
  • A variable has a clear initial value
  • The function return type is obvious
  • The assignment is a simple literal
When to add annotations
  • Function parameters are required
  • Objects or arrays have complex structure
  • The initial value cannot reveal the type
  • You need an explicit type constraint

🔄 Type Inference vs Explicit Annotation

Function return value
Use inference
function add(a: number, b: number) {
  return a + b  // inferred as number
}
Explicit annotation
function add(a: number, b: number): number {
  return a + b
}
Inference is recommended
Complex object
Use inference
const user = {
  name: "Alice",
  age: 25,
  email: "test@example.com"
}  // type is inferred automatically
Explicit annotation
interface User {
  name: string
  age: number
  email: string
}

const user: User = { ... }
Use an interface for complex structures

6.2 When to Use Explicit Type Annotations

Scenarios where type inference is recommended
typescript
// ✅ Recommended: simple literal assignments
const count = 0
const name = "Zhang San"
const isActive = true

// ✅ Recommended: function return values that can be inferred
function getUserId(user: User) {
  return user.id  // inferred as number
}
Scenarios where explicit annotations are recommended
typescript
// ✅ Recommended: function parameters (required)
function add(a: number, b: number) {
  return a + b
}

// ✅ Recommended: object property types are unclear
const user: {
  id: number
  name: string
  metadata: Record<string, any>
} = {
  id: 1,
  name: "Zhang San",
  metadata: {}  // might be inferred as {}, needs explicit specification
}

// ✅ Recommended: complex function return types
function getUser(): User | null {
  // ...
  return null
}

// ✅ Recommended: public APIs
export function calculateTotal(prices: number[]): number {
  return prices.reduce((sum, price) => sum + price, 0)
}

6.3 Type Guards

Check types at runtime:

typescript
// typeof type guard
function processValue(value: string | number) {
  if (typeof value === "string") {
    // TypeScript knows value is string here
    console.log(value.toUpperCase())
  } else {
    // TypeScript knows value is number here
    console.log(value * 2)
  }
}

// instanceof type guard
class Dog {
  bark() {
    console.log("Woof")
  }
}

class Cat {
  meow() {
    console.log("Meow")
  }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark()  // TypeScript knows this is Dog
  } else {
    animal.meow()  // TypeScript knows this is Cat
  }
}

// Custom type guard
interface User {
  name: string
  email: string
}

function isUser(value: any): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    typeof value.name === "string" &&
    typeof value.email === "string"
  )
}

function processValue(value: unknown) {
  if (isUser(value)) {
    // value is User here
    console.log(value.name)
  }
}

6.4 Utility Types

TypeScript provides some built-in utility types:

typescript
// Partial: make all properties optional
interface User {
  id: number
  name: string
  email: string
}

type PartialUser = Partial<User>
// Equivalent to: { id?: number; name?: string; email?: string }

// Required: make all properties required
type RequiredUser = Required<PartialUser>
// Equivalent to: { id: number; name: string; email: string }

// Pick: keep only specified properties
type UserBasicInfo = Pick<User, "id" | "name">
// Equivalent to: { id: number; name: string }

// Omit: exclude specified properties
type UserWithoutEmail = Omit<User, "email">
// Equivalent to: { id: number; name: string }

// Record: create an object type
type UserRoles = Record<string, boolean>
// Equivalent to: { [key: string]: boolean }

7. Practical Tips: Using TypeScript in Vibecoding

🤔 Core Question

How can you better leverage TypeScript in AI-assisted development?

7.1 Getting AI to Generate Type-Safe Code

❌ Bad prompt:

Write a user management feature for me

✅ Good prompt:

Write a user management feature for me, using TypeScript.

The data structure is defined as follows:
interface User {
  id: number
  name: string
  email: string
  age: number
}

I need to implement:
1. Get user list: returns User[]
2. Create user: accepts Partial<User>, returns User
3. Update user: accepts id and Partial<User>, returns User
4. Delete user: accepts id, returns void

Please ensure all functions have complete type annotations.

7.2 Understanding TypeScript Error Messages

Common errors and their meanings:

Error MessageMeaningSolution
Type 'X' is not assignable to type 'Y'Type X cannot be assigned to type YCheck if types match, or perform type conversion
Property 'X' does not exist on type 'Y'Property X does not exist on type YCheck property name spelling, or define the property
Argument of type 'X' is not assignable to parameter of type 'Y'Parameter type mismatchCheck the argument types when calling the function
Type 'X' is missing the following properties from type 'Y'Type X is missing certain properties from type YAdd the missing properties

7.3 Incrementally Adopting TypeScript

If you have a JavaScript project, you can gradually migrate to TypeScript:

  1. Step 1: Rename files to .ts

    bash
    # From utils.js to utils.ts
    mv utils.js utils.ts
  2. Step 2: Fix obvious type errors

    typescript
    // If you get: Parameter 'a' implicitly has an 'any' type
    // Add type annotations
    function add(a: number, b: number) {
      return a + b
    }
  3. Step 3: Gradually add type definitions

    typescript
    // First use any for a quick fix
    function processUser(user: any) {
      // ...
    }
    
    // Later refine the types
    interface User {
      id: number
      name: string
    }
    
    function processUser(user: User) {
      // ...
    }
  4. Step 4: Enable stricter type checking

    json
    // tsconfig.json
    {
      "compilerOptions": {
        "strict": true,  // Enable strict mode
        "noImplicitAny": true,  // Disallow implicit any
        "strictNullChecks": true  // Strict null checks
      }
    }

8. What You Should Now Be Able to Recognize

  • See : string → this is a string type annotation
  • See : number[] → this is a number array annotation
  • See interface User → this defines an object type
  • See type User = → this is a type alias
  • See <T> → this is a generic
  • See extends → interface inheritance or generic constraint
  • See ? → optional property
  • See readonly → readonly property
  • See | → union type
  • See & → intersection type

If you read the "deep dive" section of each chapter carefully, you've also mastered these core concepts:

  • Type annotations: Explicitly tell TypeScript the type of a variable
  • Interfaces: Define the structure and types of objects
  • Generics: Write reusable, type-safe code
  • Type inference: TypeScript automatically infers types
  • Type guards: Check types at runtime
  • Utility types: Partial, Required, Pick, Omit, and more

💡 When you run into problems, talk to AI like this:

  • "How should I write the type annotation for this function? The parameter is X, the return value is Y"
  • "Help me define an interface that describes this data structure: ..."
  • "What does this TypeScript error mean? How do I fix it?"
  • "How do I add a constraint to this generic function to ensure T must have a certain property?"