Ace your TypeScript interview with 50+ questions covering type system, generics, utility types, and best practices.
10 Questions
~30 min read
Both define object shapes, but: interfaces support declaration merging (can be extended by declaring again), types support unions and intersections more naturally. Interfaces are preferred for public APIs and when you might extend. Types are preferred for complex type compositions, unions, and primitives. Performance is essentially identical.
Utility types are built-in type transformations: Partial<T> makes all properties optional, Required<T> makes all required, Readonly<T> makes all readonly, Pick<T, K> selects subset of properties, Omit<T, K> removes properties, Record<K, V> creates object type. They reduce boilerplate and enable type manipulation.
void represents absence of return value (function returns nothing). never represents values that never occur (function throws or infinite loop). unknown is the type-safe counterpart to any - you must narrow the type before using it. Use void for function return types, never for exhaustive checks, unknown for values of unknown type.
Type narrowing refines a type to a more specific type within a code block. Type guards are expressions that perform runtime checks and narrow types: typeof (primitives), instanceof (classes), in (property check), custom type predicates (function returning "arg is Type"). TypeScript uses control flow analysis to track narrowed types.
Declaration merging combines multiple declarations with the same name into one. Interfaces can be merged (properties combined), namespaces can be merged, classes merge with namespaces. Use for: extending third-party types, module augmentation, adding properties to global scope. Types (type aliases) cannot be merged.
any disables type checking - you can do anything with it (unsafe). unknown requires type checking before use (type-safe). Use unknown when you receive data of unknown type (API responses, user input) and validate before use. Avoid any; it defeats TypeScript's purpose. unknown forces explicit type narrowing.
Generics allow creating reusable components that work with multiple types while maintaining type safety. Example: function identity<T>(arg: T): T { return arg; }. The type is determined when called: identity<string>("hello"). Use generics for: reusable functions, container types (arrays, promises), when relationships between input/output types matter.
Mapped types transform properties: { [K in keyof T]: newType }. Conditional types choose types based on conditions: T extends U ? X : Y. Combined, they enable powerful type transformations. Example: making all properties optional, filtering properties by type, extracting return types. They're the foundation of utility types.
infer declares a type variable within conditional types, extracting types from other types. Example: type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never extracts the return type. Used for: extracting array element types, promise resolved types, function parameter types. It enables complex type inference.
For function components: React.FC<Props> or (props: Props) => JSX.Element. Define props interface, use children: React.ReactNode, events like React.MouseEvent<HTMLButtonElement>. For hooks: useState<Type>(), useRef<HTMLDivElement>(null). Generic components: function List<T>(props: { items: T[] }). Avoid using any; prefer strict typing.