ESC

Type to search the knowledge base.

Basic Types and Annotations

Primitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.

beginner4 min read
  • typescript
  • basic-types

TypeScript’s value for frontend work starts with basic types and annotations: saying what a value is so the compiler can refuse nonsense before runtime. You do not need advanced mapped types on day one. You need honest string / number / boolean, typed objects, and function signatures that match how the UI actually works.

Docs: Everyday Types, Narrowing.

Primitives you will use daily

const id: string = 'user_1';
const count: number = 0;
const ready: boolean = false;
const empty: null = null;
const missing: undefined = undefined;

In practice you rarely annotate locals when the right-hand side is obvious — inference fills them in:

const id = 'user_1'; // string
const count = 0;     // number

Annotate when:

  1. The value is uninitialized and filled later.
  2. You want a wider type than the literal (let status: string = 'idle' vs literal 'idle').
  3. The value crosses a module boundary (exports, public function params).
let status: 'idle' | 'loading' | 'done' = 'idle';
// later
status = 'loading';

Arrays and tuples (peek)

const tags: string[] = ['ts', 'react'];
const pair: [string, number] = ['retries', 3];

string[] is “list of strings.” A tuple fixes length and position types — useful for coordinates, CSV rows, and React useState pairs in library code. Prefer arrays for open-ended lists.

Object types

type User = {
  id: string;
  name: string;
  email?: string; // optional
};

function greet(user: User): string {
  return `Hi ${user.name}`;
}

Optional (?) means the property may be missing — not the same as email: string | null (present but nullable). Be explicit about which you mean for API payloads.

Index signatures when keys are dynamic:

type Scores = { [userId: string]: number };

Prefer Record<string, number> for the same idea in modern codebases.

Function annotations

function add(a: number, b: number): number {
  return a + b;
}

const onSave = (value: string): void => {
  localStorage.setItem('draft', value);
};

Return type annotation is optional when inference is obvious; keep it on exported functions so refactors fail loudly at the signature.

Callbacks in React:

type Props = {
  onSelect: (id: string) => void;
  items: string[];
};

any vs leaving it untyped

Unannotated parameters under noImplicitAny are errors. That is intentional. Prefer:

function parse(raw: unknown) {
  // narrow before use
}

over raw: any. See unknown vs any.

Type annotations vs assertions

const el = document.querySelector('#app'); // Element | null
const app = document.querySelector('#app') as HTMLDivElement; // assertion — trust me

Annotations declare intent; assertions override the checker. Prefer narrowing:

const el = document.querySelector('#app');
if (!(el instanceof HTMLDivElement)) throw new Error('missing #app');
// el is HTMLDivElement

Union types early

Frontend state is rarely one shape:

type LoadState =
  | { status: 'idle' }
  | { status: 'error'; message: string }
  | { status: 'ok'; data: User };

Basic annotations + unions beat optional soup (data?: User; error?: string). More: discriminated unions for UI state, union and intersection types.

Common footguns

Footgun Fix
Annotating every local Let inference work; annotate APIs
Object / {} for “any object” Prefer concrete types or Record<string, unknown>
number for IDs from JSON IDs are often string; don’t coerce casually
Forgetting null from DOM APIs Handle null or throw
Array<any> unknown[] + map with guards
// Weak
function log(x: Object) {
  console.log(x);
}

// Stronger boundary
function logUnknown(x: unknown) {
  console.log(x);
}

Mental model for interviews

TypeScript annotations are contracts. Primitives and object types describe values; function types describe call sites. Inference is the default; write types where ambiguity or public API risk is real. Strict mode without annotations discipline is still “JavaScript with a false sense of safety.”

Interview out-loud answer

“I annotate public APIs and state that has more than one shape. Locals usually infer. I avoid any, use unknown at boundaries, and prefer unions over optional fields for mutually exclusive UI states. Assertions are last resort after a runtime check.”

Further reading

Related guides