TypeScript Fundamentals
TypeScript adds static types to JavaScript. Compiled to JS for execution; types are checked at compile time. Modern frontend development is largely TypeScript; node back-ends increasingly are too.
This page covers the practical TypeScript that matters day-to-day.
Why TypeScript
The case:
- Catches bugs at compile time (typos, wrong types, undefined access)
- Better IDE support (autocomplete, refactoring, navigation)
- Self-documenting (types are documentation)
- Refactoring safety (rename a field; compiler finds all uses)
The cost:
- Compilation step
- Initial learning curve
- Some edge cases require type gymnastics
For most non-trivial JavaScript projects, TypeScript pays for itself.
The basics
Types
```typescript
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
let scores: number[] = [1, 2, 3];
let user: { name: string, age: number } = { name: "Bob", age: 25 };
```
Most of the time, you don't write the type — TypeScript infers it.
Functions
```typescript
function greet(name: string): string {
return `Hello, ${name}`;
}
const greet2 = (name: string): string => `Hello, ${name}`;
```
Parameters and return types specified; everything else inferred.
Interfaces and types
Two ways to declare object shapes:
```typescript
interface User {
id: string;
name: string;
email?: string; // optional
}
type User = {
id: string;
name: string;
email?: string;
};
```
Mostly interchangeable. Use whichever is conventional in your codebase.
Unions
```typescript
type Status = 'pending' | 'active' | 'cancelled';
```
A value of `Status` can only be one of those strings. Compiler checks.
Generics
```typescript
function first<T>(items: T[]): T | undefined {
return items[0];
}
const x = first([1, 2, 3]); // T inferred as number
const y = first(['a', 'b']); // T inferred as string
```
The `<T>` is a type parameter — fills in based on usage.
Structural typing
TypeScript types are structural, not nominal:
```typescript
interface Point { x: number; y: number; }
const p1: Point = { x: 1, y: 2 };
const p2: { x: number, y: number, z: number } = { x: 1, y: 2, z: 3 };
function distance(p: Point) { /* ... */ }
distance(p1); // OK
distance(p2); // Also OK — has the required fields
```
The shape matters; the name doesn't. This is unlike Java/C# (nominal typing).
Common patterns
Type narrowing
```typescript
function process(value: string | number) {
if (typeof value === 'string') {
// value is narrowed to string here
return value.toUpperCase();
}
// value is narrowed to number here
return value * 2;
}
```
The compiler tracks what type a variable has at each point.
Discriminated unions
```typescript
type Result =
| { kind: 'success', value: string }
| { kind: 'error', error: string };
function handle(r: Result) {
if (r.kind === 'success') {
// r.value is accessible here
} else {
// r.error is accessible here
}
}
```
The `kind` field discriminates between variants. Pattern matching emerges from narrowing.
Optional chaining and nullish coalescing
```typescript
const value = obj?.prop?.nested ?? 'default';
```
`?.` returns undefined if anything in the chain is null/undefined.
`??` provides a default for null/undefined (not for empty strings or zero).
Type assertions
```typescript
const elem = document.getElementById('foo') as HTMLDivElement;
```
Tell the compiler "trust me, it's this type." Use sparingly; it bypasses type checking.
Utility types
TypeScript has built-in utility types:
```typescript
Partial<User> // all fields optional
Required<User> // all fields required
Readonly<User> // all fields readonly
Pick<User, 'id' | 'name'> // just specific fields
Omit<User, 'email'> // exclude fields
```
Useful for transforming types.
Strict mode
Enable strict checks in `tsconfig.json`:
```json
{
"compilerOptions": {
"strict": true
}
}
```
This enables: noImplicitAny, strictNullChecks, strictFunctionTypes, etc. The strict mode catches dramatically more bugs than the loose mode.
For new projects, always start with strict.
Adoption strategies
New project
Start with TypeScript and strict mode. No transition.
Existing JavaScript project
Two paths:
Big bang
Convert everything at once. Risky; usually delayed indefinitely.
Incremental
Add `tsconfig.json` with `allowJs: true`. Convert files one at a time. New files are TS; existing files stay JS until touched.
The incremental path is realistic. Some files may stay JS for years; that's fine.
`any`
`any` is TypeScript's escape hatch — anything goes. Useful in transition; should be avoided in new code.
For unknown values, use `unknown` instead of `any`. `unknown` requires you to narrow before use.
TypeScript and React
TypeScript works well with React:
```typescript
type Props = {
name: string;
age?: number;
};
const Greeting: React.FC<Props> = ({ name, age }) => (
<div>Hello, {name}, age {age ?? 'unknown'}</div>
);
// Or
function Greeting({ name, age }: Props) {
return <div>Hello, {name}</div>;
}
```
The function-component declaration is increasingly preferred over `React.FC`.
What TypeScript doesn't fix
- Runtime errors that types can't model (network failures, etc.)
- Poor architecture (types don't make bad code good)
- Performance issues (type checking happens at compile time only)
- Bugs in third-party libraries
Common failure patterns
- **`any` everywhere.** Defeats the purpose.
- **Type assertions to make errors go away.** Hides real bugs.
- **Over-engineered types.** Conditional types, mapped types when simple types would do.
- **Not using strict mode.** Misses many bugs.
- **Hand-writing types for API responses.** Generate from OpenAPI/GraphQL schema.
Further Reading
- [ModernBundlersAndBuildTools](ModernBundlersAndBuildTools) — Build pipeline includes TS compilation
- [WebComponents](WebComponents) — Components benefit from typed props
- [FormHandlingAndValidation](FormHandlingAndValidation) — Type-safe form schemas
- [FrontendDevelopment Hub](FrontendDevelopmentHub) — Cluster index