Skip to content

Typescript Essentials

51 companion flashcards · AI-assisted study content · Open the deck →

This deck introduces the core concepts of TypeScript, starting from the very basics and building toward a working understanding of how typed JavaScript fits into real projects. You'll cover foundational ideas like what TypeScript is, how it relates to JavaScript, and how to set it up with the compiler and configuration file. From there, the cards walk you through the type system itself, including primitive types, type inference, special types like any, unknown, and never, and more structured shapes such as arrays, tuples, and enums.

It's a great fit if you're a JavaScript developer taking your first steps into TypeScript, a beginner programmer learning a typed language for the first time, or someone preparing for an interview or project that involves typed code. The questions are short and concept-focused, so they work well whether you're brand new to the language or just looking to solidify gaps in your understanding.

To get the most out of these cards, try spacing your review sessions across several days rather than cramming, since type-system vocabulary tends to stick best with repeated exposure over time. It also helps to keep a small code editor open while you study, so you can quickly test out ideas like type assertions or tuple declarations as you encounter them. Connecting each flashcard to a real line of code will make the concepts feel concrete and much easier to recall later.

Introduction to TypeScript & Setup

TypeScript is a statically typed superset of JavaScript developed by Microsoft. Because it builds on top of JavaScript, every valid JavaScript file is also a valid TypeScript file, but TypeScript adds optional static typing, interfaces, classes, and modules to improve scalability, maintainability, and developer productivity. TypeScript code itself cannot be executed directly; instead, the TypeScript compiler transpiles it into plain JavaScript that runs in any JavaScript environment such as browsers, Node.js, or Deno.

To start using TypeScript, you install it through npm, either globally with npm install -g typescript or as a project-local development dependency with npm install typescript --save-dev. You can verify a successful installation by running tsc --version. The compiler is invoked with the tsc command: tsc file.ts compiles a single file, while running tsc alone compiles every file in the project according to the project's configuration.

Project-level configuration lives in a file called tsconfig.json. This JSON file specifies compiler options such as the target JavaScript version (for example, ES5, ES2015, or ESNext), the module system to use, and the strictness mode that governs type-checking behavior. By tailoring tsconfig.json, teams control how their TypeScript projects are compiled without having to pass flags on the command line for every build.

Basic Types & Type System Fundamentals

TypeScript's type system starts with a small set of primitive types: number, string, boolean, null, undefined, symbol, and bigint. These primitives serve as the building blocks for typing variables, function parameters, and return values. To annotate a variable's type explicitly, you place a colon and the type after the variable name, as in let name: string = "Alice";. Explicit annotations improve readability and prevent unintended type mismatches.

TypeScript also performs type inference: when a variable is initialized with a value, the compiler automatically infers its type from that initializer. Writing let count = 5; causes TypeScript to treat count as a number, eliminating the need for redundant annotations while still preserving type safety. Inference reduces boilerplate, but explicit annotations remain valuable wherever clarity or contract enforcement matters.

The type system includes a few special escape-hatch types. The any type disables type checking entirely, letting a value be treated as if it could be any type; it should be used sparingly because it bypasses the safety TypeScript exists to provide. The unknown type is a safer top type, requiring a type check or narrowing before any operation is performed on the value, which prevents accidental misuse. Finally, the never type represents values that never occur, such as the result of a function that always throws or the unreachable branch of an exhaustive switch statement.

Functions & Collection Types

TypeScript supports multiple ways to express collections of values. Arrays can be written using either bracket syntax, such as let numbers: number[] = [1, 2, 3];, or the generic Array<number> notation. Both forms accept a single uniform type, but heterogeneous data is better expressed with tuples, which are fixed-length arrays whose positions each have a specific type. An example is let pair: [string, number] = ["age", 30];, where the tuple enforces both length and the ordering of types.

Enums provide named constants that improve readability over magic numbers or scattered strings. By default, a numeric enum such as enum Color { Red, Green, Blue } assigns zero-based numeric values, but string-based enums are equally supported. Enums group related values into a single, easy-to-reference namespace and let integrated development environments offer autocomplete suggestions.

Functions are typed by annotating parameters and return values, as in function add(a: number, b: number): number { return a + b; }, and arrow functions follow the same conventions. Parameters can be made optional with ?, in which case they default to undefined when omitted, while default parameters supply a fallback value that activates when no argument is passed. Rest parameters use the spread syntax with an array type, like function sum(...numbers: number[]): number, collecting any remaining positional arguments into a single array for the body to work with.

Interfaces, Type Aliases & Object Shapes

Interfaces describe the shape of objects by declaring the names and types of their members. A simple interface Person { name: string; age: number; } tells the compiler that any value typed as Person must have those two properties with those exact types. Because interfaces exist only at compile time, they impose type-checking guarantees without any runtime overhead.

TypeScript also offers type aliases through the type keyword, which can describe unions, intersections, primitives, and other compound types. The choice between interface and type often comes down to intent: interfaces are designed for object shapes and uniquely support declaration merging, while type aliases are more flexible for unions, intersections, and computed utilities. Many developers reach for interfaces when modeling object-oriented contracts and for type aliases when assembling complex type-level logic.

Interfaces and types can be combined or extended in several ways. Index signatures allow dynamic key names for objects whose keys are not known ahead of time, such as interface StringDict { [key: string]: string; }. The readonly modifier marks a property as assignable only at initialization, which is ideal for immutable data. Interfaces inherit members from other interfaces using extends, and unions allow a value to be one of several types with string | number, while intersections require a value to satisfy every type at once with User & { permissions: string[]; }. When the compiler needs a hint about a value's type, type assertions using the as keyword tell TypeScript to treat a value as a specific type, although this should be done cautiously because it bypasses the usual checks.

Classes & Object-Oriented Programming

Classes in TypeScript follow standard JavaScript class syntax with the addition of type annotations. A class such as class Animal { name: string; constructor(name: string) { this.name = name; } } declares a property and a constructor in familiar ways. The big difference comes with access modifiers: public members are accessible everywhere and serve as the default, private members are accessible only within the declaring class, and protected members are accessible within the class and its subclasses. These modifiers enforce encapsulation at compile time without affecting the runtime behavior of the emitted JavaScript.

Constructors can be written compactly by adding access modifiers directly to their parameters, automatically promoting those parameters into instance properties such as constructor(private name: string) { }. Inheritance works through the extends keyword, and subclasses can call into their parent with super—both as super(...) from the constructor and super.method() from instance methods—to invoke overridden behavior.

Abstract classes take the inheritance model further by serving as bases that cannot be instantiated themselves; they may declare abstract methods that subclasses are required to implement, such as abstract area(): number; inside abstract class Shape. Getters and setters, declared with the get and set keywords, look like properties from the outside but allow custom logic whenever a value is read or written, giving you a clean way to validate or transform data as it crosses the boundary of an instance.

Generics for Reusable Code

Generics let you write functions, interfaces, and classes that operate on multiple types while preserving full type safety. A generic identity function written as function identity<T>(arg: T): T { return arg; } takes a value of any type and returns a value of exactly the same type, with TypeScript filling in T from context. This pattern is the foundation of reusable container types like Array<T>.

When a generic must guarantee certain capabilities, you constrain it with extends. For example, function longest<T extends { length: number }>(a: T, b: T): T ensures that any type passed in has a numeric length property, letting the function compare values safely. Generic interfaces apply the same idea at a structural level: interface Box<T> { contents: T; } describes a container whose contents vary, while generic classes like class Stack<T> with push(item: T) and pop(): T | undefined define data structures that track element types through every operation.

Modules & Advanced Type-Level Features

TypeScript supports ES module syntax: import { ModuleName } from './module'; pulls in named exports, import * as Module from './module'; imports a whole namespace, and exports are declared with export function func() {} for named exports or export default class {} for the default export. Re-exporting from another module is as simple as export { something } from './other';. Namespaces, declared with the namespace keyword, group related code under a common name to avoid polluting the global scope. They predate ES modules and remain useful for organization, though modern code typically prefers explicit imports.

At the type level, TypeScript offers features that go well beyond simple annotations. Type guards are functions whose return type is a type predicate, like value is string, allowing the compiler to narrow the type of a value within a conditional branch. Discriminated unions extend this by giving each variant of a union a shared literal property, called a discriminant, which switch statements can exhaustively check. Conditional types resemble ternaries at the type level, mapping one type to another based on a relation—T extends null | undefined ? never : T is the pattern behind NonNullable<T>.

Mapped types iterate over the keys of another type using keyof and in to transform properties, such as { readonly [P in keyof T]: T[P] }, which is how Readonly<T> is constructed. Built on these primitives, utility types offer shortcuts for common type transformations: Partial<T> makes every property optional, Required<T> makes them all required, Pick<T, K> selects a subset of keys, and Omit<T, K> excludes them. Template literal types take this further by constructing string literal unions from interpolated expressions—for instance, `on${'click' | 'hover'}` evaluates to "onclick" | "onhover", enabling powerful compile-time string manipulation.

Strict Mode & Interoperability with JavaScript

Strict mode, enabled by setting "strict": true in tsconfig.json, turns on a collection of more rigorous type-checking options, including strictNullChecks and several others. Activated in a new project, strict mode catches a far wider range of potential bugs at compile time and is widely recommended as the default for new codebases. A closely related option is "noImplicitAny": true, which causes the compiler to raise an error whenever it would otherwise silently infer the any type, forcing developers to make their intentions explicit and preventing accidental escape hatches in the type system.

Because much of the JavaScript ecosystem was not written with TypeScript in mind, TypeScript uses declaration files with the .d.ts extension to describe the types of values exported by plain JavaScript libraries. A declaration file might contain something as simple as declare function jQuery(): void;. For popular libraries, these declarations are typically published under the @types scope on npm—for example, npm install @types/node adds Node.js type definitions to a project, allowing TypeScript to understand that environment's APIs.

The final piece of project configuration is the JavaScript target version. Setting "target": "ES2020" (or another value such as ES5, ES2015, or ESNext) tells the TypeScript compiler what flavor of JavaScript to emit, balancing compatibility with older runtimes against the ability to use modern language features. Together, the strictness options and the target setting give teams fine-grained control over how their TypeScript code is validated and transpiled into JavaScript that can run in any environment of their choosing.

Frequently asked questions

What is TypeScript?

TypeScript is a statically typed superset of JavaScript developed by Microsoft that compiles to plain JavaScript code. It adds optional static typing, interfaces, classes, and modules to enhance code scalability, maintainability, and developer productivity.

What are primitive types in TypeScript?

Primitive types include number, string, boolean, null, undefined, symbol, and bigint. They are the basic building blocks for typing variables.

What is the 'never' type?

The never type represents values that never occur, such as exhaustive switch statements or impossible code paths. It is useful for function return types that always throw errors.

How do you define an interface?

Interfaces describe object shapes: interface Person { name: string; age: number; }. They are used for type checking objects without runtime overhead.

How do you type rest parameters?

Rest parameters use ... with array type: function sum(...numbers: number[]): number. They collect remaining arguments into an array.

What are intersection types?

Intersection types combine types: type Admin = User & { permissions: string[]; };. The resulting type requires all properties from intersected types.

What are abstract classes?

Abstract classes can't be instantiated and may have abstract methods: abstract class Shape { abstract area(): number; }. Used as base for concrete subclasses.

What are generic classes?

class Stack<T> { push(item: T): void { } pop(): T | undefined { } }. Classes can be generic for type-safe data structures.

What are discriminated unions?

Discriminated unions use a common literal discriminant: type Shape = { kind: 'circle'; r: number } | { kind: 'square'; side: number };. Switch on discriminant for exhaustive checks.

What does 'in' do in mapped types?

The in keyword iterates keys in mapped types: { [P in keyof T]: T[P] }. Essential for transforming all or subset of properties.

Drill this topic

51 flashcards on Typescript Essentials — free, no signup needed to start.

Study Typescript Essentials flashcards

LearnWiki pages are generated with AI assistance from LearnCoachAssist's reviewed study catalog and may contain errors — verify anything critical against your course materials.