Coding Standards

TypeScript Coding Standards

Foundational

The full reference for writing TypeScript at Finperiti: strictness, type modelling, generics, runtime validation at boundaries, async, modules, and the discipline that keeps the types meaningful. TypeScript only pays off if the types are accurate and honest. The worst mistakes are any, casts, and skipping runtime validation. Builds on JavaScript Coding Standards (all of which applies) and pairs with React Coding Standards.

We write TypeScript in strict mode and treat type errors as build failures. The whole value is type accuracy. A codebase full of any, as, and @ts-ignore pays the cost of types with none of the safety. Model data precisely, validate it at the edges, and let the compiler act as a reviewer that never tires.

Prettier and ESLint (with the TypeScript and import plugins) enforce formatting and many rules automatically. This page explains the reasoning behind them and the conventions tools cannot enforce.

Strictness — the foundation

Modelling types

Discriminated union forces handling type Result = { kind: 'ok'; value: Customer } | { kind: 'error'; message: string };
function render(r: Result) {
switch (r.kind) { // compiler errors if a case is missed
case 'ok': return show(r.value);
case 'error': return showError(r.message);
}
}

The union makes every state explicit, and the compiler forces you to handle each one. Illegal states cannot be represented.

interface vs type, enums

Boundaries & runtime safety

any plus cast of untrusted data async function load(id: string): Promise<any> {
const res = await fetch(`/api/customers/${id}`);
return res.json() as Customer; // unvalidated lie to the compiler
}

An any return type throws away type safety in all the code that follows, and casting the parsed JSON to Customer means a wrong shape crashes at runtime with no warning. The types look nice but protect nothing.

Schema-validated, inferred type const Customer = z.object({ id: z.string(), status: z.enum(['active','closed']) });
type Customer = z.infer<typeof Customer>;
async function load(id: string): Promise<Customer> {
const res = await fetch(`/api/customers/${id}`);
return Customer.parse(await res.json()); // validated at the boundary
}

One schema drives both the runtime check and the static type, so bad data is caught at the edge and the type truly matches reality.

Functions & generics

Classes & objects

Modules & structure

Async & errors

Naming & JavaScript baseline

Self-review checklist

Why it matters: TypeScript's whole payoff is catching bugs at compile time, and it disappears the moment you escape the type system with any or casts, or skip runtime validation at boundaries. Strict, honest, well-modelled types plus boundary validation turn the compiler into a tireless reviewer. That is the safety net a fast-moving, junior team needs most.