TypeScript Basics

Understand TypeScript fundamentals, including static typing, type inference, arrays, objects, functions, interfaces, type aliases, unions, classes, generics, enums, modules, type narrowing, and strict configuration.

Lesson content

What is TypeScript? TypeScript is a programming language that extends JavaScript by adding static types and development-time checking. TypeScript code is compiled into JavaScript before it runs in a browser or JavaScript runtime. Simple Formula TypeScript = JavaScript + Static Types + Better Tooling Compilation Flow TypeScript Code ↓ Type Checking ↓ Compilation ↓ JavaScript Code ↓ Browser or Runtime Why Use TypeScript? Detect errors before running the application Provide editor suggestions and autocomplete Improve code readability Make refactoring safer Define clear data structures Improve maintainability in large applications Support Angular development 1. Install TypeScript npm install --save-dev typescript Check Version npx tsc --version Compile a File npx tsc app.ts Input app.ts Output app.js 2. Type Annotations A type annotation explicitly defines the type of a variable. const userName: string = 'Ajay'; let age: number = 30; const isActive: boolean = true; Formula variableName: Type 3. Type Inference TypeScript can infer a variable's type from its initial value. const userName = 'Ajay'; let age = 30; const isActive = true; TypeScript infers the following types: userName → string age → number isActive → boolean Best Practice Allow type inference when the type is obvious. Add explicit types when they improve clarity or define a public contract. 4. Primitive Types Type Example string 'Ajay' number 100 boolean true bigint 100n symbol Symbol('id') null null undefined undefined Examples const firstName: string = 'Ajay'; const total: number = 500; const loggedIn: boolean = false; 5. Arrays Square-Bracket Syntax const numbers: number[] = [ 10, 20, 30 ]; Generic Syntax const names: Array<string> = [ 'Ajay', 'Rahul' ]; Object Array const users: User[] = [ { id: 1, name: 'Ajay' } ]; 6. Readonly Arrays A readonly array prevents normal mutation through methods such as push() and pop() . const roles: readonly string[] = [ 'admin', 'user' ]; Alternative const roles: ReadonlyArray<string> = [ 'admin', 'user' ]; 7. Tuples A tuple is an array with a fixed order and known types for each position. const userEntry: [number, string] = [ 1, 'Ajay' ]; Meaning Position 0 → number Position 1 → string Named Tuple const coordinates: [ latitude: number, longitude: number ] = [ 28.61, 77.20 ]; 8. Objects const user: { id: number; name: string; active: boolean; } = { id: 1, name: 'Ajay', active: true }; For reusable object structures, prefer an interface or type alias. 9. Optional Properties The question-mark symbol makes an object property optional. interface User { id: number; name: string; email?: string; } Valid Objects const firstUser: User = { id: 1, name: 'Ajay' }; const secondUser: User = { id: 2, name: 'Rahul', email: 'rahul@example.com' }; 10. Readonly Properties interface User { readonly id: number; name: string; } const user: User = { id: 1, name: 'Ajay' }; user.name = 'Rahul'; The id property cannot be reassigned through the User type. 11. Functions Parameter Types function add( firstNumber: number, secondNumber: number ) { return ( firstNumber + secondNumber ); } Return Type function add( firstNumber: number, secondNumber: number ): number { return ( firstNumber + secondNumber ); } Usage const result = add(10, 20); 12. Optional Function Parameters function greet( name: string, title?: string ): string { return title ? `Hello ${title} ${name}` : `Hello ${name}`; } Important Rule Required parameters should normally appear before optional parameters. 13. Default Parameters function greet( name: string = 'Guest' ): string { return ( `Hello ${name}` ); } 14. Rest Parameters function addAll( ...numbers: number[] ): number { return numbers.reduce( ( total, value ) => total + value, 0 ); } Usage const total = addAll( 10, 20, 30 ); 15. Function Types let calculate: ( first: number, second: number ) => number; calculate = ( first, second ) => first + second; Type Alias for Function type Calculator = ( first: number, second: number ) => number; 16. void The void type is commonly used for functions that do not return a useful value. function logMessage( message: string ): void { console.log( message ); } 17. never The never type represents a function that never completes normally. function throwError( message: string ): never { throw new Error( message ); } Infinite Loop function runForever(): never { while (true) { // Continue forever. } } 18. any The any type disables most type checking for a value. let value: any = 10; value = 'Hello'; value = { active: true }; Problem value.notExisting .method(); TypeScript may not report useful errors because any bypasses type safety. Recommendation Avoid any unless integration requirements make it temporarily necessary. 19. unknown The unknown type can hold any value, but it must be checked before use. let value: unknown = 'Hello'; Type Check if ( typeof value === 'string' ) { console.log( value.toUpperCase() ); } any vs unknown any unknown Disables type safety Requires type checking Any operation is allowed Operations are restricted until narrowed Less safe Safer for untrusted values 20. Union Types A union type allows more than one possible type. let userId: number | string; userId = 10; userId = 'USER-10'; Function Example function printId( id: number | string ): void { console.log(id); } 21. Literal Types Literal types restrict a value to specific allowed values. let status: 'active' | 'inactive' | 'pending'; status = 'active'; Reusable Literal Union type UserStatus = 'active' | 'inactive' | 'pending'; 22. Type Aliases A type alias gives a reusable name to a type. type UserId = number | string; Object Type Alias type User = { id: number; name: string; active: boolean; }; Usage const user: User = { id: 1, name: 'Ajay', active: true }; 23. Interfaces An interface defines the structure of an object. interface User { id: number; name: string; active: boolean; } Usage const user: User = { id: 1, name: 'Ajay', active: true }; 24. Interface vs Type Alias Interface Type Alias Common for object contracts Can represent objects, unions, primitives, and tuples Supports declaration merging Does not merge duplicate declarations Can extend another interface Can combine types using intersections Simple Rule Object Contract → interface Union, Tuple, Mapped or Combined Type → type 25. Extending Interfaces interface Person { name: string; } interface Employee extends Person { employeeId: number; } Usage const employee: Employee = { name: 'Ajay', employeeId: 101 }; 26. Intersection Types An intersection combines multiple types. type Person = { name: string; }; type Employee = { employeeId: number; }; type EmployeeProfile = Person & Employee; Usage const employee: EmployeeProfile = { name: 'Ajay', employeeId: 101 }; 27. Type Narrowing Type narrowing reduces a broad type into a more specific type. typeof Narrowing function formatId( id: number | string ): string { if ( typeof id === 'number' ) { return id.toFixed(0); } return id.toUpperCase(); } 28. Truthiness Narrowing function printName( name: string | null ): void { if (name) { console.log( name.toUpperCase() ); } } 29. in Operator Narrowing interface Admin { role: 'admin'; permissions: string[]; } interface Customer { role: 'customer'; orders: number[]; } function processUser( user: Admin | Customer ) { if ( 'permissions' in user ) { console.log( user.permissions ); } else { console.log( user.orders ); } } 30. instanceof Narrowing function formatDate( value: Date | string ): string { if ( value instanceof Date ) { return value .toISOString(); } return value; } 31. Discriminated Unions A discriminated union uses a shared literal property to identify each possible object type. interface LoadingState { status: 'loading'; } interface SuccessState { status: 'success'; data: User[]; } interface ErrorState { status: 'error'; message: string; } type RequestState = LoadingState | SuccessState | ErrorState; Usage function displayState( state: RequestState ) { switch ( state.status ) { case 'loading': return 'Loading'; case 'success': return ( state.data .length ); case 'error': return ( state.message ); } } 32. Type Assertions A type assertion tells TypeScript to treat a value as a particular type. const input = document.querySelector( '#name' ) as HTMLInputElement; console.log( input.value ); Important Warning A type assertion does not validate or convert the value at runtime. Type Assertion ≠ Runtime Validation 33. Non-Null Assertion The exclamation mark tells TypeScript that a value is not null or undefined. const button = document.querySelector( '#save' )!; button.addEventListener( 'click', save ); Use this carefully because an incorrect assertion can cause a runtime error. 34. Enums An enum defines a named set of values. enum UserRole { Admin, Manager, Customer } Usage const role: UserRole = UserRole.Admin; String Enum enum UserStatus { Active = 'active', Inactive = 'inactive', Pending = 'pending' } Alternative with Literal Types type UserStatus = 'active' | 'inactive' | 'pending'; Literal unions are often simpler when only compile-time value restrictions are needed. 35. Classes class User { id: number; name: string; constructor( id: number, name: string ) { this.id = id; this.name = name; } greet(): string { return ( `Hello ${this.name}` ); } } Create an Instance const user = new User( 1, 'Ajay' ); 36. Access Modifiers Modifier Access public Accessible from anywhere private Accessible only inside the class protected Accessible inside the class and subclasses Example class BankAccount { public owner: string; private balance: number; protected accountType: string; constructor( owner: string, balance: number ) { this.owner = owner; this.balance = balance; this.accountType = 'saving'; } } 37. Parameter Properties TypeScript can create and initialize class properties directly from constructor parameters. class User { constructor( public id: number, public name: string, private password: string ) {} } 38. readonly Class Properties class User { constructor( public readonly id: number, public name: string ) {} } The id property cannot be reassigned after initialization. 39. Getters and Setters class User { private _name = ''; get name(): string { return this._name; } set name( value: string ) { if ( !value.trim() ) { throw new Error( 'Name is required' ); } this._name = value; } } 40. Inheritance class Person { constructor( public name: string ) {} greet(): string { return ( `Hello ${this.name}` ); } } class Employee extends Person { constructor( name: string, public employeeId: number ) { super(name); } } 41. Abstract Classes An abstract class cannot be instantiated directly and can define required subclass behavior. abstract class Payment { abstract process( amount: number ): void; printReceipt(): void { console.log( 'Receipt generated' ); } } Subclass class CardPayment extends Payment { process( amount: number ): void { console.log( `Paid ${amount}` ); } } 42. Implementing Interfaces interface Printable { print(): void; } class Invoice implements Printable { print(): void { console.log( 'Invoice printed' ); } } 43. Generics Generics create reusable code that works with different types while preserving type safety. function identity<T>( value: T ): T { return value; } Usage const numberValue = identity<number>(10); const textValue = identity<string>( 'Hello' ); Type Inference const user = identity({ id: 1, name: 'Ajay' }); 44. Generic Interfaces interface ApiResponse<T> { success: boolean; data: T; message?: string; } User Response const response: ApiResponse<User> = { success: true, data: { id: 1, name: 'Ajay' } }; Array Response const response: ApiResponse<User[]> = { success: true, data: [] }; 45. Generic Constraints A generic constraint limits the types accepted by a generic. interface HasId { id: number; } function findById ( items: T[], id: number ): T | undefined { return items.find( item => item.id === id ); } 46. keyof The keyof operator creates a union of an object's property names. interface User { id: number; name: string; active: boolean; } type UserKey = keyof User; Result 'id' | 'name' | 'active' 47. Generic Property Access function getProperty ( object: T, key: K ): T[K] { return object[key]; } Usage const name = getProperty( user, 'name' ); 48. typeof in Type Positions The typeof operator can create a type from an existing variable. const defaultUser = { id: 0, name: '', active: false }; type User = typeof defaultUser; 49. Indexed Access Types interface User { id: number; profile: { city: string; country: string; }; } type Profile = User['profile']; type City = User['profile']['city']; 50. Utility Types Partial Makes all properties optional. type UserUpdate = Partial<User>; Required Makes all properties required. type CompleteUser = Required<User>; Readonly Makes all properties readonly. type ReadonlyUser = Readonly<User>; Pick Selects specific properties. type UserSummary = Pick< User, 'id' | 'name' >; Omit Removes specific properties. type PublicUser = Omit< User, 'password' >; Record Creates an object type from key and value types. type RolePermissions = Record< string, boolean >; 51. Record Example type Role = 'admin' | 'manager' | 'customer'; const labels: Record< Role, string > = { admin: 'Administrator', manager: 'Manager', customer: 'Customer' }; 52. Modules Named Export export interface User { id: number; name: string; } export function createUser(): User { return { id: 1, name: 'Ajay' }; } Named Import import { createUser, User } from './user'; 53. Default Export export default class UserService {} Default Import import UserService from './user.service'; Named exports are commonly preferred because their imported names remain explicit and refactoring-friendly. 54. Type-Only Imports import type { User } from './user.model'; A type-only import communicates that the imported value is used only during type checking. 55. Optional Chaining const city: string | undefined = user.profile?.city; Optional chaining safely stops when an earlier value is null or undefined. 56. Nullish Coalescing const displayName = user.name ?? 'Guest'; The fallback is used only when the left value is null or undefined. 57. strictNullChecks With strict null checking, null and undefined must be included explicitly when permitted. let selectedUser: User | null = null; Check Before Use if (selectedUser) { console.log( selectedUser.name ); } 58. as const The as const assertion preserves literal values and makes object or array properties readonly. const roles = [ 'admin', 'manager', 'customer' ] as const; Create a Union from the Array type Role = typeof roles[number]; Result 'admin' | 'manager' | 'customer' 59. satisfies Operator The satisfies operator checks that a value matches a type without replacing its inferred type. type Role = 'admin' | 'manager'; const permissions = { admin: [ 'read', 'write' ], manager: [ 'read' ] } satisfies Record< Role, string[] >; Benefit Validate Object Shape + Preserve Specific Inference 60. Function Overloads Function overloads describe multiple supported call signatures. function formatValue( value: number ): string; function formatValue( value: Date ): string; function formatValue( value: number | Date ): string { if ( value instanceof Date ) { return value .toISOString(); } return value .toFixed(2); } 61. Index Signatures An index signature defines object properties whose names are not known in advance. interface ValidationErrors { [ controlName: string ]: string; } Usage const errors: ValidationErrors = { email: 'Invalid email', password: 'Password required' }; 62. Async Functions An async function returns a Promise. async function loadUser(): Promise<User> { const response = await fetch( '/api/users/1' ); if (!response.ok) { throw new Error( 'Request failed' ); } return response.json() as Promise<User>; } Important Warning A type assertion on parsed JSON does not validate the server response at runtime. 63. Promise Types const userPromise: Promise<User> = loadUser(); Array of Results const usersPromise: Promise<User[]> = loadUsers(); 64. Error Handling try { const user = await loadUser(); console.log( user.name ); } catch ( error: unknown ) { if ( error instanceof Error ) { console.error( error.message ); } } Using unknown for caught values encourages proper error narrowing. 65. tsconfig.json The tsconfig.json file controls TypeScript compilation. { "compilerOptions": { "target": "ES2022", "module": "ESNext", "strict": true, "noImplicitAny": true, "strictNullChecks": true, "noUnusedLocals": true, "noUnusedParameters": true } } Important Compiler Options Option Purpose strict Enables a collection of strict type-checking rules noImplicitAny Reports values that receive an unintended any type strictNullChecks Treats null and undefined as distinct types noUnusedLocals Reports unused local declarations noUnusedParameters Reports unused function parameters target Controls the generated JavaScript language level module Controls the generated module format 66. TypeScript with Angular Interface export interface User { id: number; name: string; email: string; } Angular Service @Injectable({ providedIn: 'root' }) export class UserService { private http = inject(HttpClient); getUsers(): Observable<User[]> { return this.http .get<User[]>( '/api/users' ); } } Angular Component @Component({ selector: 'app-users', template: ` @for ( user of users(); track user.id ) { <p> {{ user.name }} </p> } ` }) export class UsersComponent { users = signal<User[]>([]); } 67. TypeScript Does Not Validate Runtime Data this.http.get<User>( '/api/users/1' ); The generic type tells TypeScript what shape the application expects. It does not prove that the server actually returned that shape. Compile-Time Check Expected User Shape Runtime Validation Actual JSON Inspection and Validation Use explicit runtime validation when external data cannot be trusted. 68. Common Mistakes 1. Using any Everywhere Avoid const response: any = getData(); Prefer const response: unknown = getData(); Narrow unknown before use. 2. Using Type Assertions to Hide Errors const user = value as User; This does not validate the value. 3. Ignoring null and undefined const name = user?.name ?? 'Guest'; 4. Creating Duplicate Object Types Use reusable interfaces or type aliases instead of repeating the same structure. 5. Using Optional Properties Without Need Make properties optional only when the value can genuinely be absent. 6. Using Enums for Every Fixed Value Literal unions may be simpler for compile-time-only restrictions. 7. Mutating Readonly Data Create a new object or array instead of modifying readonly state. 8. Disabling Strict Mode Strict checking catches many defects before runtime. 9. Confusing Interface Types with Runtime Values Interfaces and type aliases are removed during compilation and cannot be used as runtime objects. 10. Assuming Generics Validate Data Generics preserve compile-time types but do not inspect runtime values. Best Practices Enable strict TypeScript checking Allow inference for obvious local values Define explicit public function return types Use interfaces or type aliases for reusable structures Prefer unknown over any Use unions for values with multiple valid types Use literal types for controlled values Use discriminated unions for application states Use generics for reusable type-safe logic Use readonly types for immutable contracts Use optional chaining for nullable values Avoid unnecessary type assertions Validate external data at runtime Keep models close to their feature domain Interview Questions What is TypeScript? TypeScript is a statically typed extension of JavaScript that compiles into JavaScript. What is type inference? Type inference is TypeScript's ability to determine a type from a value or surrounding context. What is the difference between any and unknown? Any disables type checking, while unknown requires the value to be narrowed before operations are performed. What is a union type? A union allows a value to be one of multiple types. What is a literal type? A literal type restricts a value to specific strings, numbers, or boolean values. What is the difference between interface and type? Interfaces are commonly used for extensible object contracts, while type aliases can also represent unions, tuples, primitives, and intersections. What is type narrowing? Type narrowing uses checks such as typeof , instanceof , discriminant properties, or the in operator to determine a more specific type. What is a generic? A generic allows reusable code to preserve the type supplied by its caller. What does keyof do? It creates a union containing the property names of another type. What does Partial do? It creates a type where all properties of another type are optional. What does Pick do? It creates a type containing only selected properties from another type. What does Omit do? It creates a type after removing selected properties from another type. What is a type assertion? A type assertion tells the compiler how to treat a value but performs no runtime validation. What is never? Never represents a value or function result that cannot occur through normal completion. What is strictNullChecks? It requires null and undefined to be handled as explicit types rather than being accepted automatically. Does TypeScript run directly in the browser? No. TypeScript is compiled into JavaScript before execution. Key Point TypeScript improves JavaScript development by describing expected data, detecting invalid operations during development, and making large Angular applications easier to maintain. Easy Formula TypeScript = JavaScript + Types + Compiler Checks Reusable Object Shape → interface or type Multiple Possible Types → union Restricted Values → literal type Reusable Typed Logic → generic Unsafe Unknown Data → unknown + type narrowing + runtime validation