let varName: Type = value;
const varName: Type = value;
example
let orderId: string = "ORD-7821";
const maxRetries: number = 3;
let tags: string[] = ["urgent", "billing"];
// Often unnecessary when the type is obviouslet count = 0; // inferred as numberconst label = "total"; // inferred as literal "total"
output
// Explicit annotations override inference when needed
Note Let TypeScript infer when the type is obvious from the initializer. Add explicit annotations when: the inferred type is too wide, you are declaring without initializing, or you want documentation clarity.
annotate variabletype annotationdeclare typed variableexplicit type
Function Parameter Types
syntax
function fn(param: Type, param2: Type): ReturnType { ... }
example
function calculateTotal(
items: { name: string; price: number }[],
taxRate: number
): number {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
return subtotal * (1 + taxRate);
}
output
// Parameters MUST be annotated — TypeScript does not infer param types
Note Function parameters are never inferred from usage. Always annotate them. The return type is usually inferred correctly, but annotating it explicitly catches mistakes when the function body changes.
function parameter typetyped functionannotate parametersfunction arguments type
Note Explicit return types are especially valuable for public API functions, async functions, and functions with multiple return paths. They also speed up type-checking in large projects since the compiler does not need to analyze the function body.
return typefunction return typeasync return typePromise type
Type Inference
syntax
// No annotation needed — TypeScript figures it outlet varName = value;
example
let customerName = "Kenji"; // stringconst port = 8080; // literal type 8080 (const narrows)let results = [1, 2, 3]; // number[]let mixed = [1, "two", true]; // (string | number | boolean)[]// Return type inferencefunction double(n: number) {
return n * 2; // inferred return: number
}
output
// const uses literal types; let uses widened types
Note const declarations infer the narrowest (literal) type, while let declarations widen to the base type. This is called 'widening'. Use 'as const' to get literal types with let, or on objects/arrays to make them deeply readonly with literal types.
type inferenceinferred typeautomatic typewideninglet vs const inference
// Assertions override the compiler — they do NOT perform runtime conversion
Note Type assertions are a compile-time escape hatch — no runtime checking or conversion happens. If you assert incorrectly, you get silent bugs. Prefer type guards (typeof, instanceof) when possible. The angle-bracket syntax <Type>value conflicts with JSX — always use 'as Type' in .tsx files.
type assertionas keywordcast typeoverride typeangle bracket assertion
Function Overloads
syntax
function fn(param: TypeA): ReturnA;
function fn(param: TypeB): ReturnB;
function fn(param: TypeA | TypeB): ReturnA | ReturnB { ... }
example
function parseInput(input: string): string[];
function parseInput(input: number): number[];
function parseInput(input: string | number): string[] | number[] {
if (typeof input === "string") {
return input.split(",");
}
return Array.from({ length: input }, (_, i) => i);
}
const words = parseInput("a,b,c"); // string[]const nums = parseInput(5); // number[]
output
// Caller sees the specific overload, not the implementation signature
Note The implementation signature is not callable directly — only the overload signatures are visible to callers. Order matters: TypeScript picks the first matching overload. Put more specific signatures before general ones.
function overloadmultiple signaturesoverloaded functiondifferent return types