TypeScript Fundamentals
TypeScript is a statically-typed superset of JavaScript from Microsoft. Every valid JavaScript file is also valid TypeScript, but on top sits a layer of optional type annotations that are checked at compile time. The point beginners miss: that layer exists only until the code runs. Neither the browser nor Node understands TypeScript — the tsc compiler transpiles it to plain JavaScript and fully erases the types. A type error is a compile error, not a runtime one, and the annotation gives no runtime guarantee.
That single fact — types live only at compile time — is the root of almost everything else. The compiler rarely makes you write types by hand: it infers them, and it does so in two directions. Bottom-up it reads a value (let n = 10 → number); top-down — contextually — it takes the type from the position the expression sits in, which is why a callback parameter needs no annotation while the same arrow hoisted into a bare const suddenly fails with implicit any. Inferring from a literal triggers widening: a mutable binding gets the base type (string, number), an immutable one keeps the exact literal — hence the topic's signature question about let versus const, and the classic bug where a widened discriminant breaks a discriminated union. Finally, let/const are block-scoped while var is function-scoped and hoisted, and const fixes only the reference, it does not make an object immutable. The layers below walk these mechanisms in order.
Topic map
- Static Typing — what "type checking at compile time" means, structural typing by shape, and why there are no runtime checks left.
- Transpilation — how
tscturns TypeScript into plain JavaScript, erases the types, and which syntax still emits runtime code. - Type Annotations — the
: Typesyntax on variables, parameters, and return values, why it is optional, and howanydiffers from an omitted type. - Type Inference — how the compiler determines a type bottom-up from a value, a function body, and the best common type, and why inference is not
any. - Contextual Typing — the second, top-down direction of inference: a callback parameter's type comes from the signature, and a hoisted function loses the context.
- Type Widening — why
letwidens a literal to its base type, why object properties always widen, and how that breaks a discriminated union. - Variable Declaration —
let,const,var, block versus function scope, hoisting, and the temporal dead zone (TDZ). - const and Immutability — why
constfixes the reference, not the contents, and how to get real immutability viareadonly,as const,Object.freeze.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
| Thinking types are checked at run time | Annotations are erased by tsc; there is no runtime type information — the engine runs plain JavaScript with no checks |
Expecting the browser or Node to run .ts directly | The code will not execute without first being transpiled by tsc (or ts-node / esbuild) |
| Believing absolutely everything is erased | interface, type, and annotations disappear, but enum and constructor parameter properties emit real JS code |
| Thinking TypeScript is nominally typed | Compatibility is by shape (structural): an object with the right fields fits without an explicit implements |
Assuming the : Type annotation is mandatory | Needless noise — an omitted type is reliably inferred; annotate the boundaries, not the internals |
Believing a missing annotation yields any | Inference produces a concrete type; any appears only when there is nothing to infer from (an untyped parameter under noImplicitAny) |
| Trying to annotate a hoisted callback's parameter | Without a context, the parameter of (x) => … in a bare const becomes implicit any — the context existed only in the argument position |
Expecting arr.map(x => x.length) and const f = x => x.length to behave the same | The first is typed by the context from map's signature; the second is an error — the expression has no target position |
Confusing const x = 10 and let x = 10 when inferring | const infers the literal type 10, let widens to number — different types where you expected one |
Thinking object properties keep a literal type under const | Properties always widen (they are mutable): const o = { kind: 'a' } gives { kind: string } |
| Building a discriminated union from plain objects | The discriminant widens to string and the object no longer fits a union branch — cured by as const or a target-type annotation |
Treating let/const as function-scoped and hoisted like var | A wrong scoping model; accessing a name in its temporal dead zone (TDZ) throws a ReferenceError |
Expecting var i in a loop to make one variable per iteration | One shared var i is captured by every callback — it prints 3 3 3; let gives a fresh binding and 0 1 2 |
Thinking const makes an object immutable | push into a const array and property writes pass silently — only the reference is fixed |
Treating Object.freeze as a deep freeze | Only the top level is frozen; nested objects stay mutable |
Interview relevance
A TypeScript interview almost always opens with these questions — and the check is your model of the language, not memorized definitions. A candidate who explains tsc as "checks and erases types, leaving plain JavaScript" and inference as "two directions: from the value and from the context" immediately stands out from "well, it adds types".
Typical checks:
- How TypeScript differs from JavaScript, why every valid JS is also valid TS, and that typing is structural, not nominal.
- That types are checked at compile time and erased — no runtime guarantees; and that
enum/ parameter properties still leave code behind. - How bottom-up inference works (value, function body, best common type) and why it is not
any. - What contextual typing is and why a callback parameter needs no annotation while a hoisted function loses its type.
- Why
let x = 'a'isstringwhileconst x = 'a'is'a'; that object properties always widen and how that breaks a discriminated union. - The difference between
let/const/var: block versus function scope, hoisting, and the TDZ. - That
constfixes the binding, not the value, and how to achieve real immutability (readonly,as const, the shallowness ofObject.freeze).
Common wrong answer: "TypeScript is a language the browser runs with type checking." It opens the discussion that TS does not run on its own, that types exist only until transpilation, and that nothing of them survives into runtime. A second common miss is "since it is declared with const, the property keeps its literal type too": in fact the binding is fixed while the properties are mutable and widen, so kind: 'a' becomes string and the object stops fitting the discriminated union.