What will happen when this code runs?
let x: number = 5;
let y: string = x;TypeScript does not allow assigning a number value to a string variable. This is a compile-time error caught by the type system.
All questions with correct answers and explanations.
let x: number = 5;
let y: string = x;TypeScript does not allow assigning a number value to a string variable. This is a compile-time error caught by the type system.
? mean for age?interface User {
name: string;
age?: number;
}
const user: User = { name: "Alice" };The ? after a property in an interface marks it as optional. The object doesn't need to include it.
type Status = "active" | "inactive" | "pending";
const s: Status = "deleted";A union type restricts allowed values. 'deleted' is not part of the Status type, so the compiler will report an error.
identity<string>(42)?function identity<T>(arg: T): T {
return arg;
}
const result = identity<string>(42);Generic T was explicitly set to string, but argument 42 is of type number. TypeScript will report a compile error.
console.log(Direction.Up) print?enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up);In a numeric enum without explicit values, the first member has value 0, and subsequent members increment by 1.
push?const arr: readonly number[] = [1, 2, 3];
arr.push(4);A readonly array does not have mutating methods like push, pop, splice. This is a type-level protection.
& operator do in type context?type Point = {
x: number;
y: number;
};
type Point3D = Point & { z: number };
const p: Point3D = { x: 1, y: 2, z: 3 };The & operator creates an intersection type that combines all properties from both types into one.
void return type mean?function greet(name: string): void {
console.log(`Hello, ${name}`);
}void means the function doesn't return a value. It differs from never, which means the function never terminates.
as const do in this context?const obj = { name: "Alice", age: 30 } as const;as const creates a const assertion — all properties become readonly with literal types (e.g., 'Alice' instead of string).
type StringOrNumber = string | number;
function process(val: StringOrNumber) {
if (typeof val === "string") {
return val.toUpperCase();
}
return val.toFixed(2);
}Type narrowing is a technique to narrow the type inside a conditional block. TypeScript automatically recognizes the type after a typeof check.
Config.interface Config {
debug: boolean;
}
interface Config {
verbose: boolean;
}
const cfg: Config = { debug: true, verbose: false };Declaration Merging is a unique feature of interfaces — multiple declarations with the same name merge into one type. Type aliases (type) do not support this.
function handleInput(val: unknown) {
return val.toUpperCase();
}The unknown type is a safe alternative to any. You must narrow the value before use, e.g., via typeof val === 'string'. Key difference: any disables checking, unknown enforces it.
val is string mean in the return type?function isString(val: unknown): val is string {
return typeof val === "string";
}
function process(input: unknown) {
if (isString(input)) {
console.log(input.toUpperCase());
}
}A type predicate (val is string) is a custom type guard. When the function returns true, TypeScript automatically narrows the type in the conditional block. Stronger than a regular boolean return.
let a: Object = "hello";
let b: object = "hello";
let c: {} = "hello";Object (capital O) and {} accept primitives (string, number). object (lowercase) only accepts non-primitive types (objects, arrays, functions). String is a primitive, so b will error.
new Animal()?abstract class Animal {
abstract speak(): string;
greet(): string {
return `I say: ${this.speak()}`;
}
}
class Dog extends Animal {
speak() { return "Woof!"; }
}
const a = new Animal();An abstract class is a base class that cannot be instantiated. It forces subclasses to implement abstract methods. new Dog() works, new Animal() does not.
class User {
constructor(
public name: string,
private password: string,
protected role: string
) {}
}
const u = new User("Alice", "secret", "admin");
console.log(u.name, u.password, u.role);public — accessible everywhere. private — only within the class. protected — within the class and its subclasses. Only name is visible from outside.
const user = {
name: "Alice",
address: {
city: null as string | null,
},
};
const city = user.address?.city ?? "Unknown";
console.log(city);?. (optional chaining) safely accesses nested fields. ?? (nullish coalescing) returns the right side only when the left is null/undefined. city is null, so the result is 'Unknown'.
! operator after input do and is it safe?function getLength(input: string | null): number {
return input!.length;
}The !. (non-null assertion) tells the compiler: 'I know this is not null'. It generates no runtime code — if the value is actually null, you'll get a runtime error. Prefer type guards instead.
Pair and why won't wrong compile?type Pair = [string, number];
const p: Pair = ["age", 30];
const [label, value] = p;
const wrong: Pair = [30, "age"];A tuple is a fixed-length array with specific types at each position. [string, number] requires a string at index 0 and a number at index 1. Reversed order is an error.
type and interface when extending types?interface User {
name: string;
}
type Admin = User & { role: string };
// vs
interface Manager extends User {
department: string;
}Interface extends via extends and supports declaration merging (multiple declarations). Type uses & (intersection) and handles unions, tuples, and primitives. For public APIs, interface is recommended.
type Person = {
name: string
age: number
}
type Employee = {
name: string
email: string
}
type A = Person | Employee;
type B = Person & Employee;
const a: A = {
name: 'John',
age: 20,
email: 'john@test-email.co.uk',
}
const b: B = {
name: 'John',
age: 20,
email: 'john@test-email.co.uk',
}
console.log('A:', a.name, a.age, a.email)
console.log('B:', b.name, b.age, b.email)This is a trap! Even though object a has all fields, type A = Person | Employee means 'either Person or Employee'. TypeScript doesn't know which variant you have — it could be Person (no email) or Employee (no age). So a.age and a.email cause errors without narrowing. However, B = Person & Employee requires ALL fields from both types, so b.name, b.age, and b.email are guaranteed.
val have in the last return?function formatValue(val: string | number | boolean) {
if (typeof val === "string") {
return val.toUpperCase();
}
if (typeof val === "number") {
return val.toFixed(2);
}
// Co wie TypeScript o val tutaj?
return val;
}TypeScript progressively narrows the type after each type guard. After typeof === 'string' it eliminates string, after typeof === 'number' it eliminates number. Only boolean remains. This is automatic type narrowing — the compiler figures it out.
res.data in the success block and res.message in the error block?type ApiResponse<T> =
| { status: "success"; data: T; timestamp: number }
| { status: "error"; message: string; code: number }
| { status: "loading" };
function handleResponse(res: ApiResponse<string[]>) {
if (res.status === "success") {
console.log(res.data.join(", "));
} else if (res.status === "error") {
console.log(res.message);
}
}status field as a tag lets TypeScript narrow the type in each branchA discriminated union uses a common field (status) to let TypeScript automatically narrow the type. In the status === 'success' block, the compiler knows you have the variant with data and timestamp. In error — message and code. No casts needed.
ConfigKey and ConfigValue be?const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
debug: false,
} as const;
type Config = typeof config;
type ConfigKey = keyof Config;
type ConfigValue = Config[ConfigKey];Thanks to as const, object values keep their literal types (not string/number/boolean). typeof config extracts the object type. keyof gives a union of keys. Config[ConfigKey] is an indexed access type — a union of ALL value types. A powerful combo for type-safe configuration.
in operator work as a type guard?interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function move(animal: Bird | Fish) {
if ("fly" in animal) {
animal.fly();
} else {
animal.swim();
}
}if block TypeScript knows we have Bird'fly' in animal checks at runtime if the object has the fly method. TypeScript recognizes this as a type guard: in the if block it knows we have Bird (only Bird has fly). In else it knows we have Fish. The in operator is an alternative to instanceof when working with interfaces.
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // ?
type B = IsString<number>; // ?Conditional types work like ternary: T extends string checks if T extends string. For string → 'yes', for number → 'no'.
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};DeepReadonly recursively traverses all object properties. If a property is an object, it applies itself again. Result: the entire object becomes immutable.
Result be?type ExtractReturn<T> = T extends (...args: any[]) => infer R
? R
: never;
type Result = ExtractReturn<() => Promise<string>>;The infer R keyword extracts the function return type. The function returns Promise<string>, so R = Promise<string>.
ClickEvent be?type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">;Template literal types allow creating new string types. Capitalize uppercases the first letter: 'click' → 'Click', resulting in 'onClick'.
type Flatten<T> = T extends Array<infer U> ? U : T;
type A = Flatten<string[]>;
type B = Flatten<number>;Flatten extracts the element type from an array using infer. string[] → string. For number (non-array) it returns the type itself.
Result be?type Exclude<T, U> = T extends U ? never : T;
type Result = Exclude<"a" | "b" | "c", "a" | "c">;Exclude works distributively over union types. For each member: if it extends U → never (removed), otherwise kept. Only 'b' remains.
UserGetters be?type Getters<T> = {
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};
type UserGetters = Getters<{ name: string; age: number }>;Key remapping (as) in mapped types allows renaming keys. Here each key K is transformed to get${Capitalize<K>}, and the value becomes a getter function.
assertNever function?function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
type Shape = "circle" | "square";
function area(shape: Shape) {
switch (shape) {
case "circle": return Math.PI;
case "square": return 1;
default: return assertNever(shape);
}
}assertNever accepts never — a type that should never exist. If you add a new variant to Shape without handling it in switch, the compiler will report an error.
Result be?type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends
(x: infer I) => void ? I : never;
type Result = UnionToIntersection<{ a: 1 } | { b: 2 }>;UnionToIntersection converts a union to an intersection using contravariance of function parameter positions. Result: { a: 1 } & { b: 2 }.
payUSD(euros)?declare const brand: unique symbol;
type Branded<T, B> = T & { [brand]: B };
type USD = Branded<number, "USD">;
type EUR = Branded<number, "EUR">;
function payUSD(amount: USD) { /* ... */ }
const euros = 100 as EUR;
payUSD(euros);Branded types (nominal types) add a 'phantom brand' to a structural type. USD and EUR are distinct types even though both are based on number.
-? and -readonly modifiers do in mapped types?type MakeRequired<T> = {
[P in keyof T]-?: T[P];
};
type MakeMutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface Config {
readonly host?: string;
readonly port?: number;
}
type WritableConfig = MakeMutable<MakeRequired<Config>>;The - prefix in mapped types removes a modifier: -? makes a field required (like Required<T>), -readonly makes a field mutable. WritableConfig becomes { host: string; port: number }.
UserPreview and UserWithoutEmail have?interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserPreview = Pick<User, "id" | "name">;
type UserWithoutEmail = Omit<User, "email">;Pick<T, K> selects specified keys from a type. Omit<T, K> removes specified keys. Both create a new type with a subset of the original's properties.
Record<Roles, boolean> do and what would happen if we omitted the viewer key?type Roles = "admin" | "editor" | "viewer";
type Permissions = Record<Roles, boolean>;
const perms: Permissions = {
admin: true,
editor: true,
viewer: false,
};Record<K, V> creates a type with keys K and values V. All keys are required — omitting any is a compile error.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rect":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
}
}kind field lets TypeScript narrow the type in each branchA Discriminated Union uses a tag field (discriminant) like kind. TypeScript automatically narrows the type after checking the tag in switch/if — giving access to variant-specific fields.
function logLength<T extends { length: number }>(obj: T): void {
console.log(obj.length);
}
logLength("hello");
logLength([1, 2, 3]);
logLength(42);extends { length: number } constraint requires itThe generic constraint T extends { length: number } requires T to have a length property. String and array have length, so they pass. Number doesn't — compile error.
Result be?function getParams<T extends (...args: any[]) => any>(
fn: T
): Parameters<T> {
return [] as any;
}
type Result = Parameters<(a: string, b: number) => void>;Parameters<T> extracts the parameter types of a function as a tuple. For (a: string, b: number) => void it returns [string, number].
Result be?type NonStringKeys<T> = {
[K in keyof T]: T[K] extends string ? never : K;
}[keyof T];
interface User {
id: number;
name: string;
age: number;
email: string;
}
type Result = NonStringKeys<User>;This type filters keys: if the value is string → never (removed), otherwise keeps the key name. id and age are number, so they remain. name and email (string) are removed.
Numbers be?type Extract<T, U> = T extends U ? T : never;
type Numbers = Extract<string | number | boolean, number | boolean>;Extract<T, U> is the opposite of Exclude — it keeps from union T only the types assignable to U. string is not in U, so it's removed. number | boolean remains.
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true
}
}
function add(a, b) {
return a + b;
}strict: true enables noImplicitAny among others. Parameters a and b have no types and can't be inferred from context — the compiler would default to any, but the flag prevents this.
type ReadOnly<T> = { readonly [P in keyof T]: T[P] };
type MyPartial<T> = { [P in keyof T]?: T[P] };
interface Todo {
title: string;
done: boolean;
}
type A = ReadOnly<Todo>;
type B = MyPartial<Todo>;Mapped types iterate over type keys and allow adding modifiers. ReadOnly adds readonly to each field, MyPartial adds ? (optionality). This is how the built-in Readonly<T> and Partial<T> work.
CSSSpacing require and how are they generated?type CSSProperty = "margin" | "padding";
type Direction = "top" | "right" | "bottom" | "left";
type CSSSpacing = {
[K in `${CSSProperty}-${Direction}`]: string;
};
const spacing: CSSSpacing = {
"margin-top": "10px",
"margin-right": "20px",
// ... wymaga wszystkich 8 kombinacji
};A template literal type in a mapped type creates ALL combinations: margin-top, margin-right, margin-bottom, margin-left, padding-top, padding-right, padding-bottom, padding-left = 8 fields. TypeScript generates a Cartesian product of strings. A powerful tool for CSS-in-JS type generation.
type Awaited<T> =
T extends Promise<infer U>
? Awaited<U>
: T;
type A = Awaited<Promise<string>>;
type B = Awaited<Promise<Promise<number>>>;
type C = Awaited<boolean>;Awaited recursively 'unwraps' Promises: Promise<string> → string, Promise<Promise<number>> → Promise<number> → number. If T isn't a Promise, it returns T unchanged (boolean). This is a simplified version of the built-in Awaited<T> from TypeScript 4.5+.
type ToArray<T> = T extends any ? T[] : never;
type A = ToArray<string | number>;
type B = ToArray<string | number | boolean>;Conditional types with parameter T distribute over unions! TypeScript applies the condition to EACH union member separately: string → string[], number → number[]. The result is a union of arrays, not an array of unions. To disable distribution, wrap T in [T].
r and g be and how does satisfies differ from : Colors?type Colors = Record<string, [number, number, number] | string>;
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Colors;
const r = palette.red; // jaki typ?
const g = palette.green; // jaki typ?The satisfies operator (TS 4.9+) checks if a value conforms to a type but does NOT widen the variable's type. With : Colors, both fields would have type [number,number,number] | string. With satisfies, TypeScript keeps narrow types — red is a tuple, green is a string.
StringFields and NumberFields have?type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Model {
id: number;
name: string;
active: boolean;
email: string;
age: number;
}
type StringFields = PickByType<Model, string>;
type NumberFields = PickByType<Model, number>;Key remapping with as + conditional type allows filtering keys. When T[K] extends U is false, the key maps to never — removing it from the result. Result: StringFields has fields whose values are string, NumberFields — number.
console.log print and what is this technique called?const add = (a: number) => (b: number) => a + b;
const add5 = add(5);
console.log(add5(3));Currying transforms a multi-argument function into a sequence of single-argument functions. add(5) returns (b) => 5 + b, so add5(3) = 8.
transform(5) return?const pipe = <T>(...fns: Array<(arg: T) => T>) =>
(value: T): T =>
fns.reduce((acc, fn) => fn(acc), value);
const transform = pipe(
(x: number) => x * 2,
(x: number) => x + 1,
(x: number) => x * 3
);
console.log(transform(5));Pipe executes functions left to right: 5 → *2 = 10 → +1 = 11 → *3 = 33.
type Option<T> = { tag: "some"; value: T } | { tag: "none" };
const map = <A, B>(opt: Option<A>, fn: (a: A) => B): Option<B> =>
opt.tag === "some"
? { tag: "some", value: fn(opt.value) }
: { tag: "none" };
const result = map({ tag: "some", value: 5 }, x => x * 2);The Option (Maybe) monad wraps a value that may not exist. The map function is a functor operation — it transforms the value inside the container without changing the structure.
const compose = <A, B, C>(
f: (b: B) => C,
g: (a: A) => B
) => (a: A): C => f(g(a));
const double = (x: number) => x * 2;
const toString = (x: number) => `Value: ${x}`;
const doubleThenString = compose(toString, double);
console.log(doubleThenString(5));Compose executes functions right to left (mathematical composition f∘g). Pipe does the opposite — left to right.
flatMap operation called in the context of monads?type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
const flatMap = <T, E, U>(
result: Result<T, E>,
fn: (value: T) => Result<U, E>
): Result<U, E> =>
result.ok ? fn(result.value) : result;
const parse = (s: string): Result<number, string> => {
const n = Number(s);
return isNaN(n)
? { ok: false, error: "Not a number" }
: { ok: true, value: n };
};flatMap (bind/chain) is the fundamental monadic operation. It differs from map in that the function returns a new container (Result), and flatMap 'flattens' the result.
const memoize = <T extends (...args: any[]) => any>(fn: T): T => {
const cache = new Map<string, ReturnType<T>>();
return ((...args: Parameters<T>): ReturnType<T> => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key)!;
const result = fn(...args);
cache.set(key, result);
return result;
}) as T;
};
const factorial = memoize((n: number): number =>
n <= 1 ? 1 : n * factorial(n - 1)
);Memoization is a technique that remembers function results for given arguments. Subsequent calls with the same arguments return cached results instead of recomputing.
updated?type Lens<S, A> = {
get: (s: S) => A;
set: (a: A, s: S) => S;
};
const nameLens: Lens<{ name: string; age: number }, string> = {
get: (s) => s.name,
set: (a, s) => ({ ...s, name: a }),
};
const user = { name: "Alice", age: 30 };
const updated = nameLens.set("Bob", user);Lens is a functional programming pattern for immutable updates of nested structures. Set creates a new object with the updated field, preserving the rest.
type Predicate<T> = (value: T) => boolean;
const and = <T>(...preds: Predicate<T>[]): Predicate<T> =>
(value) => preds.every(p => p(value));
const or = <T>(...preds: Predicate<T>[]): Predicate<T> =>
(value) => preds.some(p => p(value));
const not = <T>(pred: Predicate<T>): Predicate<T> =>
(value) => !pred(value);
const isEven: Predicate<number> = n => n % 2 === 0;
const isPositive: Predicate<number> = n => n > 0;
const isOddAndPositive = and(not(isEven), isPositive);
console.log(isOddAndPositive(3), isOddAndPositive(-3));Predicate composition: 3 is odd (not even) and positive → true. -3 is odd but not positive → false.
Task represent?type Task<T> = {
fork: (reject: (e: Error) => void, resolve: (t: T) => void) => void;
};
const taskOf = <T>(value: T): Task<T> => ({
fork: (_, resolve) => resolve(value),
});
const taskMap = <A, B>(task: Task<A>, fn: (a: A) => B): Task<B> => ({
fork: (reject, resolve) =>
task.fork(reject, (a) => resolve(fn(a))),
});Task is a lazy async monad. Unlike Promise, it doesn't execute immediately — computation starts only when fork() is called.
const trampoline = <T>(fn: () => T | (() => T)): T => {
let result = fn();
while (typeof result === "function") {
result = (result as () => T)();
}
return result;
};
const sumTo = (n: number, acc: number = 0): any =>
n === 0 ? acc : () => sumTo(n - 1, acc + n);
console.log(trampoline(() => sumTo(100000)));Trampoline converts tail recursion into a while loop, avoiding stack overflow. Instead of calling itself recursively, it returns a thunk (function) that is executed in the loop.
const double = (x: number): number => x * 2;
const result1 = double(5);
const result2 = double(5);
let counter = 0;
const impureDouble = (x: number): number => {
counter++;
return x * 2;
};A pure function meets two conditions: (1) same input → same output, (2) zero side effects. impureDouble mutates the external variable counter, so it's impure. Referential transparency means double(5) can always be replaced with the value 10.
withLogging represent and what is this type of function called?const withLogging = <T extends (...args: any[]) => any>(fn: T) =>
(...args: Parameters<T>): ReturnType<T> => {
console.log("Calling with:", args);
const result = fn(...args);
console.log("Result:", result);
return result;
};
const add = (a: number, b: number) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3);A Higher-Order Function (HOF) is a function that takes or returns another function. withLogging takes function fn and returns a new function with added logging — like a decorator. In FP, functions are first-class citizens.
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count,
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount());A closure is a mechanism where a function retains access to variables from the scope in which it was defined — even after that scope has ended. count lives in the closure of createCounter.
result be?type Either<L, R> =
| { tag: "left"; value: L }
| { tag: "right"; value: R };
const left = <L>(value: L): Either<L, never> =>
({ tag: "left", value });
const right = <R>(value: R): Either<never, R> =>
({ tag: "right", value });
const map = <L, A, B>(
either: Either<L, A>,
fn: (a: A) => B
): Either<L, B> =>
either.tag === "right"
? right(fn(either.value))
: either;
const result = map(right(10), x => x * 2);Either is a monad with two variants: Left (conventionally error) and Right (success). The map function transforms the value only in Right, passing Left through unchanged — an alternative to try/catch.
type Thunk<T> = () => T;
const lazyValue: Thunk<number> = () => {
console.log("Computing...");
return 42;
};
// Wartość nie jest jeszcze obliczona
console.log("Before");
const result = lazyValue();
console.log("After:", result);A Thunk wraps a computation/effect in a function for deferred execution (lazy evaluation). The computation doesn't start until you call the thunk — giving control over when it runs.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Podejście imperatywne
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
sum += numbers[i] * 2;
}
}
// Podejście deklaratywne
const result = numbers
.filter(n => n % 2 === 0)
.map(n => n * 2)
.reduce((acc, n) => acc + n, 0);Declarative style (FP) says 'what' to do: filter even, double, sum. Imperative says 'how': iterate, check, add. Declarative is more readable and less error-prone.
safeDivide (partial) and totalDivide (total)?const safeDivide = (a: number, b: number): number | never => {
if (b === 0) throw new Error("Division by zero");
return a / b;
};
const totalDivide = (a: number, b: number): number =>
b === 0 ? 0 : a / b;A total function is defined for every input in its domain — always returns a result. A partial function may throw an exception (like safeDivide for b=0). In FP, we prefer total functions.
const idempotent = (arr: number[]): number[] =>
[...new Set(arr)].sort((a, b) => a - b);
console.log(idempotent([3, 1, 2, 1, 3]));
console.log(idempotent(idempotent([3, 1, 2, 1, 3])));Idempotency: f(f(x)) === f(x). The first call deduplicates and sorts the array. A second call on the already-processed result gives an identical result. Examples: Math.abs, Array.sort on a sorted array.
result be? Which array methods are key in FP and why?const users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 35 },
];
const result = users
.filter(u => u.age >= 30)
.map(u => u.name)
.reduce((acc, name) => acc + ", " + name);filter (select), map (transform), and reduce (fold) are fundamental FP operations on collections. They're declarative, don't mutate the source, and can be chained (pipeline). Result: Alice and Charlie are >= 30.
result and fallback be?const Maybe = <T>(value: T | null | undefined) => ({
map: <U>(fn: (val: T) => U) =>
value != null ? Maybe(fn(value)) : Maybe<U>(null),
getOrElse: (defaultVal: T) =>
value != null ? value : defaultVal,
});
const result = Maybe("hello")
.map(s => s.toUpperCase())
.map(s => s + "!")
.getOrElse("default");
const fallback = Maybe<string>(null)
.map(s => s.toUpperCase())
.getOrElse("default");Maybe handles null/undefined: if the value exists, map transforms it further. If null — the entire chain is skipped, and getOrElse returns the default. An alternative to chains of if-null checks.
result be and why is this code style preferred in FP?const users = [
{ name: "Alice", age: 30, role: "admin" },
{ name: "Bob", age: 25, role: "user" },
{ name: "Charlie", age: 35, role: "admin" },
{ name: "Diana", age: 28, role: "user" },
] as const;
const result = users
.filter(u => u.role === "admin")
.map(u => ({ ...u, name: u.name.toUpperCase() }))
.sort((a, b) => a.age - b.age);The filter→map→sort pipeline is the essence of FP: each operation creates a new structure, the original stays untouched. Declarative style says WHAT you're doing (filter admins, uppercase names, sort by age), not HOW. Easy to read, test, and debug.
type Fn<A, B> = (a: A) => B;
const pipe2 = <A, B, C>(
f: Fn<A, B>,
g: Fn<B, C>
): Fn<A, C> => (a) => g(f(a));
const parse = (s: string): number => parseInt(s, 10);
const double = (n: number): number => n * 2;
const format = (n: number): string => `Result: ${n}`;
const transform = pipe2(pipe2(parse, double), format);
console.log(transform("21"));pipe2 composes two functions: f: A→B and g: B→C giving A→C. Generics guarantee types match — you can't compose string→number with boolean→string because B must match. parse('21')=21, double(21)=42, format(42)='Result: 42'.
Tree<T> represent?type Tree<T> =
| { type: "leaf"; value: T }
| { type: "node"; left: Tree<T>; right: Tree<T> };
const sumTree = (tree: Tree<number>): number =>
tree.type === "leaf"
? tree.value
: sumTree(tree.left) + sumTree(tree.right);
const myTree: Tree<number> = {
type: "node",
left: { type: "leaf", value: 1 },
right: {
type: "node",
left: { type: "leaf", value: 2 },
right: { type: "leaf", value: 3 },
},
};
console.log(sumTree(myTree));Tree<T> is an algebraic data type (ADT) — a discriminated union with a recursive definition. sumTree recursively sums: node(leaf(1), node(leaf(2), leaf(3))) = 1 + (2 + 3) = 6. ADTs are fundamental to FP — they model data structures without classes, purely through types.
const map = <A, B>(arr: A[], fn: (a: A) => B): B[] =>
arr.map(fn);
const identity = <T>(x: T): T => x;
const double = (x: number) => x * 2;
const addOne = (x: number) => x + 1;
// Law 1: Identity
map([1, 2, 3], identity) // === [1, 2, 3]
// Law 2: Composition
map(map([1, 2, 3], double), addOne)
// ===
map([1, 2, 3], x => addOne(double(x)))A functor is a container (Array, Maybe, Promise) with a map operation satisfying two laws: (1) mapping identity doesn't change the container, (2) mapping two functions sequentially gives the same result as mapping their composition. This guarantees predictable behavior.
result be and what is a transducer?type Reducer<A, B> = (acc: B, val: A) => B;
type Transducer<A, B> = <C>(rf: Reducer<B, C>) => Reducer<A, C>;
const mapT = <A, B>(fn: (a: A) => B): Transducer<A, B> =>
(rf) => (acc, val) => rf(acc, fn(val));
const filterT = <A>(pred: (a: A) => boolean): Transducer<A, A> =>
(rf) => (acc, val) => pred(val) ? rf(acc, val) : acc;
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const isEven = (n: number) => n % 2 === 0;
const triple = (n: number) => n * 3;
const xform = (rf: Reducer<number, number[]>) =>
filterT(isEven)(mapT(triple)(rf));
const result = nums.reduce(
xform((acc, val) => [...acc, val]),
[] as number[]
);A transducer combines filter and map in a single pass — no intermediate arrays like a .filter().map() chain. Filters even numbers (2,4,6,8,10) and multiplies ×3 in one reduction. More efficient than chains on large datasets.
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
}
const Button = ({ label, onClick, variant = "primary" }: ButtonProps) => (
<button onClick={onClick} className={variant}>
{label}
</button>
);Best practice is to define an interface/type for props and use it as the parameter type. Optional props use ?, and defaults are provided in destructuring.
setCount('hello') cause an error?const [count, setCount] = useState(0);
setCount("hello");TypeScript infers state type from the initial value. useState(0) gives type number, so setCount only accepts number. For union types use useState<string | number>(0).
useState<User | null>(null)?const [user, setUser] = useState<User | null>(null);
if (user) {
console.log(user.name);
}null — it wouldn't know about UserWhen the initial value is null, TypeScript infers the type as null. The explicit generic <User | null> says: state can be User or null. After checking if (user), TypeScript narrows to User.
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget.value);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};React provides its own event types (React.MouseEvent, React.ChangeEvent, React.FormEvent, etc.) with a generic for the HTML element. This gives full access to typed element properties via currentTarget.
useRef<HTMLInputElement>(null) need a generic?const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;useRef<T>(null) creates a ref with type T | null. The generic tells TypeScript that .current will be HTMLInputElement (or null before mount). This gives full autocomplete for DOM API.
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}</ul>;
}
<List items={["a", "b"]} renderItem={(item) => <span>{item}</span>} />A generic component uses a type parameter <T> in both the props interface and the component. TypeScript infers T from passed data — here string[] → T = string. Rendering is fully typed.
ThemeContextType | undefined?interface ThemeContextType {
theme: "light" | "dark";
toggle: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}createContext needs a default value. Undefined + custom hook with a guard is a safe pattern: if a component is not in a provider, the hook throws a readable error instead of giving undefined.
children and how does it differ from React.ReactElement?interface CardProps {
title: string;
children: React.ReactNode;
}
const Card = ({ title, children }: CardProps) => (
<div>
<h2>{title}</h2>
<div>{children}</div>
</div>
);React.ReactNode is the broadest type for children: JSX, string, number, boolean, null, undefined, arrays. React.ReactElement is strictly a JSX element (<Component /> or <div />). For children, almost always use ReactNode.
React.ComponentPropsWithoutRef<'button'> do?type ButtonProps = React.ComponentPropsWithoutRef<"button"> & {
variant?: "primary" | "danger";
};
const Button = ({ variant = "primary", children, ...rest }: ButtonProps) => (
<button className={variant} {...rest}>
{children}
</button>
);ComponentPropsWithoutRef<'element'> extracts the full set of props from a native HTML element. Combining with & lets you add custom props (variant). An ideal pattern for building reusable UI components.
React.memo work with TypeScript?const MemoizedList = React.memo(function UserList({
users,
onSelect,
}: {
users: User[];
onSelect: (user: User) => void;
}) {
return (
<ul>
{users.map((u) => (
<li key={u.id} onClick={() => onSelect(u)}>{u.name}</li>
))}
</ul>
);
});React.memo() is generic and automatically infers prop types from the wrapped component. TypeScript ensures that prop comparison (shallow comparison) is type-safe.
dispatch from useReducer?type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "set"; payload: number };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment": return state + 1;
case "decrement": return state - 1;
case "set": return action.payload;
}
}
const [count, dispatch] = useReducer(reducer, 0);useReducer infers the dispatch type from the Action type. The discriminated union (type field) guarantees dispatch only accepts valid actions. action.payload is only accessible in the 'set' case.
useFetch use a generic <T,>?const useFetch = <T,>(url: string) => {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((json: T) => setData(json))
.catch((err) => setError(err))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
};
const { data } = useFetch<User[]>("/api/users");In .tsx files, <T> would be interpreted as a JSX tag. <T,> is a workaround — the trailing comma distinguishes a generic from a tag. The caller writes useFetch<User[]>(url) and data has type User[] | null.
React.forwardRef?const ForwardedInput = React.forwardRef<
HTMLInputElement,
{ label: string }
>(({ label, ...props }, ref) => (
<div>
<label>{label}</label>
<input ref={ref} {...props} />
</div>
));React.forwardRef<RefType, PropsType> — the first generic is the DOM element type (ref), the second is props type. This lets a child component forward a ref to an inner element.
React.ReactNode and React.ReactElement in props?type PropsWithChildren<P = {}> = P & {
children?: React.ReactNode;
};
interface LayoutProps {
sidebar: React.ReactElement;
}
const Layout = ({ sidebar, children }: PropsWithChildren<LayoutProps>) => (
<div>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);Use ReactNode for children (accepts everything). Use ReactElement when you require a specific JSX component (e.g., sidebar must be an element, not text). This is an important distinction for typing layouts.
AsyncState<T> better than State<T> for managing async state?type Status = "idle" | "loading" | "success" | "error";
interface State<T> {
status: Status;
data: T | null;
error: string | null;
}
type AsyncState<T> =
| { status: "idle"; data: null; error: null }
| { status: "loading"; data: null; error: null }
| { status: "success"; data: T; error: null }
| { status: "error"; data: null; error: string };A discriminated union on status eliminates impossible states. TypeScript knows: success → data: T (not null), error → error: string (not null). State<T> allows error + data simultaneously — a bug waiting to happen.
useCallback and why is as string needed for formData.get?const handleSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get("email") as string;
await submitForm({ email });
},
[]
);FormData.get() returns string | File | null because forms can have files. When you know the field is text, as string narrows the type. Alternatively: use type guard typeof val === 'string'.
| undefined unlike the earlier example?type Theme = "light" | "dark";
const ThemeContext = createContext<Theme>("light");
const useTheme = () => useContext(ThemeContext);
const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [theme, setTheme] = useState<Theme>("light");
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
};When createContext gets a meaningful default value ("light"), the component gets that value even without a provider. No undefined or guard needed. The undefined pattern is needed when there's NO sensible default value.
type ButtonVariant = "primary" | "secondary" | "danger";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
isLoading?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = "primary", isLoading, children, disabled, ...props }, ref) => (
<button
ref={ref}
disabled={disabled || isLoading}
className={variant}
{...props}
>
{isLoading ? "Loading..." : children}
</button>
)
);Extending HTMLAttributes is standard in design systems. The component accepts all native <button> props (onClick, disabled, type, etc.) + custom ones (variant, isLoading). forwardRef enables ref forwarding.
const useLocalStorage = <T,>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] => {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value: T | ((prev: T) => T)) => {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
};
return [storedValue, setValue];
};The hook mimics the useState API — returns a tuple [value, setter]. The union type T | ((prev: T) => T) allows the setter with both a value and a callback. Same pattern as built-in setState.
keyof T guarantee in the Column<T> type?type Column<T> = {
key: keyof T;
header: string;
render?: (value: T[keyof T], row: T) => React.ReactNode;
};
interface TableProps<T> {
data: T[];
columns: Column<T>[];
}
function Table<T extends Record<string, unknown>>({ data, columns }: TableProps<T>) {
return (
<table>
<thead>
<tr>{columns.map(col => <th key={String(col.key)}>{col.header}</th>)}</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i}>
{columns.map(col => (
<td key={String(col.key)}>
{col.render ? col.render(row[col.key], row) : String(row[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}key in a column is a real key of data type T — you can't specify a nonexistent field. TypeScript autocompletes available keyskeyof T in a generic Table component guarantees type-safety: columns can only reference existing data fields. Adding a new field to data automatically makes it available in columns.
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<E>, "as" | "children">;
function Box<E extends React.ElementType = "div">({
as,
children,
...props
}: PolymorphicProps<E>) {
const Component = as || "div";
return <Component {...props}>{children}</Component>;
}
<Box as="a" href="/about">Link</Box>
<Box as="button" onClick={() => {}}>Click</Box>
<Box>Default div</Box>as prop to change the HTML tag — TypeScript automatically types props based on the chosen elementA polymorphic component (popular in Chakra UI, Radix) lets you change the rendered element via as prop. TypeScript via generics knows: if as='a', props include href. If as='button' — props include onClick. Full type-safety.
{ data: T | null; loading: boolean; error: Error | null }?type QueryResult<T> =
| { status: "idle"; data: undefined; error: undefined }
| { status: "loading"; data: undefined; error: undefined }
| { status: "success"; data: T; error: undefined }
| { status: "error"; data: undefined; error: Error };
function useQuery<T>(url: string): QueryResult<T> {
// implementation
}
const result = useQuery<User[]>("/api/users");
if (result.status === "success") {
result.data.map(u => u.name); // TypeScript wie!
}With a simple { data, loading, error } nothing prevents impossible states like loading: true, data: someData, error: someError. A discriminated union excludes them at the type level. After status === 'success', TypeScript guarantees data is T (not undefined).
interface DataListProps<T> {
items: T[];
filterFn?: (item: T) => boolean;
sortFn?: (a: T, b: T) => number;
children: (item: T, index: number) => React.ReactNode;
}
function DataList<T>({
items,
filterFn,
sortFn,
children,
}: DataListProps<T>) {
let processed = [...items];
if (filterFn) processed = processed.filter(filterFn);
if (sortFn) processed = processed.sort(sortFn);
return <>{processed.map((item, i) => children(item, i))}</>;
}
<DataList
items={users}
filterFn={u => u.active}
sortFn={(a, b) => a.name.localeCompare(b.name)}
>
{(user) => <UserCard key={user.id} user={user} />}
</DataList>DataList is a generic component with render prop pattern. TypeScript infers T from items and propagates it to filterFn, sortFn, and children. Separates logic (filtering, sorting) from presentation (render prop). Pure FP in React.
handleChange('name', 42) and handleChange('unknown', 'value') cause errors?type FormFields = {
name: string;
email: string;
age: number;
};
const useForm = <T extends Record<string, unknown>>(initial: T) => {
const [values, setValues] = useState<T>(initial);
const handleChange = <K extends keyof T>(
field: K,
value: T[K]
) => {
setValues(prev => ({ ...prev, [field]: value }));
};
return { values, handleChange };
};
const { values, handleChange } = useForm<FormFields>({
name: "",
email: "",
age: 0,
});
handleChange("name", "Alice"); // OK
handleChange("age", 25); // OK
handleChange("name", 42); // Error!
handleChange("unknown", "value"); // Error!The generic K extends keyof T in handleChange creates a dependency: field: K restricts to existing keys, value: T[K] requires the type matching that key. TypeScript checks BOTH simultaneously — you can't provide a wrong field or wrong value type.
interface DialogProps {
open: boolean;
onClose: () => void;
children: React.ReactNode;
}
interface DialogComposition {
Header: React.FC<{ children: React.ReactNode }>;
Body: React.FC<{ children: React.ReactNode }>;
Footer: React.FC<{ children: React.ReactNode }>;
}
const Dialog: React.FC<DialogProps> & DialogComposition = ({
open,
onClose,
children,
}) => {
if (!open) return null;
return <div className="dialog">{children}</div>;
};
Dialog.Header = ({ children }) => <header>{children}</header>;
Dialog.Body = ({ children }) => <main>{children}</main>;
Dialog.Footer = ({ children }) => <footer>{children}</footer>;Compound Component pattern (popular in Radix, Headless UI) uses dot notation for composition. Type FC<DialogProps> & DialogComposition is an intersection — TypeScript knows Dialog is both a component and has sub-components. Provides autocomplete on Dialog.