TypeScript
100 questions
How to install TypeScript in a project?
•npm i -D typescript.
•npx tsc --init will create tsconfig.json.
•Add scripts: tsc, tsc -w.
•For Node: npm i -D ts-node.
•Check: npx tsc -v.
What is tsconfig.json and what does it control?
•It is the compiler configuration.
•target/module define the output JS.
•strict enables strict checks.
•include/exclude manage files.
•paths/baseUrl configure aliases.
How do type and interface differ?
•interface extends via extends.
•interface supports declaration merging.
•type is convenient for union/intersection.
•For objects, interface is more common, but not a rule.
•In practice, choose based on task and team style.
What is strict mode in TypeScript and why should you enable it?
•strict enables a set of strict flags.
•Catches errors with null/undefined and types.
•Improves autocompletion and refactoring.
•Enhances code quality in large projects.
•Enable gradually if the project is old.
What is any and why should you avoid it?
•any disables type checking.
•Errors move to runtime.
•Instead of any, use unknown, generics, or precise types.
•For migration, you can temporarily use any but plan to remove it.
•You can restrict via eslint/tsconfig.
How does unknown differ from any?
•unknown is safer: cannot use without checking.
•Requires narrowing (typeof, in, instanceof).
•Suitable for external data (JSON).
•any is a 'hole' in types.
•With unknown, errors are caught at compile time.
How to type the result of JSON.parse?
•JSON.parse returns any.
•Use unknown and validate structure.
•Create a type guard or schema (zod).
•After validation, cast to the desired type.
•Do not blindly use as YourType without checking.
What is type narrowing and how does it work?
•It is narrowing the type in code branches.
•Use typeof for primitives.
•Use in/instanceof for objects/classes.
•Custom type guards also narrow the type.
•Helps write code without unnecessary casts.
How to write a type guard function?
•The function returns an expression of the form x is T.
•Inside, check fields and types.
•Use for unknown/union.
•Keep checks simple and cover with tests.
•Schema validation (zod) is often more reliable.
What are union types and where are they useful?
•Union: A | B.
•Good for limited sets of values.
•Often used with a discriminant field.
•Requires narrowing before accessing specific fields.
•Improves API expressiveness.
What are intersection types and when to use them?
•Intersection: A & B.
•Combines properties of multiple types.
•Convenient for compositions and mixins.
•Be careful with conflicting fields.
•Commonly found in library types.
What are literal types and why are they needed?
•Literal types: 'GET', 200, true.
•Basis for union of specific values.
•Useful for statuses, roles, modes.
•Work with as const.
•Help avoid typos.
Why is as const needed?
•Makes literals immutable and narrow.
•Turns array into readonly tuple.
•Useful for configs and dictionaries.
•Simplifies type inference.
•Often used with satisfies.
What is satisfies and how is it better than 'as'?
•satisfies checks conformity to a type but preserves inference.
•Doesn't 'narrow' values like as.
•Useful for configs and mappings.
•Helps catch key/field errors.
•Available in TS 4.9+.
How to type an object dictionary with fixed keys?
•Use Record<K, V>.
•K can be union literals.
•For partial keys — Partial<Record<...>>.
•For validation without losing inference — satisfies.
•For dynamic keys — index signature.
What is an index signature and when is it needed?
•{[key: string]: Value}.
•For objects with unknown keys.
•Limits the type of all values.
•Can conflict with specific fields — be careful.
•Sometimes better to use Map<string, V>.
How to type an array with a fixed structure (tuple)?
•Tuple: [number, string].
•Can make readonly tuple via as const.
•For named elements: [id: string, value: number].
•Convenient for key-value pairs.
•For larger structures, better to use interface.
What are generics and why are they needed?
•Allow parameterizing types.
•For example: function first<T>(a: T[]): T.
•Remove code duplication.
•Can constrain T via extends.
•Improve library typing.
How to constrain generics using extends?
•<T extends { id: string }>.
•Then T is guaranteed to have an id.
•You can constrain with a union.
•Use extends keyof for keys.
•Constraints improve autocomplete.
How to type the function getProperty(obj, key)?
•<T, K extends keyof T>(obj: T, key: K) => T[K].
•keyof gives a set of keys.
•T[K] returns the type of the value at the key.
•Works with optional fields too.
•This is a canonical example of generics.
What is keyof and why is it useful?
•keyof T gives a union of the keys of type T.
•Allows safe indexing.
•Useful in utility types like Pick/Omit.
•Helps build type-safe APIs.
•Works with mapped types.
What are mapped types?
•Types like { [K in keyof T]: ... }.
•Allow transforming fields.
•They underpin Partial/Required/Readonly.
•You can change modifiers: -? / +readonly.
•Powerful tool for libraries.
How does Partial<T> work and when to use it?
•Makes all fields optional.
•Convenient for patch objects (update).
•Danger: guarantees of field presence are lost.
•Often better to use Partial<Pick<T, ...>>.
•Good for input forms.
What is the difference between Pick and Omit?
•Pick takes a subset of fields.
•Omit excludes fields.
•Useful for DTOs and public models.
•Avoid duplicating structures.
•Combine with Partial/Required.
What is Record<K, V> and where is it useful?
•Record creates an object with keys K and values V.
•Useful for dictionaries and mappings.
•K can be a union.
•For incomplete keys, use Partial<Record<...>>.
•For strict key checking — satisfies.
How to type callback functions?
•Describe the signature: type Fn = (x: number) => string.
•For events — use library types (React, DOM).
•For async — Promise<...>.
•Don't forget optional parameters.
•Make the return type explicit.
How to type Promise and async/await?
•async function returns Promise<T>.
•Specify the result type: Promise<User>.
•await unwraps the Promise.
•Handle errors with try/catch.
•Don't return any from async chains.
What are overloads of functions?
•You can declare multiple signatures.
•Implementation is one, but types differ.
•Useful for functions with different modes.
•Overloads should be compatible.
•Sometimes easier to use union and narrowing.
What is the difference between type assertion (as) and actual type checking?
•as does not check at runtime.
•It tells the compiler: 'trust me'.
•Can hide bugs.
•It's better to validate data first.
•Use as only when you are sure of the structure.
What is non-null assertion (!) and why is it dangerous?
•x! indicates that x is not null/undefined.
•The compiler stops warning.
•It can fail at runtime if you are wrong.
•Better to check with if (x) or optional chaining.
•Use sparingly and consciously.
How does optional chaining (?.) work?
•Allows safe access to nested fields.
•obj?.a?.b returns undefined if something is missing.
•Works with function calls: fn?.().
•Does not replace business validation.
•Convenient for UI and API data.
How does nullish coalescing (??) work?
•a ?? b takes b only if a is null/undefined.
•Not confused with 0/''/false like ||.
•Great for defaults.
•Often combined with optional chaining.
•Understand the difference between || and ??.
How to type errors in catch in strict mode?
•In TS, catch variable is often unknown.
•Check: err instanceof Error.
•Log safely: String(err).
•For libraries, create your own error types.
•Do not assume structure without checking.
What is discriminated union and how to design it?
•Union with a tag field: type: 'a' | 'b'.
•In switch on the tag, the type narrows automatically.
•Useful for events and state.
•Add exhaustive check (never).
•One of the best patterns in TS.
How to do exhaustive check in switch?
•Assign a variable of type never in default.
•If a new union variant appears — the compiler will warn.
•Example: const _exhaustive: never = x.
•Helps maintain code.
•Especially useful in reducers.
How to type classes and their fields in TypeScript?
•Specify modifiers: public/private/protected.
•Declare fields in advance or in constructor.
•Use readonly for immutable fields.
•Parameter properties: constructor(private api: Api) {}.
•Do not overuse classes if functions suffice.
What is abstract class and interface: which to choose?
•interface describes a contract without implementation.
•abstract class can contain common implementation.
•In TS, you can implement multiple interfaces but inherit from one class.
•For 'ports' and APIs, usually enough to use interface.
•Abstract is convenient for base components/templates.
How to type this in methods and functions?
•In class methods, this is inferred automatically.
•For functions, specify this parameter: function f(this: X) {}.
•Arrow functions take this from external context.
•In callbacks, this is often lost — bind or use arrows.
•Proper typing catches errors.
How does enum work and why is union often recommended instead of enum?
•enum generates runtime code (usually).
•Can complicate the bundle.
•Literal unions are simpler and tree-shakable.
•If a runtime value is needed — you can use enum or const object.
•const enum speeds up, but is risky for libraries.
What is a const enum and what are its risks?
•const enum is inlined into the code.
•Reduces runtime overhead.
•Can break when building libraries and with different configurations.
•Not always compatible with babel/transpileOnly.
•Often better to use object + as const.
How to type higher-order functions (HOF)?
•Generalize input and output types.
•Keep the signature via generics.
•For function decorators, use <T extends (...a: any[]) => any>.
•Return Parameters<T> and ReturnType<T>.
•This provides strong typing.
What are ReturnType and Parameters and when are they needed?
•ReturnType<F> takes the function's result type.
•Parameters<F> takes the tuple of arguments.
•Useful for wrappers and proxies.
•Reduces duplication.
•Works only with function types.
How to type a safe event emitter system?
•Create a map: EventName -> payload type.
•on<K extends keyof Events>(name: K, cb: (p: Events[K]) => void).
•emit similarly.
•This way, you avoid type errors in events.
•A good pattern for frontend and Node.
How to type an API client and server responses?
•Define DTO types for responses.
•Use runtime validation for external data.
•For fetch, create a request<T>() function returning Promise<T>.
•Never trust the server without checks.
•Log and handle status errors.
How to type fetch and JSON processing?
•const res = await fetch(url).
•Check res.ok.
•const data: unknown = await res.json().
•Validate data and then cast to type.
•Add timeouts/retries separately.
How to properly work with DOM types in TS?
•Enable lib: ['DOM'].
•Use querySelector with generics: document.querySelector<HTMLDivElement>('...').
•Check for null.
•For events, use MouseEvent/KeyboardEvent.
•Don't cast without necessity.
How to type addEventListener and handlers?
•element.addEventListener('click', (e) => { ... }).
•e has type MouseEvent (for click).
•Types for window/document are also available.
•If needed, specify target via instanceof.
•Don't use any for events.
How to type React components in TypeScript?
•Props via interface/type.
•function Component(props: Props) { ... }.
•For children: React.ReactNode.
•For events: React.ChangeEvent<HTMLInputElement>.
•React.FC is not always necessary.
When to use React.FC and when not to?
•React.FC adds children by default.
•Sometimes this is undesirable.
•DefaultProps typing differs.
•Simple functions with Props are usually enough.
•Choose a consistent style in the project.
How to type useState in React?
•useState<number>(
•fixes the type.
•For nullable: useState<User | null>(null).
•For complex objects, an interface is better.
•Lazy initialization: useState(() => compute()).
•Avoid any in state.
How to type useRef?
•useRef<HTMLDivElement | null>(null).
•Check ref.current for null.
•For mutable value: useRef<number>(0).
•Do not use ref as state.
•For callbacks, you can use useCallback ref.
What is declaration merging and where does it occur?
•An interface can merge by name.
•Convenient for extending global types.
•Often used to augment Window/NodeJS.ProcessEnv.
•Can be a source of conflicts.
•Control where you declare global types.
How to extend the type Window or ProcessEnv?
•Create a d.ts file in the project.
•declare global { interface Window { ... } }.
•For env: declare namespace NodeJS { interface ProcessEnv { ... } }.
•Don't forget export {} in the file.
•Make sure include sees this file.
What are .d.ts files and why are they needed?
•These are type declaration files.
•Needed for JS libraries without types.
•Can describe API without implementation.
•Used by DefinitelyTyped (@types/*).
•Included via types/include.
How to add types for a JS library without @types?
•First check if there are built-in types.
•If not — create a minimal .d.ts with declare module 'lib'.
•Gradually refine types as you use them.
•You can write a wrapper API with strict types.
•For complex cases — generate from OpenAPI/JSON schema.
What is moduleResolution and when to change it?
•Determines how TS searches for modules.
•NodeNext is needed for ESM/Node 16+.
•Bundler is suitable for Vite/webpack.
•Classic is rarely needed.
•Choice depends on runtime and bundler.
How to configure path aliases (paths) in tsconfig?
•Set baseUrl.
•Add paths: { '@/*': ['src/*'] }.
•Configure the bundler (webpack/vite) similarly.
•Configure jest/vitest aliases too.
•Otherwise, aliases won't work at runtime.
Why does TS pass, but the module is not found at runtime?
•TS checks paths via tsconfig paths.
•But Node/bundler may not recognize aliases.
•You need to configure resolver in bundler/tests.
•For Node, you can use tsconfig-paths.
•Always check runtime configuration.
What is isolatedModules and when is it needed?
•Required for transpiling via Babel/'single file'.
•Prohibits certain constructs (e.g., const enum).
•Helps with compatibility with bundlers.
•Usually enabled in Next.js.
•Better to keep it enabled in frontend projects.
What is the difference between tsc and babel/swc when building TS?
•tsc performs type checking and can emit JS.
•Babel/SWC transpile quickly but do not check types.
•In such schemes, type checks are run separately (tsc --noEmit).
•Next/Vite often use SWC/Babel.
•Type checking is needed in CI.
How to enable type checking without generating JS?
•Run: tsc --noEmit.
•Or set noEmit: true in tsconfig.
•Convenient for projects where another tool handles the build.
•In CI, do a separate step for typecheck.
•Type errors then do not go into production.
What are source maps and how do they help?
•Link compiled JS with the original TS.
•Simplify debugging in browser and Node.
•Enable with sourceMap: true.
•Sometimes disabled in production due to size and security.
•Useful for Sentry.
How to properly type config objects?
•Use const + as const for literals.
•Use satisfies to check structure.
•Avoid any.
•Keep config in one place, do not spread.
•For env values, do explicit type conversion.
How to type process.env and remove 'string | undefined'?
•Variables may be missing at runtime.
•First validate presence (e.g., via requireEnv function).
•After validation, can return string.
•Better to have a single configuration layer.
•For strict schema, use zod/valibot.
How to type a function that throws an error (never)?
•Functions that always throw/terminate process return never.
•Example: function fail(msg: string): never { throw new Error(msg) }.
•Useful in exhaustive checks.
•Helps flow analysis.
•Improves error readability.
What are template literal types?
•String types with a template: `user:${string}`.
•Useful for building keys and routes.
•Work with union and mapped types.
•Complicate types, use carefully.
•Great for events/ID tags.
How to type strings like UUID/id?
•Full validation with types is not possible.
•Can create a branded type: type UUID = string & { __brand: 'UUID' }.
•Convert only after regex check.
•Increases API safety.
•Without runtime check, brand is meaningless.
What are branded types and why are they needed?
•A way to distinguish identical primitives (string vs Email).
•Done via intersection with a 'marker'.
•Requires factory/validation.
•Reduces errors when passing parameters.
•Useful in domain modeling.
How to type validator functions?
•Return boolean and type predicate.
•For example: (x: unknown): x is User.
•Keep checks at the same level, without side effects.
•Schema validation is often simpler.
•Add tests for validators.
What are conditional types?
•Types like T extends U ? X : Y.
•Used for smart utilities.
•Can distribute over union.
•Sometimes significantly slow down compilation.
•Mainly use in library code.
What is infer in conditional types?
•infer allows 'extracting' part of a type.
•For example, to get the type of an array element.
•Used in ReturnType/Parameters.
•Powerful, but harder to read.
•Write examples and test types.
How to get the type of an array element?
•For array T[] element is T.
•For arbitrary: type Elem<A> = A extends (infer T)[] ? T : never.
•Consider readonly for readonly arrays.
•Useful in generic utilities.
•Don't forget about tuple.
What are utility types Required/Readonly and why?
•Required makes fields mandatory.
•Readonly makes fields read-only.
•Use for public interfaces and immutability.
•There is ReadonlyArray and readonly tuple.
•Reduces mutations in code.
How to type a partial update of an object more safely?
•Don't do Partial<T> on everything.
•Limit the set of fields: Partial<Pick<T, 'a' | 'b'>>.
•To forbid some fields — Omit.
•Validate data before applying.
•For API, better to use a separate DTO.
How to type a merge function without losing types?
•<A extends object, B extends object>(a: A, b: B): A & B.
•But consider conflicting keys.
•At runtime, use Object.assign/spread.
•For deep merge, types are more complex.
•Sometimes explicit description of the result is better.
How to work with readonly and object mutations?
•Readonly forbids assigning to fields.
•For changes, create a copy via {...obj}.
•For arrays: [...arr].
•Use immutable patterns in state.
•For deep structures, discipline/immer is needed.
How to type an object that can be null?
•Use union: T | null.
•Check with if (x == null) return.
•Avoid ! for bypassing.
•For chain access, use ?.
•Think about where null is allowed.
How to properly choose between null and undefined?
•undefined often means 'no value' by default.
•null is often used as explicit 'empty'.
•Keep a consistent style in the project.
•Better to document in API.
•Both require handling in strictNullChecks.
How to type functions with optional parameters?
•Parameter: foo(x?: string).
•Or foo(x: string | undefined).
•Keep in mind that x can be undefined.
•Set default: function foo(x: string = 'a').
•Don't make many optionals in a row — better to use a parameter object.
How to type an 'options object' instead of a long list of arguments?
•Create interface Options { ... }.
•function f(opts: Options) {}.
•Provide defaults via destructuring.
•This is more convenient for API extension.
•Allows using Partial<Options> for defaults.
How does structural typing work in TypeScript?
•In TS, the structure matters, not the name of the type.
•Two types are compatible if their fields match.
•This is convenient but can hide domain errors.
•Branded types help distinguish.
•Always consider system boundaries.
What is nominal typing and how to emulate it in TS?
•Nominal — compatibility by type name.
•In TS, you can emulate via brands.
•type UserId = string & { __brand: 'UserId' }.
•Create a constructor function after validation.
•This reduces accidental substitutions.
How to type functions working with arrays (map/filter/reduce) without any?
•TS infers callback types automatically.
•Ensure the source array is typed.
•For reduce, specify initial value and accumulator type.
•Avoid reduce without initial — issues may occur.
•For filter, sometimes a type guard is needed.
How to make a type guard for filter?
•Example: const isDefined = <T>(x: T | undefined): x is T => x !== undefined.
•Then arr.filter(isDefined) removes undefined.
•Similarly for null.
•Useful in data pipelines.
•Make such utilities in one place.
What is satisfies for arrays and dictionaries?
•You can check array types without losing literal types.
•Example: const routes = [...] satisfies Route[].
•The compiler will check the structure of elements.
•But will keep specific value types.
•Good balance between 'as is' and strict checking.
How to speed up TypeScript compilation in a large project?
•Enable incremental.
•Use project references.
•Reduce include and exclude build/output folders.
•Avoid complex conditional types in hot spots.
•Update TS to the latest version.
What are project references and why are they needed?
•They split a monorepo into TS projects.
•Speed up build and type checking.
•Allow 'incremental' checking of only changed parts.
•Require composite: true.
•Good for large codebases.
How to set up a monorepo with TypeScript?
•Use a workspace manager (pnpm/yarn/npm).
•Create a base tsconfig and extend it.
•Enable project references.
•Keep TS and types versions consistent.
•Configure linter and formatter uniformly.
How to properly type a public API of a library?
•Export only the necessary types.
•Do not export internal details.
•Stabilize the contract and version it.
•Generate d.ts (declaration: true).
•Test types with dtslint or tsd.
How to check types in CI?
•Add a script: npm run typecheck.
•Run tsc --noEmit.
•Run eslint/test separately.
•Do not rely on IDE.
•Make the check mandatory before merge.
How to type a Node.js project in TypeScript?
•Configure module and moduleResolution for ESM/CJS.
•Add @types/node.
•Choose a runner: ts-node, tsx, or build via tsc.
•Watch paths/aliases.
•Check production run (without ts-node).
What is the difference between ESM and CommonJS in the context of TypeScript?
•ESM uses import/export at runtime.
•CJS uses require/module.exports.
•TS can emit both variants.
•In Node, package.json type and extensions .mts/.cts are important.
•Errors often due to mixing systems.
How to configure TypeScript for Node ESM (NodeNext)?
•module: NodeNext and moduleResolution: NodeNext.
•Set type: module in package.json.
•Use extensions/exports correctly.
•For types, use .d.ts or .mts.
•Check build and run together.
How to type Express middleware?
•Install @types/express.
•Types for Request, Response, NextFunction.
•Extend Request via declaration merging if adding fields.
•Make explicit types for body/query/params.
•Validate input data.
How to type DTOs and validate input data?
•TS types do not validate at runtime.
•Use zod/yup/class-validator.
•Schema should be the source of truth.
•Generate type from schema if library allows.
•Keep validator at system boundary (API).
How to safely work with external data: JSON, API, localStorage?
•Read everything external as unknown.
•Validate structure (schema/type guards).
•Handle missing fields and versioning.
•Log parsing errors and fallbacks.
•Do not do as without checks.
How to type localStorage and type conversions?
•localStorage stores strings.
•Create functions getJSON<T>() with unknown + validator.
•For numbers/booleans — explicit conversion.
•Handle missing keys.
•Version storage format.
How to properly type errors for domain logic?
•Create your own error classes (extends Error).
•Add code/type of cause.
•Catch and map at top layer (API/UI).
•Do not throw strings.
•Document what errors are possible.
What are the typical anti-patterns in TypeScript to avoid?
•any and chains of as as.
•! instead of checks.
•Complex types for the sake of types without benefit.
•Duplicating DTOs instead of utility functions like Pick/Omit.
•'Magic' in types without tests and examples.
How to write maintainable types: practical rules?
•Start with simple types, complicate as needed.
•Validate at system boundaries at runtime.
•Do not hide problems with any/as.
•Use discriminated unions for states.
•Maintain a consistent style and type checks in CI.