TypeScript Interview Questions and Answers
31 hand-picked TypeScript interview questions with
detailed answers. Open the interactive version above to search, filter
by difficulty, run code, bookmark questions and track your progress.
interface vs type — what's the difference?
Both describe shapes. interface can be re-opened/merged (declaration merging) and is the idiom for object/class contracts. type is more flexible — it can be a union, intersection, tuple, or alias a primitive.
Rule of thumb: interface for public object shapes, type for unions and utility compositions.
type Status = 'active' | 'inactive';
interface User { id: number; name: string; }
any vs unknown vs never.
- any — turns off type checking (avoid).
- unknown — a safe any; you must narrow it before use.
- never — a value that can't exist (a function that always throws, or an exhausted union).
// any — checking OFF, everything allowed, crashes at runtime
const a: any = JSON.parse(s);
a.foo.bar.baz(); // compiles. explodes.
// unknown — must narrow before use
const u: unknown = JSON.parse(s);
if (typeof u === 'object' && u !== null && 'name' in u) {
console.log((u as { name: string }).name); // now safe
}
// never — no value can exist
function fail(msg: string): never { throw new Error(msg); }
What are generics?
Generics allow functions, classes, interfaces, and types to work with different data types while preserving type safety. Instead of using any, a generic acts as a placeholder type that is determined when the function or class is used.
// Generic Function
function first<T>(arr: T[]): T {
return arr[0];
}
const name = first(['Alice', 'Bob']);
const number = first([10, 20, 30]);
console.log(name); // Alice
console.log(number); // 10
// Generic Interface
interface ApiResponse<T> {
data: T;
success: boolean;
}
const response: ApiResponse<string[]> = {
data: ['Angular', 'React'],
success: true
};
Name some built-in utility types.
Partial<T> — all props optional (patch/update DTOs).Required<T> — all props required.Pick<T,K> / Omit<T,K> — select or exclude keys.Record<K,V> — a map/dictionary type.Readonly<T> — immutable props.
type UserPatch = Partial<User>;
type Roles = Record<string, boolean>;
What are enums, and what's a union alternative?
enum defines a set of named constants. Numeric enums generate reverse mappings; string enums are more readable in logs.
Many teams prefer a string literal union (type Size = 'sm' | 'md' | 'lg') — it's lighter and tree-shakable, with no runtime object.
enum Role { Admin = 'ADMIN', User = 'USER' }
Explain public, private, protected, and readonly in TypeScript.
TypeScript access modifiers control the visibility of class members:
- public (default) — accessible from anywhere.
- private — only inside the same class.
- protected — the class and its subclasses.
- readonly — assignable once (at declaration or in the constructor).
These are compile-time only and erased in the output JS (unlike JavaScript's #private, which is real runtime privacy).
class Person {
public name = 'John';
private age = 25;
protected city = 'Bangalore';
readonly country = 'India';
}
const person = new Person();
console.log(person.name); // ✅
// console.log(person.age); // ❌
// console.log(person.city); // ❌
console.log(person.country); // ✅
What is type narrowing / type guards?
Refining a broad type to a specific one within a block using checks like typeof, instanceof, in, or a custom type predicate (x is Cat). TypeScript then knows the precise type inside that branch.
function isCat(a: Animal): a is Cat { return 'meow' in a; }
Union vs intersection types.
Union (A | B) — the value is one of the types. Intersection (A & B) — the value has all properties of both.
type Id = string | number; // union
type Employee = Person & Staff; // intersection
What does strictNullChecks do in TypeScript?
strictNullChecks is a TypeScript compiler option that treats null and undefined as separate types. When enabled, they cannot be assigned to other types unless explicitly included using a union type. This helps catch null-related bugs at compile time instead of runtime.
// strictNullChecks = true
let username: string;
// username = null; // ❌ Error
// username = undefined; // ❌ Error
let name: string | null = null; // ✅
let email: string | undefined; // ✅
What are mapped types?
Types that transform each property of another type. Utility types like Partial and Readonly are mapped types under the hood.
type Optional<T> = { [K in keyof T]?: T[K] };
What do keyof and typeof do at the type level?
keyof T produces a union of a type's property names. Type-level typeof value captures the type of an existing value/const so you can derive types from data.
const config = { host: '', port: 0 };
type ConfigKey = keyof typeof config; // 'host' | 'port'
What are decorators (as used in Angular)?
Decorators are functions that add metadata/behaviour to classes, methods, properties, or parameters. Angular relies on them heavily: @Component, @Injectable, @Input, @Output.
@Input() label = '';
@Output() change = new EventEmitter();
What does 'as const' do?
A const assertion makes a value deeply readonly and narrows it to its literal types instead of the widened primitive — useful for building precise union types from arrays/objects.
const sizes = ['sm', 'md', 'lg'] as const;
type Size = typeof sizes[number]; // 'sm' | 'md' | 'lg'
What is the non-null assertion operator (!)?
The postfix ! tells the compiler 'this value is definitely not null/undefined here'. It removes the check but does not add a runtime guard — use it only when you're certain.
@ViewChild('el') el!: ElementRef; // asserted, assigned by Angular later
What is a discriminated (tagged) union?
A union of object types that share a common literal tag field. Switching on that tag lets TypeScript narrow to the exact member, giving type-safe, exhaustive handling — great for state machines and Redux/NgRx actions.
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'square'; side: number };
function area(s: Shape) {
switch (s.kind) {
case 'circle': return Math.PI * s.r ** 2;
case 'square': return s.side ** 2;
}
}
What are conditional types?
Types that branch based on a condition: T extends U ? X : Y. They power much of the standard library (e.g. Exclude, ReturnType) and let generic types adapt to their inputs.
type NonNull<T> = T extends null | undefined ? never : T;
type A = NonNull<string | null>; // string
What does the infer keyword do?
Inside a conditional type, infer captures a type from a position so you can reuse it — e.g. extract a function's return type or an array's element type.
type Return<T> = T extends (...args: any[]) => infer R ? R : never;
type R = Return<() => number>; // number
What is an index signature?
Declares that an object can have arbitrary keys of a given type: { [key: string]: number }. Useful for dictionaries when keys aren't known ahead of time. Record<K, V> is the shorthand.
interface Scores { [player: string]: number; }
const s: Scores = { alice: 10, bob: 7 };
What are tuple types in TypeScript?
A tuple is a special type of array where the number of elements and the type of each element are fixed. Unlike normal arrays, each position in a tuple has its own predefined type.
const employee: [string, number] = ['John', 25];
console.log(employee[0]); // John
console.log(employee[1]); // 25
What are function overloads in TypeScript?
Function overloads allow a function to have multiple call signatures while sharing a single implementation. Each overload describes a different way the function can be called, enabling TypeScript to infer the correct parameter and return types based on the arguments passed.
interface User {
id: number;
name: string;
}
function getUser(id: number): User;
function getUser(ids: number[]): User[];
function getUser(idOrIds: number | number[]) {
if (Array.isArray(idOrIds)) {
return idOrIds.map(id => ({ id, name: `User ${id}` }));
}
return {
id: idOrIds,
name: `User ${idOrIds}`
};
}
const user = getUser(1);
const users = getUser([1, 2, 3]);
What is the difference between an Abstract Class and an Interface in TypeScript?
- Abstract class — a base class you can't instantiate directly. It can hold both implemented methods and
abstract methods that subclasses must implement — so it shares real behavior. - Interface — only the shape/contract, with no implementation; a class can implement many.
Rule of thumb: abstract class = "what it is and how it works"; interface = "what it should look like".
abstract class Animal {
abstract makeSound(): void;
eat() {
console.log('Eating...');
}
}
class Dog extends Animal {
makeSound() {
console.log('Bark');
}
}
What does the satisfies operator do?
(TS 4.9+) satisfies checks that a value matches a type without widening it — you keep the precise inferred literal types while still validating the shape. Best of both worlds vs a plain annotation.
const routes = { home: '/', about: '/about' } satisfies Record<string, string>;
// routes.home is '/', not just string
What are Template Literal Types in TypeScript?
Template Literal Types allow you to create new string literal types by combining existing literal types using JavaScript template string syntax. They help generate strongly typed strings, such as event names, CSS classes, API routes, and object keys.
type Size = 'sm' | 'md' | 'lg';
type ButtonClass = `btn-${Size}`;
// 'btn-sm' | 'btn-md' | 'btn-lg'
How do you constrain generics using extends and keyof in TypeScript?
Generic constraints limit the types that can be passed to a generic. The extends keyword ensures a type has a required shape, while keyof restricts a value to the valid property names of an object. Together, they enable type-safe and reusable generic functions.
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = {
id: 1,
name: 'John'
};
console.log(getProp(user, 'name')); // John
// getProp(user, 'age'); // ❌ Error
Why TypeScript over JavaScript? And what does tsconfig strict mode buy you?
TypeScript is JavaScript + a static type system that runs at compile time and is erased at runtime. The wins:
- Errors at compile time, not in production — typos, wrong argument shapes, null access surface in the editor.
- Refactoring at scale — rename a field and every stale usage lights up; the compiler is your regression net.
- Self-documenting APIs + tooling — autocomplete, jump-to-def, IntelliSense all flow from types.
"strict": true turns on the family that makes types honest: strictNullChecks (null/undefined must be handled), noImplicitAny (no silent any), strictFunctionTypes, strictPropertyInitialization, and more. Without strict, TypeScript is mostly decoration.
// tsconfig.json — the non-negotiable core
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true, // arr[i] is T | undefined — honest!
"noImplicitOverride": true
}
}
Type assertion (as) vs casting — what are the risks?
value as T is not a cast — nothing is converted or checked at runtime. It's you overruling the compiler: "trust me, this is a T". If you're wrong, the error surfaces later as an undefined property somewhere far from the lie.
Risks: asserting API responses (res as User validates nothing), and double assertion (x as unknown as T) which can connect any two types.
Prefer, in order: proper typing/generics → type guards / narrowing (runtime-checked) → satisfies (checks without widening) → assertion as the last resort at genuine trust boundaries (DOM lookups, JSON you control).
// legit: you know more than the compiler
const input = document.getElementById('email') as HTMLInputElement;
// dangerous lie: no runtime check whatsoever
const user = (await res.json()) as User; // prefer: parse with zod
// checked alternative — narrowing:
if (isUser(data)) { data.name } // custom guard actually verified it
TypeScript private vs JavaScript # private fields?
TS private — a compile-time promise only; erased in the emitted JS, so at runtime the field is a normal property (obj['secret'] works, JSON.stringify includes it).
JS #field — real language-level privacy (ES2022): inaccessible outside the class at runtime, invisible to bracket access, Object.keys and JSON.
Other differences: # is "hard private" even from subclasses; TS also offers protected (no JS equivalent) and parameter properties (constructor(private http: HttpClient) — the Angular idiom).
class A {
private tsSecret = 1; // erased -> reachable at runtime
#jsSecret = 2; // truly private
}
const a = new A();
(a as any).tsSecret; // 1 — the 'privacy' was a type error only
// (a as any).#jsSecret // SyntaxError — # isn't even valid syntax outside
How do you make a generic fetch function resolve to the right type (e.g. fetchData<User>(url))?
Make the function generic and let the caller supply the type argument, so the return type flows through: function fetchData<T>(url: string): Promise<T>. Calling fetchData<User>(url) makes the promise resolve to User.
This is strictly better than the wrong alternatives: any throws away all safety, a type assertion inside the function lies to the compiler, and a global type forces one shape on every call. A type parameter keeps each call site correctly typed with zero casts.
function fetchData<T>(url: string): Promise<T> {
return fetch(url).then(r => r.json() as Promise<T>);
}
const user = await fetchData<User>('/api/user'); // user: User
How do you build a mapped type that makes all fields of a type optional (keeping their types)?
Iterate the keys with a mapped type and add the ? modifier, indexing back into the source for each value type:
type AllOptional<T> = { [K in keyof T]?: T[K] };
That's exactly what the built-in Partial<T> does — prefer Partial<ApiResponse> in real code. Writing { id?: number; name?: string; } by hand duplicates the shape and drifts when the source type changes; the mapped form stays in sync automatically.
Related modifiers: -? removes optionality (see Required<T>), readonly/-readonly toggle immutability.
type ApiResponse = { id: number; name: string; email?: string };
type Draft = { [K in keyof ApiResponse]?: ApiResponse[K] }; // = Partial<ApiResponse>
With strictNullChecks on, what does this return for null: input: string | null guarded by if (input) return input.trim(); return 'No input';?
It returns 'No input' — no error. null is falsy, so the if (input) guard is skipped and control falls through to the return 'No input'.
The point of strictNullChecks is that this is safe: inside the if, TypeScript narrows string | null to string, so input.trim() compiles. Without the guard, input.trim() would be a compile error ("Object is possibly 'null'"). The truthiness check both prevents the runtime crash and satisfies the compiler.
function processInput(input: string | null) {
if (input) return input.trim(); // input narrowed to string here
return 'No input'; // null falls through to here
}
processInput(null); // 'No input'
Spot the bug: a class method assigns currentClasses = { saveable: this.canSave, ... }.
Two this. mistakes to watch for:
- Missing
this. on the target — currentClasses = {} refers to a (non-existent) local/global, not the field. It must be this.currentClasses = {}. - Missing
this. on a member read — saveable: canSave looks up a free variable canSave, not the class member; it must be this.canSave.
In a class method, instance members — fields and other methods — are only reachable through this; there's no implicit scope like some languages. The correct version qualifies every member on both sides.
setCurrentClasses() {
this.currentClasses = { // this. on the target
saveable: this.canSave, // this. on each member read
modified: !this.isUnchanged,
special: this.isSpecial,
};
}