Fundamentals
What TypeScript is and how it relates to JavaScript, variable declaration, const versus let, and type inference.
8 questions
JuniorTheoryVery commonWhat is the difference between const and let in TypeScript?
What is the difference between const and let in TypeScript?
const forbids reassigning the binding after initialization, while let allows reassignment. const does not make the value deeply immutable — you can still mutate a const object's properties or push into a const array, because only the reference is fixed. Both are block-scoped. For real immutability use readonly, as const, or Object.freeze.
Common mistakes
- ✗Assuming
constmakes the value deeply immutable, when it only fixes the binding, not the contents - ✗Thinking a
constarray or object cannot be mutated, whenpushand property writes still work - ✗Believing
constandletdiffer in scope — both are block-scoped, onlyvaris function-scoped
Follow-up questions
- →How do you make a value truly immutable in TypeScript?
- →Why can you still
pushinto an array declared withconst?
JuniorTheoryCommonWhen do you write an explicit type annotation instead of relying on inference?
When do you write an explicit type annotation instead of relying on inference?
Rely on inference for a local with an initializer — annotating it only repeats what the compiler derived. Annotate where inference has nothing to read: function parameters, and an empty initializer like const xs: string[] = []. Pin exported return types.
Common mistakes
- ✗Annotating a local that already has an initializer, repeating what inference derived
- ✗Expecting a parameter's type to be inferred from how the body uses it
- ✗Leaving an exported function's return type inferred, so a refactor changes the public contract silently
Follow-up questions
- →Why is an unannotated function parameter an error under
noImplicitAny? - →What type does an empty array literal get, and why is that a problem?
JuniorTheoryCommonHow do you declare variables in TypeScript?
How do you declare variables in TypeScript?
Declare variables with let, const, or var, optionally followed by a : Type annotation, as in let count: number = 0. If the annotation is omitted, the type is inferred from the initializer. var is function-scoped and hoisted, while let and const are block-scoped. Prefer const, then let, and avoid var.
Common mistakes
- ✗Claiming
letandconstare function-scoped — they are block-scoped, onlyvaris function-scoped - ✗Thinking the
: Typeannotation is mandatory, when an omitted one is inferred from the initializer - ✗Believing the annotation persists at runtime, when it is erased during compilation
Follow-up questions
- →When does TypeScript infer a variable's type instead of requiring an annotation?
- →Why is
vardiscouraged compared toletandconst?
JuniorTheoryCommonWhat is TypeScript and how does it differ from JavaScript?
What is TypeScript and how does it differ from JavaScript?
TypeScript is a statically-typed superset of JavaScript from Microsoft. It adds optional type annotations checked at compile time, so type errors are caught before the code runs, and every valid JS is also valid TS. It does not run directly in browsers or Node — tsc transpiles it to plain JavaScript and erases the types.
Common mistakes
- ✗Thinking TypeScript runs in the browser directly instead of being transpiled to JavaScript by
tsc - ✗Believing the type annotations survive into runtime and are checked while the program runs
- ✗Assuming valid JavaScript needs rewriting to work in TypeScript, when every valid JS is already valid TS
Follow-up questions
- →What does it mean that TypeScript types are erased after compilation?
- →Why is every valid JavaScript file also a valid TypeScript file?
MiddleCodeCommonType a destructured options parameter and return a typed tuple
Type a destructured options parameter and return a typed tuple
The annotation goes on the whole pattern, not the names inside: ({ host, retries = 3 }: { host: string; retries?: number }). The default narrows retries to plain number in the body. The return needs : [string, number] — inference widens the literal to (string | number)[].
Common mistakes
- ✗Writing
{ host: string }in the pattern, which renameshosttostringinstead of typing it - ✗Expecting the returned array literal to be inferred as a tuple
- ✗Thinking a default inside the pattern still leaves the name typed
T | undefined
Follow-up questions
- →What does
as conston the returned array literal give you instead? - →Why does a destructured array element lose its position without a tuple annotation?
MiddleTheoryCommonWhat is type inference in TypeScript?
What is type inference in TypeScript?
Type inference is TypeScript automatically determining a type from a value or context when no annotation is given. let x = 10 infers number, and a function's return type is inferred from its body. For arrays and unions it computes a best-common-type, and contextual typing infers a parameter's type from the expected type. It keeps code concise while preserving type safety.
Common mistakes
- ✗Thinking inference happens at run time, when it is fully resolved at compile time by the type checker
- ✗Believing an unannotated variable becomes
any, when inference derives a concrete type from the initializer - ✗Forgetting that
const x = 10infers the literal type10whilelet x = 10infersnumber
Follow-up questions
- →Why does
const x = 10infer10whilelet x = 10infersnumber? - →What is contextual typing and when does TypeScript apply it?
MiddleTheoryOccasionalWhat is contextual typing, and how does it type a callback's parameters?
What is contextual typing, and how does it type a callback's parameters?
Contextual typing is inference from the outside in: the type expected at a position types the expression written there. In xs.map(x => x.length), x needs no annotation — the parameter type map expects flows into the arrow. Hoist it into a const and x becomes an implicit any.
Common mistakes
- ✗Expecting a hoisted standalone arrow to keep the parameter types it had inline
- ✗Thinking the compiler infers a parameter's type from how the body uses it
- ✗Annotating callback parameters that the surrounding context already types
Follow-up questions
- →Why does extracting a callback into a
constproduce an implicitanyerror? - →How does passing an explicit generic argument change what the context supplies?
MiddleTheoryOccasionalWhy is let x = 'a' typed string while const x = 'a' is typed 'a'?
Why is let x = 'a' typed string while const x = 'a' is typed 'a'?
Because a let binding can be reassigned, TypeScript widens its literal type to the base string. A const binding cannot be reassigned, so the literal type 'a' is kept. The same widening hits mutable object properties: const o = { k: 'a' } types o.k as string; as const stops it.
Common mistakes
- ✗Expecting
const o = { k: 'a' }to giveo.kthe literal type'a' - ✗Reading
constas a deep freeze of the value rather than a fixed binding - ✗Not reaching for
as constwhen a literal type has to survive inside an object
Follow-up questions
- →How does
as constchange the type of a nested object literal? - →Why does passing
let mode = 'dark'into a'dark' | 'light'parameter fail?