The questions that come up most often, each with a sample answer you can adapt into your own words. Read them out loud until the explanation feels natural.
What is structural typing and how does it differ from nominal typing?
IntermediateStructural typing means a value is assignable to a type if it has the required members, regardless of its declared name. Two independently declared types with the same shape are interchangeable. Nominal typing (Java, C#) requires an explicit declared relationship. TypeScript is structural, which is why an object literal with the right properties satisfies an interface it never names.
What is the difference between interface and type?
IntermediateBoth describe object shapes and are largely interchangeable. interface supports declaration merging (multiple declarations combine) and is conventional for public object/class contracts. type is more flexible: it can alias unions, intersections, primitives, tuples, and mapped/conditional types. The rule of thumb is interface for object shapes you might extend, type for unions and computed types.
What are generics and why use them?
IntermediateGenerics let you write a function or type parameterised over a type, so it stays reusable without resorting to any and without losing type information. For example, function identity<T>(x: T): T preserves the input type in the output. They are essential for collections, utility functions, and APIs that should work over many types while keeping the relationship between inputs and outputs type-checked.
What is the difference between any and unknown?
Intermediateany disables type checking entirely; you can do anything with it and the compiler stays silent, which defeats the purpose of TypeScript. unknown is the type-safe counterpart: it accepts any value, but you cannot use it until you narrow it with a guard. Use unknown at boundaries (parsed JSON, external input) and narrow before use; avoid any except as a deliberate, isolated escape hatch.
What is a discriminated union and why is it useful?
AdvancedA discriminated (tagged) union is a union of object types that share a common literal property, like { kind: 'circle'; r: number } | { kind: 'square'; side: number }. Switching on the discriminant lets the compiler narrow to the exact member in each branch, so you get exhaustive, type-safe handling. Adding a never check in the default branch makes the compiler flag any unhandled case.
What is a type guard / user-defined type predicate?
AdvancedA type guard narrows a value's type within a scope. Built-in guards include typeof, instanceof, and the in operator. A user-defined guard is a function returning a predicate like value is Cat; when it returns true the compiler treats the argument as that type in the calling code. They are how you safely refine unknown or a broad union into a specific type.
Name some utility types and what they do.
IntermediatePartial<T> makes all properties optional, Required<T> the reverse; Pick<T, K> selects a subset of keys and Omit<T, K> drops keys; Record<K, V> builds an object type with given key and value types; ReturnType<F> and Parameters<F> extract a function's return and argument types. They let you derive new types from existing ones instead of duplicating shapes.
What does enabling strict mode change?
Advancedstrict turns on a family of checks, most importantly strictNullChecks, which removes null and undefined from every type unless you explicitly include them. This forces you to handle the absent case, eliminating a large class of runtime errors. It also enables noImplicitAny and stricter function and this checks. Most teams treat strict as mandatory for new code.
What is a mapped type?
AdvancedA mapped type builds a new object type by iterating over the keys of another with the in keyof construct, transforming each property as it goes. The built-in utility types are mapped types under the hood: Partial adds ? to every property, Readonly adds readonly. You write your own when you need a systematic transformation, like making every field nullable or wrapping every value in a promise. Being able to sketch one shows you can read the standard library's own type definitions rather than treating them as magic.
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type User = { id: number; name: string };
type DraftUser = Nullable<User>;
// { id: number | null; name: string | null }
What do keyof and typeof do in type positions?
Intermediatekeyof takes an object type and produces a union of its property names, so keyof User might be 'id' | 'name'. typeof, in a type position, lifts a value's type so you can reuse it without writing it out, most usefully applied to a const configuration object. Combined, keyof typeof config gives you the keys of that object as a type, which powers safely typed lookup functions where an invalid key is a compile error rather than an undefined at runtime.
const statuses = {
open: "Open",
closed: "Closed",
} as const;
type StatusKey = keyof typeof statuses; // "open" | "closed"
function label(key: StatusKey): string {
return statuses[key];
}
What does as const do?
Intermediateas const is a const assertion: it tells the compiler to infer the narrowest possible type. Literals keep their literal types instead of widening (so 'GET' stays 'GET' rather than string), arrays become readonly tuples, and object properties become readonly. It is the idiomatic way to define fixed sets of values, since keyof typeof over an as const object yields a precise union without maintaining a separate type by hand. Without it, the widened types quietly erase the safety you were trying to build.
Would you use an enum or a union of string literals?
IntermediateBoth express a fixed set of choices. A union like 'draft' | 'published' is the lighter option: it is erased at compile time, works structurally, serialises naturally as plain strings, and pairs well with as const objects when you also need runtime values. enum creates a real object at runtime and behaves nominally, which can help with numeric flags but surprises people, especially the numeric variety, which historically accepted any number. Most modern TypeScript style guides lean towards literal unions; explaining the tradeoff rather than answering dogmatically is the good answer.