This article is published in English.
TypeScript 6's Quiet Wins and the JavaScript Habits of Senior Devs
Discover TypeScript 6's overlooked features like explicit resource management and const type parameters, plus the JavaScript idioms senior engineers rely on daily.
Modern JavaScript and TypeScript keep evolving in ways that go far beyond headline features and changelog bullet points. Two threads of practice matter right now: the quieter, less-publicized additions in TypeScript 6, and the everyday JavaScript idioms that separate senior-level pull requests from merely functional code. Both are really about the same discipline — knowing the tools well enough to reach for the right one instead of the familiar one. This article walks through both, starting with what TypeScript 6 shipped under the radar, then moving into the JavaScript patterns that experienced engineers reach for by default.
Quiet TypeScript 6 Upgrades Worth Adopting
Most coverage of TypeScript 6 stops at the fact that strict mode and ES2025 are now the defaults. That is a real change, but it overshadows a set of smaller additions that quietly remove long-standing workarounds. If you only read the tsconfig-default headlines, you missed the more useful part of the release.
Cleanup You Don't Have to Remember to Write
A common source of bugs is forgetting to close a database connection or remove a listener. TypeScript 6 addresses this with explicit resource management, letting you mark a value so it gets cleaned up automatically once it leaves scope.
function readUserSession() {
using session = openSession(); // auto-disposed at scope end
const user = session.getUser();
return user.name;
} // session.dispose() called automatically here
This removes the need for try/finally blocks whose only job was guaranteeing cleanup. The syntax addition is small, but the reliability payoff is significant for anything involving files, sockets, or open connections.
Literal Types Without Sprinkling as const Everywhere
Previously, keeping literal types intact through a generic function meant scattering as const throughout your code. TypeScript 6 introduces const type parameters, which preserve literal inference automatically.
function createConfig<const T extends Record<string, unknown>>(config: T) {
return config;
}
const config = createConfig({
env: "production",
features: ["auth", "billing"],
});
// config.env is "production", not string
// config.features is readonly ["auth", "billing"], not string[]
If you build typed API clients or write Redux action creators, this cuts out a large share of the as const boilerplate you'd otherwise need.
Proper Narrowing Inside switch (true)
Another welcome fix: discriminated unions can now be narrowed correctly inside a switch (true) statement, something that used to force you back into nested if/else chains to get the compiler to understand your types.
function area(shape: Shape): number {
switch (true) {
case shape.kind === "circle":
return Math.PI * shape.radius ** 2; // narrowed to Circle
case shape.kind === "rectangle":
return shape.width * shape.height; // narrowed to Rectangle
}
}
With this change, you no longer have to abandon switch just to keep type narrowing intact.
Temporal Gets First-Class Types
TypeScript 6 also ships built-in types for the Temporal API, so you're no longer fighting Date and its timezone quirks with manual type assertions.
const meetingStart = Temporal.Instant.from("2026-04-06T10:00:00Z");
const localTime = meetingStart.toZonedDateTimeISO("America/Toronto");
Subpath Imports That Actually Resolve
A smaller but genuinely annoying pain point has been fixed too: internal subpath imports now resolve properly, without needing a long chain of relative path segments.
import { User } from "#/models/user.js";
// instead of ../../../models/user.js 🙃
TypeScript's product manager, Daniel Rosenwasser, has described TS6 as a deliberate bridge toward the upcoming Go-based TypeScript 7, which suggests these aren't experimental side features — they're meant to stick around. If your team works across a full-stack JavaScript, React, or Next.js codebase, adopting these habits now should mean fewer surprises when TypeScript 7 arrives. Overall, TypeScript 6 isn't only about new compiler defaults; it quietly delivers cleaner resource handling, sharper type inference, and fewer manual workarounds. Trying using and const type parameters in your next pull request may make you wonder how you managed without them.
JavaScript Habits That Separate Senior Code From the Rest
Knowing TypeScript's newer capabilities is only half the picture. The other half is how JavaScript itself gets written day to day. When a senior engineer's pull request reads more cleanly than everyone else's, it's rarely about cleverness — it's about a consistent set of habits applied without hesitation.
Optional Chaining and Nullish Coalescing
Instead of chains like user && user.profile && user.profile.name, senior code relies on optional chaining paired with nullish coalescing.
const displayName = user?.profile?.name ?? 'Guest';
It's concise, defensive against missing values, and doesn't break when the code around it is refactored.
Destructuring With Default Values
Destructuring props, API responses, and function arguments — with sensible defaults baked in — is second nature at this level.
function createUser({ name, role = 'member', isActive = true }) {
return { name, role, isActive };
}
Doing this documents the expected shape of the data directly in the function signature.
Async/Await Instead of Chained Promises
Chaining .then().then().catch() still works, but it doesn't scale well as logic grows. Compare that to an async/await version:
async function fetchOrders(userId) {
try {
const res = await fetch(`/api/orders/${userId}`);
if (!res.ok) throw new Error('Failed to fetch orders');
return await res.json();
} catch (err) {
console.error('Order fetch failed:', err);
throw err;
}
}
The result reads top to bottom, and it's easier to debug and test.
Array Methods Over Manual Loops
Reaching for map, filter, reduce, and find replaces most hand-written for loops.
const activeAdmins = users
.filter((u) => u.isActive && u.role === 'admin')
.map((u) => u.email);
This means fewer mutable variables and fewer off-by-one mistakes.
Utility Types Instead of Duplicated Interfaces
Rather than redefining near-identical interfaces, senior TypeScript code leans on utility types like Partial, Pick, and Omit.
type UserFormInput = Pick<User, 'name' | 'email'> & Partial<Pick<User, 'phone'>>;
That keeps a single source of truth, so schema changes require updates in fewer places.
Hooks and Composition in React
Custom hooks that extract and encapsulate logic show up constantly in mature React codebases.
function useDebouncedValue(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
A hook like this lets the underlying logic be reused and tested independently of any specific component.
Structured State Management
For state management, many senior teams favor Redux Toolkit slices over hand-written reducers built from switch statements, since RTK reduces boilerplate and gives you immutability for free through Immer.
As Douglas Crockford famously observed, JavaScript is made up of "the good parts and the bad parts" — the same flexibility that makes the language powerful also invites confusion. Knowing which parts to lean on is what separates confident engineers from ones who are still finding their footing.
A short set of principles ties all of this together: favor readability over cleverness, let TypeScript's type system carry the documentation burden instead of comments, and build functionality out of small composed functions rather than large monolithic ones.