What is TypeScript?
TypeScript is JavaScript with a type system added on top. You write types, the compiler checks them, and then the types are removed - the browser runs plain JavaScript. The whole benefit happens before the code runs.
38 questions - Beginner to intermediate
TypeScript questions come up in almost every Angular interview and most React ones. Interviewers want to see that you use types to catch real bugs, not that you memorised syntax. Keep answers concrete.
TypeScript is JavaScript with a type system added on top. You write types, the compiler checks them, and then the types are removed - the browser runs plain JavaScript. The whole benefit happens before the code runs.
It catches typos and wrong shapes at compile time instead of in production, gives real autocomplete and safe renaming in the editor, and acts as documentation that cannot go out of date. On a large team that saves a lot of time.
TypeScript works out the type by itself from the value you assign, so you do not have to annotate everything. Annotate function parameters and public return types; let inference handle simple local variables.
let count = 5; // inferred as number
count = "five"; // Error
any turns all checking off and should be a last resort. unknown is the safe version: you can hold anything in it but you must narrow the type before using it. never means a value that can never happen, like the return type of a function that always throws.
let a: any = 1; a.foo(); // allowed, may crash
let u: unknown = 1;
// u.toFixed(); // Error
if (typeof u === "number") u.toFixed(); // fine
With strictNullChecks on, null and undefined are not allowed in a normal type, so you must say string | null and then check before use. That single flag prevents most "cannot read property of undefined" crashes.
A union with | means the value is one of these types. An intersection with & means the value has all of these types combined.
type Status = "idle" | "loading" | "error";
type Admin = User & { permissions: string[] };
A type whose only allowed value is one exact value, like "GET". Combined into a union it gives you a safe set of options, which is usually better than an enum.
An enum produces real JavaScript code at runtime and adds to the bundle. A union of string literals disappears at compile time and is easier to work with. Use const enum or a plain union unless you truly need the runtime object.
An array with a fixed length where each position has its own type. React useState returns a tuple: a value and a setter.
const point: [number, number] = [10, 20];
const pair: [string, boolean] = ["ok", true];
A question mark makes a property optional, so it may be missing. readonly stops assignment after the object is created. Both are compile-time only.
interface User {
id: number;
readonly createdAt: string;
nickname?: string;
}
Both describe the shape of an object. An interface can be reopened and added to later (declaration merging) and is the usual choice for public object shapes and classes. A type alias can also describe unions, tuples and mapped types, which an interface cannot. Pick one style and stay consistent.
interface Props { title: string; }
interface Props { subtitle?: string; } // merged, both exist
type Id = string | number; // interface cannot do this
TypeScript compares the shape of types, not their names. If an object has all the required properties with the right types, it fits - it does not have to declare that it implements anything.
When you assign an object literal directly, TypeScript complains about extra properties that are not in the type, to catch typos. Assigning through a variable first skips that check, because then it is only checking compatibility.
Generics let a function or type work with any type while keeping the relationship between input and output. The caller decides the concrete type, so you keep safety without repeating code.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefined
It limits what the generic type can be, using extends. That lets you safely use properties inside the function.
function getLength<T extends { length: number }>(x: T) {
return x.length;
}
keyof gives a union of the property names of a type. typeof in a type position takes the type of an existing value. Together they let you write code that stays correct when the object changes.
const config = { host: "localhost", port: 8080 };
type Config = typeof config; // { host: string; port: number }
type Key = keyof Config; // "host" | "port"
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
Partial makes everything optional, Required does the opposite, Pick keeps chosen keys, Omit removes keys, Record builds a key-value map, Readonly locks properties, and ReturnType extracts the return type of a function.
type Draft = Partial<User>;
type Preview = Pick<User, "id" | "name">;
type Safe = Omit<User, "password">;
type Lookup = Record<string, User>;
A type that builds a new type by looping over the keys of another one. Partial and Readonly are written this way internally.
type Nullable<T> = { [K in keyof T]: T[K] | null };
A type that chooses between two types based on a check, using the extends ? : form. It is how helpers like ReturnType are built.
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type A = Unwrap<Promise<string>>; // string
Inside a conditional type, infer captures a piece of the type into a new name so you can return it. It is how you pull the element type out of an array or the value out of a promise.
It is TypeScript reducing a union to a smaller type after a check. typeof, instanceof, the in operator, a truthy check or comparing a literal field all narrow.
function print(value: string | number) {
if (typeof value === "string") {
value.toUpperCase(); // string here
} else {
value.toFixed(2); // number here
}
}
A union of object types that all share one literal field, usually called kind or type. Checking that field narrows to exactly one member. It is the cleanest way to model API responses and Redux actions.
type Result =
| { status: "success"; data: User }
| { status: "error"; message: string };
function show(r: Result) {
if (r.status === "success") return r.data.name;
return r.message;
}
A function whose return type is written as "value is Type". When it returns true, TypeScript narrows the variable in the calling code. Useful for validating data that came from an API.
function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null && "id" in x;
}
Writing "as Type" tells the compiler to trust you. Nothing is checked at runtime, so if the data does not actually match, the crash happens later and further away. Prefer narrowing or a validation library.
It tells the compiler a value is definitely not null or undefined. Like as, it is unchecked, so use it only when you can prove it, for example right after an explicit guard.
public is the default and visible everywhere. private is visible only inside the class and is enforced at compile time only. protected is visible in the class and its subclasses. The JavaScript # private field is enforced at runtime as well.
A class you cannot instantiate directly. It can define some working methods and leave others abstract for subclasses to implement. Use it when several classes share real code; use an interface when you only need a contract.
A function that adds behaviour or metadata to a class, method or property using @ syntax. Angular is built on decorators such as @Component, @Injectable and @Input.
@Component({ selector: "app-user" })
export class UserComponent {
@Input() userId!: string;
}
Two declarations with the same name can be combined. Two interfaces merge into one, and a namespace can add to a class or function. It is how libraries extend types like the Express Request object.
A declaration file that describes the types of JavaScript code without containing any implementation. It is how you add types to an untyped library, either from DefinitelyTyped as @types/name or written yourself.
The configuration file that tells the compiler which files to include, which JavaScript version to output, which module system to use and how strict to be. It is the first file to check when a build behaves oddly.
A group of flags including strictNullChecks, noImplicitAny, strictFunctionTypes and strictPropertyInitialization. Turning it on for a new project is standard advice; on an old project turn the flags on one at a time.
It makes the compiler complain when it cannot infer a type and would otherwise silently use any - most often on function parameters. It is the single most valuable strict flag.
No. Everything is erased during compilation. If data comes from an API or a form, you still need a runtime check, either written by hand or with a validation library such as Zod.
A compile-time error is TypeScript refusing to build because the types do not fit; nothing has run yet. A runtime error happens in the browser. TypeScript only helps with the first kind, which is why input validation still matters.
It checks that a value matches a type without widening the value to that type, so you keep the precise inferred literal types and still get the error checking.
const routes = {
home: "/",
blog: "/blog",
} satisfies Record<string, string>;
// routes.home is "/" not just string
The return type is a Promise of the resolved value. TypeScript infers it, but writing it makes the contract clear.
async function getUser(id: string): Promise<User | null> {
const res = await fetch("/api/users/" + id);
return res.ok ? ((await res.json()) as User) : null;
}
Add TypeScript with allowJs so both file types build, rename files a few at a time starting with shared utilities, add types where they help most, turn on strict flags one by one, and avoid a big-bang rewrite.
CrackInterviewAI listens to the live interview and gives a structured answer on screen for coding, system design, HR and project questions. Download for Windows.