The "Small" JS/TS Features That Actually Matter
January 11, 2026
These features shape most day-to-day JavaScript and TypeScript code. Some affect runtime and can break your app; others are for TypeScript-only, so your editor (and the type checker) can understand what your code is supposed to do and you can develop onto it more effectively with type-safety.
I’ve gathered some features that I believe are essential for writing robust, professional frontend code today.
1. Optional chaining ?.
You've seen:
user?.address?.first;
Rule: The moment the left side is null or undefined, evaluation stops and the result becomes undefined.
It also works in places people forget:
user.getAddress?.(); // Optional call items?.[0]; // Optional element access items?.[0]?.title;
It's helpful when dealing with values that might be empty at the moment. But it can also hide bugs silently, so if a value supposed to be required, avoid using ?. and confirm the value exists and handle error if necessary.
2. ?? exists because || is too aggressive
const name1 = input.name || "Anonymous"; const name2 = input.name ?? "Anonymous";
||falls back for any falsy value ("",0,false,null,undefined)??falls back only for nullish (null,undefined)
Practical rule:
- Use
??when you mean: “value is missing” - Use
||when you mean: “value must be truthy”
Example, try to get the value, if you can't pass the default value.
const first = user?.address?.first ?? "—";
3. Assignment operators ||=, ??=, &&=
Well, these are not widely used afaik but they're cool to have.
-
??=set if current value is nullishconfig.timeout ??= 5000; // If timeout is null or undefined set it to 5000, otherwise don'T. -
||=set if current value is falsyconfig.timeout ||= 5000; // If timeout is falsy set it to 5000, otherwise don't. -
&&=update only if current value is truthylet username = "ramazan_ozturk"; username &&= username.toUpperCase(); // RAMAZAN_OZTURK // If username is truthy uppercase it, otherwise don't.
4. The "Trust Me Bro" Operator: !. (Non-null Assertion)
Sometimes you know more than the TypeScript compiler. You've fetched an element, you know it's there, but TS is screaming that it might be null.
ts// TS thinks this is 'HTMLElement | null' const button = document.querySelector("#submit-btn")!; // The '!' tells TS: "I guarantee this isn't null. Proceed." button!.click();
Use this sparingly. If you're wrong, your app will crash at runtime. It's the "hold my beer" of operators.
4.5. Type Assertion: as Type (The "I Know What I'm Doing" Operator)
Sometimes you have more information about a value than TypeScript does. You might be receiving data from an external API or a legacy library that returns any or a broad Union, but you are 100% sure of its specific type.
// TS thinks 'element' is just an 'Element' const element = document.getElementById("user-avatar") as HTMLImageElement; // Now you can access image-specific properties without errors element.src = "profile.png"; // ⚠️ BE CAREFUL: This doesn't actually change the data at runtime. // If 'element' is actually a <div>, this code will pass TS but might crash in the browser.
Only use as when it's impossible for TypeScript to infer the type (like DOM elements or some API responses). If you find yourself using it everywhere, your types are probably poorly designed. Git gud!
5. The JS/TS Glossary Break
Before we go deeper, let’s define the "vibe" of JavaScript values and the syntax used to move data around. You're likely to hear these terms in discussions:
-
1. Truthy vs. Falsy: JavaScript is "flexible." Values like
0,"",NaN,null,undefined, andfalseare considered Falsy. Everything else (including empty arrays[]and objects{}) is Truthy. -
2. Nullish: A stricter subset of Falsy. It refers only to
nullandundefined. Use this when you care if a value is missing, not just zero or false. -
3. Short-circuiting: Using
&&or||to stop execution early.- Example,
user && user.save()only runs the save function if the user exists.
- Example,
-
4. Destructuring: A fancy way to "unpack" values from arrays or properties from objects into distinct variables.
- Example:
const { name, age } = user;
- Example:
-
5. Spread Operator (...): When used in an object or array literal, it "spreads" the contents into a new one. It creates a Shallow Copy.
- Example:
const newUser = { ...user, status: "active" };
- Example:
-
6. Rest Parameter (...): Same syntax as spread, but used in function arguments to gather "the rest" of the values into a single array.
- Example:
function logItems(first, ...others) { }
- Example:
-
7. Type Widening: TypeScript’s tendency to turn a specific value like
"red"into a genericstring. We stop this usingas const. -
8 . Type Narrowing: The process of "proving" to TypeScript that a variable is a specific type (e.g., using an
if (typeof x === "string")block).
6. as const (The Ultimate Immutability)
When you want a variable to be treated as a literal value rather than a generic type (like string or number), and you want it deeply read-only.
tsconst ACTIONS = ["create", "update", "delete"] as const; // ACTIONS is now: readonly ["create", "update", "delete"] // You can't .push() or .pop() anymore. It’s immutable. // "create" | "update" | "delete" type ActionType = (typeof ACTIONS)[number]; const createAction: ActionType = "create"; // OK const deleteAllAction: ActionType = "delete-all"; // TS ERROR!
tsconst ICON_MAP = { save: "floppy-disk-icon", edit: "pencil-icon", cancel: "x-mark-icon", } as const; // Get the Keys: "save" | "edit" | "cancel" type IconName = keyof typeof ICON_MAP; // Get the Values: "floppy-disk-icon" | "pencil-icon" | "x-mark-icon" type IconValue = (typeof ICON_MAP)[keyof typeof ICON_MAP];
Every value becomes its own unique type. Use this for navigation links, status codes or any configuration where the data itself should define the rules of your app.
7. satisfies my needs. Validate Without Data Loss
This is a subtle but massive upgrade over standard type annotations (: Type).
The Pain: When you use a colon to define a type (e.g., const theme: Palette = ...), TypeScript "forgets" the specific values you wrote and only remembers the general shape. If your type allows a property to be a string | object, TS will make you check which one it is every single time, even if you clearly wrote a string.
The Solution: satisfies checks if the object matches a type without changing the inferred type of the object.
type Palette = "red" | "green" | { custom: string }; const theme = { primary: "red", secondary: { custom: "#00ff00" }, } satisfies Record<string, Palette>; // Since we used 'satisfies', TS knows 'primary' is definitely a string. theme.primary.toUpperCase();
Use this for Theme objects, API configs, or complex nested objects where you want strict validation but still want your IDE to know exactly what’s inside.
8. Discriminated Unions (The Pattern for Pros)
This is the #1 pattern for handling complex states (like API responses). It relies on a "tag" (a shared property) to help TypeScript narrow down which object you are working with.
type State = | { status: "loading" } | { status: "success"; data: string[] } | { status: "error"; message: string }; function render(state: State) { switch (state.status) { case "loading": return "Loading..."; case "success": // TS knows 'data' exists here! return state.data.map((item) => `<li>${item}</li>`); case "error": // TS knows 'message' exists here! return `❌ Error: ${state.message}`; } }
Why it matters: It makes it impossible to access state.data while the status is still "loading". Total type safety.
9. Template Literal Types (Type-Level String Building)
You can literally "program" your types to follow a specific string pattern. This is cool because it gives you autocomplete for strings you haven't even written yet.
tstype Size = "sm" | "md" | "lg"; type Color = "primary" | "secondary"; // Generates: "sm-primary" | "sm-secondary" | "md-primary" ... etc. type ButtonClass = `btn-${Size}-${Color}`; // This pattern looked familiar? :) const myBtn: ButtonClass = "btn-md-primary"; // ✅ const wrongBtn: ButtonClass = "btn-xl-blue"; // ❌ TS Error
10. User-Defined Type Guards (is)
Sometimes, an if check inside a function isn't enough—you need a way to tell TypeScript: "If this function returns true, I guarantee the variable is this specific type."
The Pain: You have a reusable function to check if something is a Fish, but TypeScript "forgets" that check as soon as the function returns.
The Solution: Use the is keyword in the return type.
tstype Fish = { swim: () => void }; type Bird = { fly: () => void }; // The "is Fish" is the magic part. // It means: "If this function returns TRUE, then 'pet' is a Fish." function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; } function move(pet: Fish | Bird) { if (isFish(pet)) { // If the function above returned TRUE: pet.swim(); // ✅ TS knows it's a Fish } else { // If it returned FALSE: pet.fly(); // ✅ TS knows it must be a Bird } }
Summary
TypeScript is the operators we met along the way.
| Feature | Syntax | The Quick Win |
|---|---|---|
| Optional Chaining | ?. | No more if (a && a.b) mess. Stops null crashes instantly. |
| Nullish Coalescing | ?? | The "Safe" Default. Keeps 0 and "" as valid values. |
| Assignment Ops | ??=, ||=, &&= | Inline defaults. Update variables only when you actually need to. |
| Assertions | !., as | The Escape Hatch. Use when you're smarter than the compiler. |
| as const | as const | Lock your data down. Turn whole object into type as is |
| satisfies | satisfies | Validate the shape, but keep the specific details. |
| Tagged Unions | type: "tag" | The only way to manage complex state without losing your mind. |
| Template Literals | `${T}` | Automate your string types with perfect autocomplete. |
| Type Guards | is Type | Help TypeScript remember your custom logic. |