learn
TypeScript

Interview question bank

Every TypeScript interview question in one searchable list.

Search every question by wording or answer, or filter by topic and level. Your filters live in the URL, so a filtered view can be shared.

Topic

Level

111 questions

What is TypeScript?basicWhy TypeScript exists

TypeScript is a typed superset of JavaScript maintained alongside the typescript compiler package. You write .ts files with optional type annotations, then the compiler checks them and emits plain .js you can run anywhere JavaScript runs. Valid JavaScript is usually valid TypeScript, so you can adopt it gradually. Think of it as JavaScript plus a static checker that catches mistakes before your code runs.

Interviewers also ask

  • Where does type checking happen — runtime or before?
Why would you choose TypeScript over plain JavaScript?basicWhy TypeScript exists

You get earlier feedback on the mistakes JavaScript hides until runtime: misspelled properties, wrong argument shapes, and forgotten null cases. Types also act as living documentation, so you spend less time guessing what a function expects. Editors use them for precise autocomplete and safe renames. For teams and long-lived codebases, that means fewer runtime surprises and faster refactors, at the cost of writing and maintaining the annotations.

Interviewers also ask

  • What does TypeScript cost you in return?
What changed with TypeScript 7's native compiler?intermediateWhy TypeScript exists

TypeScript 7, released July 8 2026, ships a native compiler written in Go inside the typescript package for much faster checking and builds. The native compiler does not offer a programmatic JavaScript API, so tools that called the old API should use the @typescript/typescript6 compatibility package, which provides the tsc6 binary and re-exports the 6.0 API. Everyday tsc command-line usage stays familiar.

Interviewers also ask

  • What should a tool author do if they relied on the compiler API?
What kinds of bugs does TypeScript catch for you?basicWhy TypeScript exists

It catches shape mismatches: calling a function with the wrong arguments, reading a property that does not exist, or assuming a value is defined when it can be null. It also flags misspelled names and incorrect assignments across files, which plain JavaScript would only reveal at runtime. You still need tests for logic errors, but the compiler removes a whole class of silly wiring mistakes before you even run the code.

Interviewers also ask

  • Can TypeScript catch logic errors too?
When does TypeScript check your types?intermediateWhy TypeScript exists

It checks statically, before your code runs — when your editor highlights an error or when you run tsc in your build. Nothing about the check survives into the emitted JavaScript. That means a passing check tells you the code was consistent at build time, not that runtime data will match. Data from an API or user input still needs runtime validation, because the compiler cannot see what the network will send.

Interviewers also ask

  • Why do you still need runtime validation for API data?
How does the tsc compiler turn TypeScript into JavaScript?basicHow TypeScript works

You run tsc over your .ts files and it does two jobs: it checks your types against each other, then it strips the type syntax and emits plain .js. Modern syntax is downleveled to match your target, so optional chaining or classes still run on older runtimes. You configure the input, output folder, and language level in tsconfig.json, and the emitted files are what you actually deploy and run.

Interviewers also ask

  • What part of the output can you run in the browser?
What does it mean that TypeScript types are erasable?basicHow TypeScript works

It means every annotation, interface, and type alias is removed when the code is emitted — no trace remains at runtime. The emitted JavaScript behaves exactly as if you had written it by hand without types. You cannot inspect an interface in running code or branch on it, because it no longer exists. If you need a runtime check, you must write real JavaScript logic such as a typeof test or a validation function.

Interviewers also ask

  • Can you check an interface with an if statement at runtime?
Does TypeScript do any runtime type checking?intermediateHow TypeScript works

No, the compiler performs no runtime checking itself. Once your .ts is emitted to .js, all types are gone and execution follows normal JavaScript rules — a wrong-typed API response flows straight through. The safety comes entirely from the build-time check plus the runtime guards you write yourself. That is why teams pair TypeScript with small validators at program boundaries, where outside data enters typed code.

Interviewers also ask

  • Where should you put runtime checks in a typed app?
How can Node.js run .ts files directly with type stripping?basicHow TypeScript works

Recent Node versions can run .ts files by stripping the type syntax without doing any type checking, so development feels instant. The feature first appeared around v22.6.0, became enabled by default through the v22 and v23 line, and stabilized later — check your exact Node version before relying on it. Remember it only removes types: type errors still run, and syntax Node cannot strip cleanly needs a real build step.

Interviewers also ask

  • Does running a .ts file in Node prove its types are correct?
If tsc reports a type error, does it still emit JavaScript?intermediateHow TypeScript works

By default yes — checking and emitting are separate steps, so tsc prints the errors but still writes .js output unless you set noEmitOnError. That surprises beginners who expect errors to block the build. In practice you decide: leave the default for quick iteration, or enable noEmitOnError in CI so broken types cannot ship. Either way, fix the reported errors — the emitted code still runs as plain JavaScript.

Interviewers also ask

  • How would you block a deploy on type errors?
What tools besides tsc can compile TypeScript?intermediateHow TypeScript works

Many tools transpile TypeScript by stripping types without full checking: esbuild, swc, tsx, and bundlers built on them compile single files very fast. That speed is ideal for development and bundling, but none of them replaces tsc for correctness — they happily emit code with type errors. A common setup uses a fast transpiler to build and runs tsc --noEmit alongside it purely to check types.

Interviewers also ask

  • Why run tsc --noEmit next to a fast bundler?
What are the main benefits of TypeScript?basicPros and cons

You catch wiring mistakes early, document intent in the code itself, and get far better editor help — accurate jumps, autocomplete, and renames that update every caller. Refactors that feel risky in JavaScript become mechanical: change a shape and the compiler lists every place to fix. For shared libraries, published .d.ts files let consumers use your API correctly without reading the source. The payoff grows with codebase size and team size.

Interviewers also ask

  • How do types help when refactoring?
What are the main costs of adopting TypeScript?basicPros and cons

You pay in setup and upkeep: a build step, a tsconfig.json to maintain, and annotations to write for every boundary. Third-party libraries occasionally need extra @types packages or small local declarations. Beginners also fight the compiler at first, especially around null and third-party shapes. For tiny scripts the overhead can outweigh the help, so weigh the long-term maintenance win against the short-term slowdown.

Interviewers also ask

  • What extra packages do untyped libraries sometimes need?
When should you NOT use TypeScript?intermediatePros and cons

Skip it for throwaway scripts, quick prototypes, and tiny automations where a build step slows you down more than types help. It also adds friction to highly dynamic code — heavy metaprogramming or loosely shaped plugin systems can force you into any everywhere, defeating the purpose. If your team will not maintain the types, an untyped codebase with good tests beats a half-typed one that everyone ignores. Choose the tool that fits the lifespan of the code.

Interviewers also ask

  • What is a better fit than TypeScript for a one-off script?
How does TypeScript compare to JavaScript with JSDoc type checking?intermediatePros and cons

Both give you editor help and tsc checking, but JSDoc keeps you in plain .js with types written as comments, while TypeScript uses dedicated .ts syntax. JSDoc suits gradual adoption or teams that cannot add a build step, since the code runs as-is. Full TypeScript scales better: its syntax stays readable for complex generics, and refactoring tools understand it more reliably. You can even mix them with allowJs and checkJs during a migration.

Interviewers also ask

  • How would you check plain JS files with the TypeScript compiler?
What are the most common basic types in TypeScript?basicBasic types and inference

You will use string, number, boolean, null, and undefined for everyday values, plus bigint and symbol for rarer cases. Arrays are written string[] and objects get inline shapes like { name: string }. void marks functions that return nothing useful. Start with these small building blocks — every interface, union, and generic you meet later is composed from them.

Interviewers also ask

  • How do you write the type of an array of strings?
How do you add a type annotation to a variable?basicBasic types and inference

You add a colon plus the type after the name, like let name: string = 'Asha'. The annotation tells the compiler what you intend, and it rejects any later assignment that disagrees. You only need it where inference cannot figure things out — for empty declarations like let user: User or function boundaries. Inside a function body, prefer letting inference do the work so the code stays quiet.

Interviewers also ask

  • When can you leave the annotation off?
How does type inference work, and when should you rely on it?basicBasic types and inference

The compiler infers a type from the initializer, so let count = 0 is treated as number without you writing anything. Rely on it for locals — it keeps code short and stays accurate. Write explicit annotations at function parameters and return positions, where there is no initializer to learn from and where the annotation documents your contract for every caller. Hover over a variable in your editor to see what was inferred.

Interviewers also ask

  • Where does inference fall short?
How do null and undefined differ in TypeScript?basicBasic types and inference

undefined means a value is missing — an uninitialized variable or a skipped optional property. null means you deliberately set emptiness, like clearing a selected user. With strictNullChecks on, they are distinct types and you must handle each before use, which removes the classic Cannot read properties of null crash. Be explicit about which one your function accepts and returns.

Interviewers also ask

  • What does strictNullChecks change here?
How do you type arrays in TypeScript?basicBasic types and inference

You write the element type plus brackets, like string[] or number[], which is shorthand for Array<string>. The compiler then checks every element on creation and every access afterwards — pushing a number into a string[] is an error. Use readonly string[] when callers should not mutate the array. For fixed-length mixed shapes, reach for a tuple instead of a plain array.

Interviewers also ask

  • When would you use a tuple instead of an array?
What is a tuple and when would you use one?basicBasic types and inference

A tuple is a fixed-length array where each position has its own type, like [string, number] for a name-age pair. You annotate it with brackets around the sequence, and the compiler checks both the length and each slot's type. Use tuples for small, positionally meaningful returns such as [user, error] helpers. For anything with named fields, prefer an object — names document intent better than positions do.

Interviewers also ask

  • How is [string, number] different from (string | number)[]?
What does void mean as a function return type?intermediateBasic types and inference

void means the function returns nothing you should use — typical for callbacks, event handlers, and side-effect helpers. You can still return early with no value, but assigning the result is pointless. Do not confuse it with undefined as a value type: void describes intent at the function boundary, while undefined is a concrete value that can appear anywhere. Annotate effect-only functions : void to keep callers honest.

Interviewers also ask

  • How is void different from undefined?
What does never mean, and when do you see it?intermediateBasic types and inference

never marks values that cannot occur: a function that always throws or loops forever, or a code branch that should be unreachable. You meet it most in exhaustive switch statements, where narrowing every union member leaves never in the final default — assigning anything there proves a case was missed. It also appears when you intersect incompatible types. Treat a never sighting as the compiler telling you this path is impossible.

Interviewers also ask

  • How does never help with exhaustive switches?
How do you describe the shape of an object in TypeScript?basicObjects: interface versus type

You write an object literal type naming each property and its type, like { name: string; age: number }. The compiler then requires those exact properties with compatible values. Add ? for optional members and readonly for fields that must not be reassigned. For reused shapes, lift the literal into an interface or type alias so every function shares one named contract instead of repeating the literal.

Interviewers also ask

  • How do you mark a property optional?
What is the difference between interface and type?intermediateObjects: interface versus type

Both name an object shape, and for everyday use they are interchangeable. An interface can be extended with extends and re-opened across declarations, which libraries use to augment shapes. A type alias can additionally name unions, tuples, and mapped shapes that an interface cannot express. As a rule of thumb, use interface for public object contracts and type when you need unions or computed transformations.

Interviewers also ask

  • Which one can name a union like string | number?
How do optional properties work?basicObjects: interface versus type

You add ? after the name, like nickname?: string, which lets callers omit the property entirely. Inside your code the value reads as string | undefined, so you must handle the missing case before using it. Keep optional fields for genuinely absent data, not for values you always set — overusing ? pushes undefined checks onto every reader. Pair optionals with defaults or narrowing at the point of use.

Interviewers also ask

  • What type does an optional property read as?
What does readonly do on a property?basicObjects: interface versus type

It forbids reassignment after creation, so readonly id: string can be set once in the object literal or constructor and never changed again. The compiler rejects later writes, which protects identifiers and shared config. Note it is a compile-time guard only — the emitted JavaScript still allows the write at runtime. For arrays, use readonly T[] or Readonly<T> to get the same one-way protection across the whole shape.

Interviewers also ask

  • Does readonly protect you at runtime?
What are excess property checks?intermediateObjects: interface versus type

When you pass a fresh object literal to a typed position, the compiler rejects unknown extra properties — passing { name, age, nick } where only { name, age } is expected is an error. It catches your typos instead of silently ignoring the extra field. The check only applies to fresh literals; assigning through a variable skips it. If extras are legitimate, add an index signature or extend the target type rather than working around the error.

Interviewers also ask

  • Why does the same object pass through a variable but fail inline?
How do you extend an interface or combine object types?intermediateObjects: interface versus type

You extend an interface with extends, like interface Admin extends User { level: number }, which inherits every member of User plus the new ones. With type aliases you intersect instead: type Admin = User & { level: number }. Both produce one flattened shape the compiler checks as a whole. Prefer extends for clear hierarchies and intersections when you compose small reusable pieces. Conflicting property types in an intersection become never.

Interviewers also ask

  • What happens if two intersected types disagree on a property?
What is structural typing?intermediateObjects: interface versus type

TypeScript checks what an object contains, not what class or interface name it was declared with. If a function wants { name: string }, any object with at least a string name is accepted — extra properties are fine and no explicit implements is needed. That keeps plain object literals and third-party shapes interoperable. The tradeoff is that two unrelated shapes with the same members look identical to the checker, so use branding or literal tags when identity matters.

Interviewers also ask

  • Do you need implements for a plain object to match an interface?
How do index signatures work?intermediateObjects: interface versus type

They describe objects with unknown keys but known value types, like { [key: string]: number } for a score table. You read and write any string key and the compiler enforces the value type. Use them for dictionaries and lookup maps, but prefer explicit properties or Record unions when the keys are known — named fields document intent and catch typos that an open index signature would silently accept.

Interviewers also ask

  • When is Record a better fit than an index signature?
How do you type a function's parameters and return value?basicFunctions

You annotate each parameter and the result, like function add(a: number, b: number): number. Parameters need explicit types because there is nothing to infer them from, while the return annotation documents the contract for callers. The compiler then rejects wrong-typed arguments and checks that every code path returns a compatible value. Keep the annotations at the boundary and let locals inside the body infer.

Interviewers also ask

  • Can you leave the return type off?
How do optional and default parameters work?basicFunctions

You mark a trailing parameter name?: string to let callers skip it, or give it a default like name = 'guest' which makes it optional automatically. Optional parameters read as possibly undefined inside the body, so handle that case. Keep required parameters first — a required parameter after an optional one confuses callers and needs an explicit undefined argument. Defaults also document the common case right in the signature.

Interviewers also ask

  • Where must optional parameters appear in the list?
How do you type an arrow function or callback?basicFunctions

You annotate it inline like (user: User) => user.name, and the return type is usually inferred. When passing a callback directly, you can often omit annotations entirely — the expected function type provides context, so (u) => u.name is checked against the parameter's declared signature. This contextual typing keeps event handlers and array callbacks quiet. Add explicit annotations only when the callback stands alone without context.

Interviewers also ask

  • What is contextual typing for inline callbacks?
How do function overloads work?intermediateFunctions

You write several call signatures above one implementation, each describing a supported way to call the function. Callers see only the overload list, while the implementation signature stays hidden and must accept them all. Use overloads when one function genuinely accepts different shapes, like pad(s: string) versus pad(n: number). If the variants can be expressed as one union or optional-parameter signature, prefer that — it is shorter and easier to maintain.

Interviewers also ask

  • When is a union signature better than overloads?
How do you type a function that takes no useful result, like a click handler?basicFunctions

You annotate the return as void, like (e: MouseEvent) => void, to show the result is ignored. Handlers, loggers, and forEach callbacks all use this shape. Callers can pass functions that happen to return something — TypeScript allows that — but nobody should read the value. Reserve never for functions that never return at all, such as ones that always throw.

Interviewers also ask

  • Can a void callback return a value?
How do rest parameters work with types?intermediateFunctions

You collect trailing arguments with ...rest: string[], which reads inside the body as a normal array. Callers can pass any number of extra arguments and each is checked against the element type. Tuples make rest precise: ... args: [string, number?] enforces the exact sequence. Use rest for wrappers like loggers and forwarders where the arity varies, and prefer fixed parameters when the count is known.

Interviewers also ask

  • How would you type a fixed two-argument rest pair?
How do you type an async function?intermediateFunctions

You annotate the unwrapped value inside Promise, like async function load(): Promise<User>. The async keyword wraps your return and throw into the promise for you, so you write the body as if it were synchronous. Callers must await or .then the result. For error paths, let the promise reject with an Error rather than resolving a union — it keeps try/catch at the call site natural.

Interviewers also ask

  • Do you write Promise in the return annotation of an async function?
How do you write a reusable function type?basicFunctions

You alias the signature, like type Handler = (id: string) => void, then reuse Handler for every matching function. It keeps callbacks consistent across components and makes refactors one-line changes. For methods inside objects, you can write the shorthand { onSave(id: string): void }. Name the alias after its role, not its shape, so readers learn intent from the name alone.

Interviewers also ask

  • How do you type a method inside an object type?
What is a union type?basicUnions, literals and narrowing

A value that can be one of several types, written with | like string | number. You use it when a variable, parameter, or response genuinely holds different shapes at different times. The compiler forces you to handle each member before using type-specific features — you cannot call string methods until you have narrowed away the number case. Keep unions small and meaningful so every branch is worth handling.

Interviewers also ask

  • What must you do before calling a method on a union?
How do you narrow a union with typeof?basicUnions, literals and narrowing

You guard with typeof value === 'string' inside an if, and the compiler shrinks the union to that branch — string in the true path, the remainder in the else. It works for primitives like string, number, and boolean. Write the check as a plain condition so it also guards at runtime. For objects, switch to in, instanceof, or a literal tag check instead.

Interviewers also ask

  • What does the else branch contain after a typeof guard?
What is a discriminated union?intermediateUnions, literals and narrowing

A union of object types that share one literal tag field, like { kind: 'circle'; radius: number } | { kind: 'square'; size: number }. You switch on shape.kind and the compiler narrows the whole object in each branch, so only the matching fields are visible. It is the clearest way to model states, events, and API variants. Keep the tag a string literal and give every member a distinct value.

Interviewers also ask

  • Why must the shared field be a literal type?
How does the in operator narrow a union?basicUnions, literals and narrowing

You test for a property with 'email' in user, and the compiler keeps only the union members that declare it. It suits object unions where members have different fields. Like typeof, the check runs at runtime too, so it doubles as real validation. Prefer a discriminated tag when you control the shapes — an explicit kind field reads better than probing for incidental properties.

Interviewers also ask

  • When is a kind field better than an in check?
How does instanceof narrow a union?basicUnions, literals and narrowing

You write if (err instanceof Error) and the compiler treats the true branch as that class, exposing its methods and fields. It works because the check consults the prototype chain at runtime. Use it for class instances like Date, Error, or your own classes. For plain object literals or interfaces, instanceof cannot help — there is no constructor to test, so prefer a tag field or in check there.

Interviewers also ask

  • Why doesn't instanceof work on interfaces?
How do truthiness checks narrow a value?intermediateUnions, literals and narrowing

Writing if (value) removes null, undefined, '', 0, and other falsy members from the type in that branch. It is concise for optional values, but be careful: legitimate 0 or empty strings are dropped too. When those values are valid, compare explicitly with value != null or value !== undefined instead. Choose the narrowest check that matches what you actually want to exclude.

Interviewers also ask

  • What valid values can a bare if accidentally drop?
How do you write an exhaustive switch over a union?intermediateUnions, literals and narrowing

You handle every member in case branches and let the default assign the remainder to never — if a member is unhandled, that assignment errors and points at the missing case. Adding a new union member then breaks the switch loudly instead of silently falling through. Keep a small assertNever helper for the default branch so the intent reads clearly and every future reader knows the switch must stay complete.

Interviewers also ask

  • What does a never assignment in default prove?
What is a custom type guard?intermediateUnions, literals and narrowing

A function whose return type is a predicate like pet is Fish, which tells the compiler the argument's narrowed type when it returns true. You implement the runtime test yourself — checking a tag or property — and the is annotation exports that knowledge to every if that calls it. Use guards to hide repeated narrowing logic behind a named, testable function. Keep the runtime test honest, since the compiler trusts your predicate.

Interviewers also ask

  • What does the is keyword promise the compiler?
What is a generic in TypeScript?basicGenerics

A placeholder type you fill in at use time, written <T> like function first<T>(items: T[]): T. One definition then works for strings, users, or any shape while preserving the exact type through to the result. Without generics you would duplicate the function per type or fall back to any and lose safety. Read T as a variable for types: the caller chooses what it stands for on each call.

Interviewers also ask

  • How is a generic different from any?
How do you call a generic function?basicGenerics

You usually let inference fill the placeholder: first(['a', 'b']) resolves T to string with nothing extra to write. When inference fails or you want to be explicit, pass the argument in angle brackets like first<string>([]). Explicit arguments also pin an empty array to the type you intend. Prefer inference at call sites — it keeps code quiet and stays correct when element types evolve.

Interviewers also ask

  • When must you write the type argument explicitly?
How are built-in Array and Promise generic?basicGenerics

Array<T> stores whatever element type you choose, so string[] only accepts strings and returns strings from indexing. Promise<T> wraps the resolved value, so Promise<User> forces you to await a User. Both preserve the inner type through methods like map and then instead of degrading to any. Whenever you model a container or wrapper, follow the same pattern: parameterize the inside rather than fixing it.

Interviewers also ask

  • What does the T stand for in Array<T>?
How do generic constraints with extends work?intermediateGenerics

You bound the placeholder like <T extends { id: string }>, which requires every argument type to have at least those members. Inside the body you can then safely read item.id without narrowing. Constraints turn an anything-goes generic into one with a minimum shape. Keep them as small as the body needs — an over-wide constraint rejects valid callers for no reason.

Interviewers also ask

  • What can you safely access inside a constrained generic?
How do generic defaults work?intermediateGenerics

You provide a fallback like <T = string>, used when the caller omits the argument and inference finds nothing better. It makes a generic convenient without closing off other types — Box means Box<string> but Box<number> still works. Use defaults for the common case in library types and config objects. Keep the default compatible with your constraint, or the declaration itself becomes an error.

Interviewers also ask

  • When does the default apply instead of inference?
How do you write a generic interface?intermediateGenerics

You parameterize the whole shape, like interface Box<T> { value: T }, so each use picks its contents: Box<string> versus Box<number>. Methods inside can reuse T without redeclaring it. It suits wrappers, responses, and state containers where the shell is stable but the payload varies. Prefer one well-named parameter over several — extra parameters multiply the complexity every caller must manage.

Interviewers also ask

  • How would you type a generic API response wrapper?
Why is a generic safer than any?basicGenerics

any erases the type — inputs and outputs both become unchecked, so mistakes flow silently. A generic like <T>(x: T): T remembers the exact type from call to result, so passing a string guarantees a string back. You write the function once but every caller keeps full checking. Reach for generics whenever the shape varies but the relationship between input and output stays fixed.

Interviewers also ask

  • What does the compiler remember that any forgets?
What does keyof do in a generic?intermediateGenerics

keyof T is the union of T's property names, so <T, K extends keyof T> ties a key argument to the object it indexes. A helper like get(obj, key) then returns the precise property type instead of any, and misspelled keys become compile errors. It powers typed pickers, sorters, and form helpers. Use it whenever a string argument must name a real property of another argument.

Interviewers also ask

  • How does keyof prevent misspelled property names?
What is a literal type?basicEnums, const assertions and literal unions

A type that allows exactly one value, like 'save' or 200. You use literals to name the precise strings and numbers your code accepts instead of widening to string or number. Combined with unions they describe finite choices — 'small' | 'medium' | 'large' — and misspellings become errors. Prefer literals for options, states, and tags where only specific values are valid.

Interviewers also ask

  • How do literals combine into a union of choices?
What is an enum in TypeScript?basicEnums, const assertions and literal unions

A named set of related constants, like enum Role { Admin, User }, that groups choices under one type. Members are accessible as Role.Admin and document intent better than bare strings scattered through code. String enums assign explicit values per member; numeric enums auto-increment when you omit values. Use enums when the set belongs together and deserves a single importable name.

Interviewers also ask

  • How do numeric and string enums differ?
Should you use an enum or a string union?intermediateEnums, const assertions and literal unions

Prefer a string union like 'admin' | 'user' for most application code — it is erasable, interoperates with plain JavaScript, and needs no import at runtime. Choose an enum when you want a namespaced container with behavior or when a library already exposes one. Unions also narrow cleanly in discriminated patterns. As a default, reach for the union and reserve enums for cases where the grouping itself adds value.

Interviewers also ask

  • Why do unions interoperate better with plain JS?
How do string enums work?basicEnums, const assertions and literal unions

You assign each member an explicit string, like enum Direction { Up = 'UP', Down = 'DOWN' }, and use Direction.Up throughout your code. The explicit values serialize predictably and read well in logs. Unlike numeric enums, string enums have no reverse mapping surprises. They still emit a runtime object, so if you want zero runtime cost, consider a string union plus a plain object instead.

Interviewers also ask

  • Do string enums emit runtime code?
What is a const enum, and when should you avoid it?intermediateEnums, const assertions and literal unions

A const enum is inlined at compile time, so uses of its members become raw values with no runtime object emitted. That saves bytes but breaks isolated transpilation workflows that compile file-by-file without type information. For that reason many style guides avoid const enum in shared libraries. Prefer regular enums or string unions unless you control the whole build and genuinely need the inlining.

Interviewers also ask

  • Why do some bundlers struggle with const enums?
How does as const create literal types?intermediateEnums, const assertions and literal unions

Appending as const to an object or array freezes it to its narrowest literal form: properties become readonly and values become literals instead of widening to string or number. A config like { mode: 'dark' } as const gives mode the type 'dark', so typos elsewhere fail. Use it for constant tables, option maps, and test fixtures where the exact values are the contract. It is the lightest way to get enum-like precision.

Interviewers also ask

  • What two things does as const change about a literal?
How do you declare a class with typed fields in TypeScript?basicClasses

You list each field with its type, like name: string, and assign them in the constructor or at declaration. The compiler requires every non-optional field to be definitely assigned before construction finishes. Declare the shape up front so readers see the state at a glance. Use constructor parameter properties like constructor(public name: string) as a shorthand when you want declaration and assignment in one line.

Interviewers also ask

  • What shorthand declares and assigns a field at once?
How does the constructor work with types?basicClasses

The constructor takes typed parameters and initializes the instance, like constructor(name: string) { this.name = name }. Its parameters follow the same rules as functions — required first, optionals and defaults last. The checker verifies every field is set on all paths through the constructor. Keep construction simple: validate and assign, and move complex creation into static factory methods that stay easy to type and test.

Interviewers also ask

  • Who checks that every field gets assigned?
What do public, private, and protected mean?basicClasses

They control where a member can be used: public anywhere, protected inside the class and its subclasses, and private only inside the declaring class. The compiler rejects out-of-scope access at build time, which documents your intended surface area. Remember it is a static guard — emitted JavaScript historically still exposes the field at runtime. For hard runtime privacy, use JavaScript #private fields alongside the type-level modifiers.

Interviewers also ask

  • Do private fields stay private at runtime?
How does readonly work on class fields?basicClasses

Marking a field readonly lets the constructor assign it once and forbids later writes, like readonly id: string. It suits identifiers and injected dependencies that must never change. The compiler enforces the rule statically; plain JavaScript output does not freeze the value. Combine readonly with constructor shorthand — constructor(readonly id: string) — for immutable state with minimal boilerplate.

Interviewers also ask

  • Where is the only place a readonly field can be set?
How does implements work with a class?intermediateClasses

You declare class User implements Named { name: string } to promise the class satisfies the Named shape, and the compiler verifies every member exists with a compatible type. It only checks the contract — no code is inherited, unlike extends. Use implements to keep classes honest against shared interfaces while leaving the implementation free. If the interface changes, every implementing class is flagged at once.

Interviewers also ask

  • How is implements different from extends?
How does extends work for class inheritance?intermediateClasses

A subclass with class Admin extends User inherits fields and methods and can override them, calling super() to run the parent constructor first. The checker ensures overrides stay compatible — you cannot widen parameter types or drop required members. Keep hierarchies shallow: deep chains are hard to type and test. Favor composition over inheritance when the relationship is has-a rather than is-a.

Interviewers also ask

  • What must a subclass constructor call first?
What is an abstract class?intermediateClasses

A base class marked abstract that cannot be instantiated directly — it exists to be extended. It can declare abstract methods with no body, which each concrete subclass must implement. You use it when subclasses share real logic plus required holes, like a Shape base with a shared move method and an abstract area. For pure contracts with no shared code, prefer an interface instead.

Interviewers also ask

  • When is an interface a better fit than an abstract class?
How do getters, setters, and static members work?intermediateClasses

Getters and setters expose computed access like get fullName() while storing simpler fields underneath — callers read a property, you run a function. Annotate the getter's return and the setter's parameter so both stay consistent. static members belong to the class itself, like User.nextId, and are accessed without new. Use statics for factories and shared counters, and accessors when derived state must stay in sync.

Interviewers also ask

  • How do you access a static member?
What are TypeScript utility types?basicUtility types, keyof and typeof

Small built-in generics that transform existing types instead of you rewriting them by hand. Partial, Pick, Omit, and Record reshape objects, while ReturnType and Awaited unwrap function results. You pass your type in and get a precise variant out. Learn this toolbox early — reaching for a utility keeps one source of truth instead of duplicating slightly different interfaces across files.

Interviewers also ask

  • Why use a utility instead of writing a second interface?
How does Partial work?basicUtility types, keyof and typeof

Partial<T> makes every property of T optional, so Partial<User> accepts any subset of user fields. It suits update payloads and patch helpers where callers send only what changed. Inside the function, remember each property may now be undefined and guard accordingly. For the reverse — requiring every field — use Required<T>. Keep the base T strict and derive the lenient variant with the utility.

Interviewers also ask

  • What utility does the opposite of Partial?
How do Pick and Omit work?basicUtility types, keyof and typeof

Pick<User, 'name' | 'email'> keeps only the listed properties, while Omit<User, 'password'> drops the listed ones — both produce a new object type without touching User. Use Pick for narrow views like list rows and Omit for safe variants like creation payloads without server-set fields. Prefer them over copying field declarations, so base-type changes propagate automatically to every derived shape.

Interviewers also ask

  • When would you choose Omit over Pick?
How does Record work?basicUtility types, keyof and typeof

Record<Keys, Value> builds an object type with a fixed key set and a uniform value type, like Record<'admin' | 'user', number> for role counts. Every listed key is required and every value is checked. It suits lookup tables, dictionaries with known keys, and mapped config. For open-ended keys you do not know upfront, use an index signature instead — Record shines when the key union is finite and explicit.

Interviewers also ask

  • When is an index signature better than Record?
How does Required work, and when would you use it?intermediateUtility types, keyof and typeof

Required<T> removes every ?, turning all optional properties into required ones. You use it after defaults are applied or validation has run — the point where a previously partial shape is now guaranteed complete. A function that fills missing config can return Required<Config> to prove the result is whole. It keeps the input type lenient while the output type stays strict, with both derived from one definition.

Interviewers also ask

  • What stage of a pipeline suits Required?
How does ReturnType work?intermediateUtility types, keyof and typeof

ReturnType<typeof fn> extracts a function's declared return type, so you name the result without repeating it. If getUser returns Promise<User>, ReturnType<typeof getUser> is that promise type. It keeps callers in sync when the function evolves — change the implementation once and every derived alias follows. Combine it with Awaited to unwrap the promise to User itself.

Interviewers also ask

  • How would you get the awaited result type too?
How do Exclude and Extract work?intermediateUtility types, keyof and typeof

Both filter a union: Exclude<'a' | 'b', 'a'> keeps 'b', while Extract<'a' | 'b', 'a'> keeps 'a'. You use them to derive narrower states from a shared union — public versus internal statuses, for example — without redeclaring members. They operate purely at the type level and emit nothing. When the union grows, the filtered aliases update automatically, which beats maintaining parallel hand-written lists.

Interviewers also ask

  • How do Exclude and Extract differ?
How does Awaited work?intermediateUtility types, keyof and typeof

Awaited<T> unwraps nested promises to the final resolved value, so Awaited<Promise<Promise<User>>> is just User. It mirrors what await actually gives you at runtime. Pair it with ReturnType — Awaited< ReturnType<typeof getUser>> — to name the loaded data type for state and props without duplicating declarations. It is the cleanest way to talk about async results in synchronous type positions.

Interviewers also ask

  • What does Awaited give you that ReturnType alone does not?
What does any mean?basicany, unknown, assertions and satisfies

any opts out of checking — the value can be anything and every operation on it is allowed. It is an escape hatch for untyped boundaries and quick prototypes, but overuse spreads unchecked values through typed code. The compiler will not help you with an any, so errors resurface at runtime. Treat each any as tech debt: contain it at the boundary and convert to a real type or unknown plus narrowing as soon as you can.

Interviewers also ask

  • Why should you contain any at the boundary?
What does unknown mean?basicany, unknown, assertions and satisfies

unknown means a value of uncertain type that you must narrow before use — the type-safe counterpart to any. Assign anything to it, but the compiler forbids calling, indexing, or reading properties until you prove the shape with a guard. It suits parsed JSON, caught errors, and plugin inputs. Prefer unknown over any whenever you cannot name the type yet but still want every use site to verify before proceeding.

Interviewers also ask

  • What must you do before using an unknown value?
How do you narrow an unknown value safely?basicany, unknown, assertions and satisfies

You apply runtime checks — typeof, instanceof, in, or a custom guard — and the compiler shrinks unknown to the proven type in that branch. For example, if (typeof data === 'string') lets you call string methods inside. For objects, validate the needed properties before casting. Never jump straight to as without checking: narrowing keeps the runtime test and the static type in agreement.

Interviewers also ask

  • Why check before you cast?
How does the as keyword work?basicany, unknown, assertions and satisfies

value as User tells the compiler to treat the value as that type for the rest of the expression. It performs no runtime conversion or validation — it is purely your assertion, and the emitted code passes the value through unchanged. Use it after you have validated, or for cases the checker cannot see like DOM lookups. If the types do not overlap enough, the compiler rejects the assertion and asks you to justify it more explicitly.

Interviewers also ask

  • Does as convert or validate anything at runtime?
How is satisfies different from as?intermediateany, unknown, assertions and satisfies

satisfies checks that an expression matches a type while keeping its precise inferred shape, whereas as widens or overrides to the asserted type. A config object with satisfies Config still exposes its literal values for autocomplete, but a mistyped field errors immediately. Use satisfies when you want validation without losing narrowness — theme objects and route tables are classic cases. Reach for as only when you intentionally re-label a value.

Interviewers also ask

  • Why does satisfies preserve literals better than as?
What does the non-null assertion ! do?intermediateany, unknown, assertions and satisfies

Appending ! like user!.name tells the compiler a nullable value is definitely present, removing null and undefined from the type. It emits no runtime check — if you are wrong, the code still crashes. Use it sparingly for cases the checker cannot see, such as values set by a framework lifecycle. Prefer explicit guards or defaults wherever the absence is genuinely possible, since those handle the failure instead of asserting it away.

Interviewers also ask

  • What happens at runtime if a ! assertion is wrong?
What are assertion functions with asserts?intermediateany, unknown, assertions and satisfies

Functions declared asserts value is string throw on failure and narrow the argument in code that follows — no if needed at the call site. You centralize validation like assertIsUser(data) and every later line sees the proven type. They suit loaders and test setup where invalid data must halt execution. Keep each assertion focused on one shape so failures point clearly at what was missing.

Interviewers also ask

  • How does an assertion function differ from a type guard?
When is a double assertion like as unknown as T justified?intermediateany, unknown, assertions and satisfies

It forces a conversion between types that do not overlap enough for a single as, by routing through unknown first. It is occasionally needed for test doubles, deserialized payloads, or legacy interop. Treat it as a loud warning: you have bypassed the overlap check, so validate at runtime before trusting the result. If you reach for it often, the underlying types likely need remodeling rather than more forceful casting.

Interviewers also ask

  • What runtime step should follow a double assertion?
How do import and export work in TypeScript?basicModules and declaration files

Exactly as in modern JavaScript: you export functions, classes, and types from one file and import them where needed. Each file with top-level import or export is a module with its own scope. You can export values and types side by side, and importers pull only what they use. Keep exports intentional — a small public surface makes refactors and API reviews far easier than re-exporting everything.

Interviewers also ask

  • What makes a file a module?
What does import type do, and why use it?intermediateModules and declaration files

import type { User } brings in only the static type, guaranteeing no runtime dependency on the source module. The import is fully erased at emit, which avoids circular runtime imports and lets bundlers skip the module. Use it whenever you need just the shape — props, payloads, return types. It also documents intent: readers see immediately that the import cannot affect runtime behavior.

Interviewers also ask

  • How does import type help with circular imports?
How do you share a type across files?basicModules and declaration files

You export it from one module with export interface User and import it with import type { User } wherever it is needed. One canonical definition keeps every consumer in sync — changing the interface updates all usages at the next check. Place widely shared shapes in a nearby types.ts or next to the domain they describe. Avoid global ambient types for app code, since explicit imports trace where each name comes from.

Interviewers also ask

  • Where should you put widely shared types?
What is a .d.ts declaration file?intermediateModules and declaration files

A file containing only types — no runtime code — that describes the shapes of JavaScript for the compiler. Libraries ship them so your editor and tsc understand the API without seeing the implementation. You can also write local .d.ts files to type untyped JavaScript you depend on. They are never executed, only read during checking, which is why they contain declare statements rather than real logic.

Interviewers also ask

  • Are declaration files ever executed?
What is DefinitelyTyped and when do you need @types packages?intermediateModules and declaration files

DefinitelyTyped is the community repository of declaration packages for libraries that do not bundle their own types. When an import errors with a missing-declarations message, you install the matching @types package as a dev dependency — for example @types/lodash for lodash. Modern well-maintained libraries usually ship types already, so check the package itself first. Remove the @types package once the library bundles its own.

Interviewers also ask

  • How do you know if a library already ships its own types?
How do you declare a missing module with declare module?intermediateModules and declaration files

You write a short ambient declaration like declare module 'x' { const v: string; export default v } in a .d.ts file to give the compiler a shape for an untyped import. It unblocks the build without editing the library. Keep the declaration minimal and accurate, and prefer contributing real types upstream or installing @types when available. Treat local declares as temporary shims with a note about what should replace them.

Interviewers also ask

  • Why prefer @types over a hand-written declare module?
What is tsconfig.json?basictsconfig and strict mode

The configuration file for the TypeScript compiler: it selects which files to include, how strictly to check them, what language level to emit, and where output goes. Editors read it too, so its settings drive the errors you see inline. Every project should commit one at the root rather than relying on command-line flags. Start from the strict: true preset and relax individual options only with a written reason.

Interviewers also ask

  • What are the most important options to set first?
What does strict mode enable?intermediatetsconfig and strict mode

Setting strict: true turns on the whole family of correctness flags at once — strictNullChecks, noImplicitAny, strict function and property checks among them. It is the recommended default because each flag closes a specific hole where errors slip through silently. Enable it for new projects without hesitation. For older codebases, turn flags on one at a time so each batch of errors stays reviewable.

Interviewers also ask

  • How would you adopt strict in an old codebase?
What does strictNullChecks do?intermediatetsconfig and strict mode

It removes null and undefined from every type unless you list them explicitly, so string truly means a present string. Reading a possibly missing value becomes an error until you guard, default, or narrow it. That one flag eliminates the most common production crash — dereferencing nothing. Keep it on and model absence honestly with | null or optional properties instead of letting every type secretly include emptiness.

Interviewers also ask

  • How do you model a value that can be missing?
What does noImplicitAny do?intermediatetsconfig and strict mode

It forbids the compiler from silently falling back to any where it cannot infer a type — untyped parameters and bare [] initializers become errors instead. Each error asks you to state your intent explicitly. That keeps any visible and deliberate rather than accidental. When migrating, these errors form a natural todo list: annotate each site with a real type instead of suppressing the flag.

Interviewers also ask

  • Why is a silent any worse than an explicit one?
How do include, exclude, and lib work in tsconfig?basictsconfig and strict mode

include and exclude choose which files the compiler checks — typically your src folder minus tests or build output. lib selects the built-in APIs available, like DOM for browser code or ES2022 for modern runtime helpers. Narrowing these keeps checking fast and prevents server code from accidentally using browser globals. Review them when adding a new folder or runtime target.

Interviewers also ask

  • Why would server code exclude DOM lib?
What do target and module control?intermediatetsconfig and strict mode

target sets the JavaScript version emitted — how modern the syntax and helpers in your .js output may be. module sets the import format used between emitted files, like ESNext for bundlers or CommonJS for older Node setups. Match them to your runtime: modern browsers and Node accept higher targets, while libraries often compile lower for compatibility. Mismatched settings cause missing helpers or unloadable output.

Interviewers also ask

  • What breaks if target is newer than your runtime?
How do noUnusedLocals and noUnusedParameters help?intermediatetsconfig and strict mode

They turn dead code into compile errors: unused local variables and unused function parameters are flagged instead of lingering. That keeps diffs clean and surfaces leftover scaffolding from refactors. Prefix a deliberately unused parameter with _, like _next, to signal intent and silence the check. Enable both once strict is green — they are the cheapest tidy-up automation a codebase can have.

Interviewers also ask

  • How do you mark an intentionally unused parameter?
How do you type React component props?basicTypeScript in a React app and an API call

You define a Props interface or type and annotate the component, like function Card({ title }: { title: string }). Required data stays required, optional UI gets ?, and mismatched usages fail at the call site instead of rendering broken output. Keep the Props type next to the component and name it after the component. For children, add children?: React.ReactNode explicitly.

Interviewers also ask

  • How do you type children?
How do you type useState in React?basicTypeScript in a React app and an API call

You pass the value type as a generic, like useState<User | null>(null), so the state and setter stay checked. Inference handles the easy case — useState('') is already string — but empty or nullable initial values need the explicit argument to avoid widening to null or never[]. The generic documents what the state will become, not just what it starts as.

Interviewers also ask

  • When does useState need an explicit generic?
How do you type React event handlers?basicTypeScript in a React app and an API call

You annotate the event with its React type, like (e: React.ChangeEvent<HTMLInputElement>). The generic names the element, so e.target.value is correctly a string. Inline handlers usually infer the event from the JSX prop, so explicit annotations matter most in extracted helpers. Use React.FormEvent for submits and React.MouseEvent for clicks, and call e.preventDefault() where the behavior needs it.

Interviewers also ask

  • How do you type an input change handler?
How do you type a fetch call with ApiResponse<T>?intermediateTypeScript in a React app and an API call

You wrap the payload in a generic envelope like interface ApiResponse<T> { data: T; error: string | null }, then write fetchUser(): Promise<ApiResponse<User>>. The generic keeps every endpoint's data type precise while sharing one error and loading shape. Callers narrow on error before reading data. Keep the wrapper small — status, data, and error — so it fits every endpoint without sprouting per-route variants.

Interviewers also ask

  • How does the caller know whether data is safe to read?
Why must you validate API data at runtime even with TypeScript?intermediateTypeScript in a React app and an API call

fetch returns any-shaped JSON the compiler cannot see, so asserting it with as User proves nothing — a changed backend still flows through. Add a small validator or schema check where the response enters your app and narrow to the typed shape only after it passes. That one boundary guard protects every downstream component. Treat network data as unknown until proven otherwise, no matter how precise your interfaces look.

Interviewers also ask

  • Where exactly should the validation live?
How do you type loading and error states for data fetching?intermediateTypeScript in a React app and an API call

You model the states explicitly, often as a discriminated union like { status: 'loading' } | { status: 'ok'; data: User } | { status: 'error'; message: string }. The component switches on status and each branch sees only the fields that exist there. That removes impossible states — no data access while loading. Keep the union next to the fetch helper so UI and data logic share one definition of what can happen.

Interviewers also ask

  • Why is a status union safer than separate booleans?
How do you type a ref to a DOM element?intermediateTypeScript in a React app and an API call

You parameterize the ref with the element type, like useRef<HTMLInputElement>(null), so .current is checked on every use. Because the ref starts empty, its type includes null and you must guard before calling methods like .focus(). Callback refs and non-null assertions have their place, but an explicit guard reads best. Use refs for focus, measurement, and uncontrolled inputs — not as a second state system.

Interviewers also ask

  • Why must you check ref.current for null?
What is a safe first step when migrating JavaScript to TypeScript?basicMigrating a JavaScript project

Rename one small, well-tested file from .js to .ts and fix only its errors before moving on. Start at the leaves — utility modules with few imports — so each conversion stays reviewable. Keep the rest of the app running in JavaScript throughout. Small vertical slices beat a big-bang rewrite: every commit stays shippable and each fix teaches the patterns the next file will need.

Interviewers also ask

  • Which files should you convert first?
How do allowJs and checkJs enable gradual migration?intermediateMigrating a JavaScript project

allowJs lets tsc compile your existing .js alongside new .ts, so the project builds during the transition. Adding checkJs extends type checking into those JavaScript files, surfacing errors without renaming anything yet. You tighten file by file, converting the cleanest ones first. Once a file is fully typed, rename it to .ts and let the stricter rules apply. It is the official on-ramp for large codebases.

Interviewers also ask

  • When do you rename a file from .js to .ts?
How does JSDoc help during a gradual migration?intermediateMigrating a JavaScript project

JSDoc comments like @param {string} name give JavaScript files real types that checkJs enforces, without changing any runtime code. You document function contracts in place, fix the errors they reveal, and later convert each annotation mechanically into TypeScript syntax. It suits teams that must keep shipping .js while still gaining safety. Treat dense JSDoc as a staging area — convert to .ts once a file's annotations stabilize.

Interviewers also ask

  • How does a JSDoc type become a TypeScript annotation?
How should you handle the flood of implicit any errors during migration?basicMigrating a JavaScript project

Work through them top-down from shared types outward: define the core domain interfaces first, then annotate function boundaries to use them. Prefer real types over quick any fixes — each honest annotation resolves several downstream errors at once. Where the shape is genuinely unknown, use unknown plus a guard instead of any. Track remaining errors as a visible count so the team sees steady progress toward zero.

Interviewers also ask

  • Why fix shared types before leaf functions?
How do you keep tests passing while converting to TypeScript?intermediateMigrating a JavaScript project

Convert source files before test files, keeping the runtime behavior identical — types alone should not change what the code does. Run the existing suite after each file conversion so regressions point at the last change. Only then add type-aware assertions for the new contracts. If a test needs loosening with a cast during transition, leave a comment naming the follow-up. Green tests on every commit prove the migration is behavior preserving.

Interviewers also ask

  • Should behavior change during a type migration?
When should you enable strict mode during a migration?intermediateMigrating a JavaScript project

Finish a lenient pass first — whole codebase compiling with basic checking — then enable strict flags one at a time, starting with strictNullChecks. Each flag produces a focused batch of real bugs to fix and review separately. Turning everything on at once buries the team in unrelated errors and stalls the migration. Celebrate each flag going green: it marks a permanent safety upgrade, not just a config tweak.

Interviewers also ask

  • Which strict flag should you enable first?

References

On this page