Fundamentals
2 min readFundamentals
TL;DR
TypeScript fundamentals: type inference and annotations, unions and intersections, interfaces vs type aliases, generics with constraints, narrowing via guards and predicates, function overloads, class access modifiers, and core tsconfig settings. These are the building blocks for writing type-safe code and the vocabulary an interviewer expects before discussing advanced types or tooling.
How it works
Table of Contents
- Types and Inference
- Unions and Intersections
- Interfaces vs Type Aliases
- Generics
- Narrowing and Guards
- Functions and Overloads
- Classes and Access Modifiers
- Modules and tsconfig Basics
Types and Inference
let count = 0; // number inferred
let label: string = 'Tasks';
let isOpen = false; // boolean inferred
const ids = [1, 2, 3]; // number[] inferred
const mixed: (string | number)[] = [1, 'two'];
Use explicit annotations when inference is unclear or when defining public APIs.
Unions and Intersections
type Id = string | number;
function formatId(id: Id) {
return typeof id === 'number' ? id.toFixed(0) : id.toUpperCase();
}
type WithTimestamp = { createdAt: string };
type WithAuthor = { author: string };
type AuditRecord = WithTimestamp & WithAuthor;
Unions represent alternatives, intersections combine required fields.
Interfaces vs Type Aliases
interface User {
id: string;
name: string;
}
interface Admin extends User {
role: 'admin';
}
type Status = 'idle' | 'loading' | 'success' | 'error';
type ApiResponse = {
status: Status;
data?: unknown;
};
Use interfaces for object shapes you expect to extend, and type aliases for unions, primitives, and composable helpers.
Generics
function identity<T>(value: T): T {
return value;
}
const name = identity('Ada');
const countValue = identity(42);
function mapValues<T, U>(items: T[], mapper: (item: T) => U): U[] {
return items.map(mapper);
}
Add constraints to keep generics safe:
function toMap<T extends { id: string }>(items: T[]) {
return new Map(items.map((item) => [item.id, item]));
}
Narrowing and Guards
type Result = { ok: true; value: string } | { ok: false; error: string };
function handleResult(result: Result) {
if (result.ok) {
return result.value;
}
return result.error;
}
function isString(value: unknown): value is string {
return typeof value === 'string';
}
Use typeof, instanceof, in, and custom predicates to narrow types safely.
Functions and Overloads
function parseInput(input: string): number;
function parseInput(input: number): number;
function parseInput(input: string | number): number {
return typeof input === 'string' ? Number(input) : input;
}
const fromString = parseInput('42');
const fromNumber = parseInput(42);
Overloads let you expose precise call signatures while implementing a single function body.
Classes and Access Modifiers
class Session {
public readonly id: string;
private token: string;
constructor(id: string, token: string) {
this.id = id;
this.token = token;
}
refresh(newToken: string) {
this.token = newToken;
}
}
Use public, private, and protected to communicate intent and enforce encapsulation.
Modules and tsconfig Basics
export function toTitleCase(value: string) {
return value.replace(/\b\w/g, (char) => char.toUpperCase());
}
import { toTitleCase } from './strings';
Common tsconfig.json settings for frontend apps:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noImplicitAny": true
}
}