02. TypeScript Basics - Types and Syntax
🎯 Master TypeScript's type system! Learn primitive types, arrays, tuples, enums, type assertions, union/intersection types, and literal types with practical examples.
What we will learn in this post?
- 👉 Basic TypeScript Types
- 👉 Type Inference in TypeScript
- 👉 Arrays and Tuples
- 👉 Enums in TypeScript
- 👉 Type Assertions and Type Casting
- 👉 Union and Intersection Types
- 👉 Literal Types and Type Aliases
Welcome to TypeScript’s Basic Types! 🧑💻
TypeScript adds type safety to JavaScript, helping you catch errors early and write more robust code. Let’s explore its fundamental types!
The Everyday Essentials ✨
These are your go-to types for common data:
Number 🔢
For any numeric value, whether whole (integer) or decimal (float).
1
2
let age: number = 30;
let price: number = 19.99; // Use for quantities, costs, etc.
String 📝
For text! Enclose content in single (''), double (""), or backticks () for template literals.
1
2
let username: string = "Alice";
let greeting: string = `Hello, ${username}!`; // For names, messages, descriptions.
Boolean ✅
For simple true/false values, perfect for logical checks or flags.
1
2
let isActive: boolean = true;
let hasPermission: boolean = false; // For toggles, status indicators.
Special Mentions 💡
Null & Undefined 👻
These represent an absence of a value. null is an intentional absence, while undefined means a variable hasn’t been assigned anything yet.
1
2
let data: string | null = null; // Data might be absent.
let notAssigned: string | undefined; // Defaults to undefined if not initialized.
Void 🚫
Used for functions that don’t return any value. They just perform an action.
1
2
3
function logMessage(msg: string): void {
console.log(msg); // This function logs, but returns nothing.
}
Flexibility vs. Safety: Any vs. Unknown 🚧
Any (The Wildcard) 🃏
Bypasses all type checking. Use any sparingly when you truly don’t know the type, or are migrating legacy JavaScript. It turns off TypeScript’s safety net.
1
2
3
let whatever: any = "hello";
whatever = 123; // No error! (TypeScript doesn't check)
whatever.toUpperCase(); // Possible runtime error if 'whatever' is 123
Unknown (The Safer Wildcard) ❓
Like any, unknown can hold any value, but TypeScript forces you to narrow its type (check what it is) before performing operations. This promotes type safety.
1
2
3
4
5
let mystery: unknown = "secret";
// mystery.toUpperCase(); // Error: Object is of type 'unknown'.
if (typeof mystery === 'string') {
console.log(mystery.toUpperCase()); // OK, type narrowed to string
}
When to choose:
any: Avoid if possible. Use for quick fixes or deeply dynamic data that’s hard to type.unknown: Prefer for values coming from external sources (APIs, user input) where the exact type isn’t guaranteed. It forces explicit, safe type checks.
graph TD
A["🚀 Need to Handle Any Value?"]:::style1 -->|"✅ Yes"| B{"Prioritize Type Safety?"}:::style3
B -->|"✅ Yes, strongly (recommended)"| C["🛡️ Use 'unknown'"]:::style4
B -->|"❌ No (legacy code, quick hack)"| D["🃏 Use 'any'"]:::style7
A -->|"❌ No (Know the value's type)"| E["🎯 Use Specific Type (number, string, etc.)"]:::style6
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style7 fill:#9e9e9e,stroke:#616161,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
TypeScript Arrays & Tuples: Your Data Squad! 🚀
Let’s make our data lists super organized with TypeScript! It helps us know exactly what’s inside.
Basic Arrays: Organized Lists ✨
TypeScript lets you specify what type of items an array holds. For numbers, use number[] or Array<number>. For strings, string[] or Array<string>.
- Example:
let scores: number[] = [85, 92, 78];
Multi-dimensional Arrays: Arrays of Arrays 🗺️
Need arrays within arrays, like a grid? Just add more [] to your type!
- Example:
let matrix: number[][] = [[1, 2], [3, 4]];
Tuples: Fixed-Length, Mixed Types 🤝
Tuples are special arrays with a fixed number of elements, where each position has a specific, known type. Great for profiles or coordinates!
- Example:
let userProfile: [string, number, boolean] = ["Alice", 30, true];- Here,
userProfilemust have exactly three elements: a string, then a number, then a boolean.
- Here,
Readonly Arrays: Immutable Data 🔒
To ensure an array’s contents can’t be changed after creation, use the readonly keyword.
- Example:
let fixedNumbers: readonly number[] = [10, 20];fixedNumbers.push(30);// ❌ Error! You can’t modify a readonly array.
Array Methods: Business as Usual 🛠️
Standard JavaScript array methods like .push(), .map(), and .filter() work just as you’d expect, respecting all your TypeScript types!
- Example:
scores.push(95);// ✅ Valid, asscoresis an array of numbers.
graph TD
A["📊 Data Lists"]:::style1 --> B["📦 Arrays"]:::style2
A --> C["🎯 Tuples"]:::style4
B -- "Same Type" --> B1["🔢 number[]"]:::style5
B -- "Multi-dim" --> B2["📝 string[][]"]:::style6
C -- "Fixed Length, Mixed Types" --> C1["🤝 [string, number, boolean]"]:::style3
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
TypeScript Enums: Your Constant Companions 🤝
Need to represent a fixed set of meaningful values like North or Pending clearly? TypeScript Enums (enum) are perfect! They let you define named constants, making your code super readable and reducing errors from “magic numbers” or hardcoded strings. Think of them as a way to give names to numbers or strings that have special meaning.
Enum Types & Purpose 💡
Enums boost readability and provide type safety. Here are the common kinds:
- Numeric Enums: Assign numerical values (defaulting
0,1, etc. if not specified). Great for status codes or directions.1 2
enum Direction { North, East, South } // North is 0, East is 1 let myWay: Direction = Direction.North;
- String Enums: Use actual readable string values. Very useful for API responses or logging categories.
1 2
enum Status { Pending = "PENDING", Approved = "APPROVED" } let orderStatus: Status = Status.Approved;
- Heterogeneous Enums: A mix of numeric and string members (less common, but possible).
1
enum Mixed { No = 0, Yes = "YES" }
Compilation & Const Enums ✨
When TypeScript compiles, regular enums typically become JavaScript objects, allowing you to access both the name from the value and vice-versa. This offers flexibility but adds minor runtime overhead.
Optimize with const enum 🚀
For maximum performance, use const enum. These completely disappear at compile time, replaced directly by their literal values in the JavaScript output. They’re ideal when enum values are known before runtime, like Direction.North becoming 0. This makes your JavaScript leaner and faster!
1
2
const enum LogLevel { DEBUG, INFO }
console.log(LogLevel.INFO); // Compiles to console.log(1);
TypeScript Type Assertions: Your Guiding Hand! 👋
Sometimes, you know a variable’s type better than TypeScript. Assertions tell the compiler, “Trust me!” They’re compile-time hints, not runtime conversions.
Two Ways to Assert! ✨
You can use:
- Angle-Bracket:
<string>myVar asSyntax:myVar as string(preferred, especially in JSX).- Example:
let val: any = "text"; let len = (val as string).length;
- Example:
Assertion ≠ Conversion! 💡
Assertions don’t change data. (someValue as number) won’t convert "5" to 5. It just tells TS how to treat it. If someValue is a string but asserted as number, you risk a runtime error using number methods.
The “I’m Sure it’s Not Null!” Operator ! 🛡️
The non-null assertion ! tells TypeScript: “This value isn’t null or undefined.”
- Example:
document.getElementById('id')!.value. Use only when certain, as it bypasses critical null safety checks.
When & How to Assert Wisely 🤔
Use sparingly when TypeScript can’t infer correctly (e.g., document.getElementById) or with any types. Best Practice: Only when absolutely certain of the type. as Type is generally favored. Overuse hides potential bugs.
Understanding Union & Intersection Types in TypeScript ✨
TypeScript helps us define variable types precisely. But what if a variable can be one of several types, or needs all properties from multiple types? Let’s explore!
Union Types: “OR” Logic 🧩
A variable can be either Type1 or Type2. We use the pipe symbol (|) to define them.
- How it works:
let id: number | string;meansidcan hold anumberor astring. - Why use it: Great for flexible data, like an ID that might be numeric or text.
Intersection Types: “AND” Logic 🤝
Intersection types combine all properties from Type1 and Type2 into a single new type. We use the ampersand symbol (&).
- How it works:
type FullUser = User & Permissions;creates aFullUserwith all properties fromUserandPermissions. - Why use it: Useful for combining functionalities or merging object shapes.
Type Guards & Narrowing: Staying Safe! 🛡️
When using union types, TypeScript needs to know the specific type at runtime. This process is called narrowing. We achieve this using type guards. A common guard is the typeof operator.
- How it works:
typeofchecks a variable’s type, intelligently narrowing it within a code block. This allows safe access to type-specific methods.
1
2
3
4
5
6
7
function processId(input: number | string) {
if (typeof input === "string") {
console.log(input.toUpperCase()); // input is now 'string'
} else {
console.log(input.toFixed(2)); // input is now 'number'
}
}
graph TD
A["🚀 Start: Variable with Union Type (number | string)"]:::style1 --> B{"🛡️ Use Type Guard (typeof)?"}:::style3
B -- "✅ Yes, typeof === string" --> C["📝 Type Narrowed to string"]:::style4
B -- "❌ No, else" --> D["🔢 Type Narrowed to number"]:::style5
C --> E["✅ Safely use string methods"]:::style6
D --> F["✅ Safely use number methods"]:::style6
E --> G["🏁 End: Type-safe operation"]:::style2
F --> G
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Practical Applications 💡
- Unions:
- Handling diverse API responses (e.g.,
SuccessData | ErrorData). - Defining flexible component props that accept different data types.
- Handling diverse API responses (e.g.,
- Intersections:
- Creating complex objects by merging simpler types.
- Implementing mixins for shared functionalities.
For more info: TypeScript Handbook - Everyday Types
Super Specific Types & Nicknames! 🎯
Ever need a type that’s exactly a certain value, not just any string or number? That’s where literal types come in! Instead of string, you can say let x: 'hello' | 'world', meaning x can only be the string "hello" or "world". It’s like a very specific menu!
Here are some examples:
- String Literals:
let color: 'red' | 'blue';🟥 (Can only be ‘red’ or ‘blue’) - Numeric Literals:
let level: 1 | 2 | 3;🔢 (Can only be 1, 2, or 3)
Type Aliases: Your Code’s Nickname Generator! 🏷️
When types get a bit long or complex, type aliases (type keyword) are your best friend! They let you create a friendly custom name for any type definition. It makes your code much cleaner and easier to read.
type UserID = string | number;- Now, instead of writing
string | numberrepeatedly, you can just useUserID.
Combining Them for Super Clarity! 🤝
Mixing literal types with type aliases makes your code incredibly readable and maintainable! You can give a simple name to a set of exact values.
Let’s define allowed directions for a game:
1
2
3
type Direction = 'north' | 'south' | 'east' | 'west';
let playerMovement: Direction = 'east'; // This is valid!
// playerMovement = 'up'; // ❌ Error! 'up' isn't a valid Direction.
This clearly states what values playerMovement can hold.
graph LR
A["🎯 Literal Types"]:::style1 --"Define Exact Values"--> B["💡 e.g., 'on' | 'off'"]:::style3
C["🏷️ Type Aliases"]:::style4 --"Give Custom Names"--> D["📝 e.g., type Status = ..."]:::style5
B --"Combined for Clarity"--> E["✨ type ToggleStatus = 'on' | 'off'"]:::style6
D --"Makes Code More Readable"--> E
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
For more details, check out the TypeScript Handbook on Literal Types and Type Aliases.
🎓 Conclusion
Congratulations on mastering TypeScript’s type system! You’ve learned how to leverage primitive types, arrays, tuples, enums, and advanced features like union/intersection types and type aliases to write safer, more maintainable code. By understanding type guards and narrowing, you can now handle dynamic data confidently while maintaining TypeScript’s compile-time safety guarantees. These fundamentals form the foundation for building robust applications with TypeScript.