Job Interview Questions - Typescript

Table of Content

  1. [[#What is the difference between JavaScript and TypeScript?]]
  2. [[#What TypeScript is bringing to JavaScript?]]
  3. [[#Does TypeScript improve your code only if you change file extension to .ts?]]
  4. [[#How to define basic types inside TypeScript?]]
  5. [[#What is the difference between explicit vs implicit types?]]
  6. [[#What is the difference between type and interface?]]
  7. [[#Do you know what is union inside TypeScript?]]
  8. [[#What do you know about type narrowing?]]
  9. [[#What is the difference between unknown and any types, and why would you use one over the other?]]
  10. [[#How does TypeScript handle null and undefined, and what are the implications of the strictNullChecks option in the tsconfig.json?]]
  11. [[#Can you explain the concept of “declaration merging” in TypeScript and how it applies to interfaces? Additionally, how does this differ from the behavior of types?]]
  12. [[#Can you explain what generics are in TypeScript and give an example of how they can be used to create a reusable function or class?]]
  13. [[#What is the difference between private, protected, and public access modifiers in classes, and how do they affect the accessibility of class members?]]
  14. [[#What is the purpose of the as keyword, and how does it differ from type assertions using angle brackets?]]
  15. [[#What is the difference between function types and callable objects, and how can you define a function that has properties and methods in addition to being callable?]]
  16. [[#Can you explain the difference between never and void in TypeScript, and give examples of when you’d use each?]]
  17. [[#3. What are mapped types, and how can they be used to transform existing types?]]
  18. [[#What is Record utility type?]]
  19. [[#4. How do conditional types work in TypeScript, and can you give an example of a useful conditional type?]]
  20. [[#What does infer do in TypeScript?]]

Questions

What is the difference between JavaScript and TypeScript?
What TypeScript is bringing to JavaScript?

TypeScript brings types to JavaScript which means you can write typings for different things like variables, functions, objects.

In JavaScript we're getting errors only in runtime. TypeScript allows to catch errors in compiling time.

Does TypeScript improve your code only if you change file extension to .ts?

Yes, it can provide code completion and type errors.

How to define basic types inside TypeScript?
const foo: string = "Foo";
What is the difference between explicit vs implicit types?

Explicit types (clarity) - providing type explicitly in the code after ie. name of variable

const foo: string = 'fooo';

Implicit types (reduced boilerplate) - TypeScript infers the type based on the assigned value.

const foo = 'fooo'; // type fooo
let foo = 'bar'; // type string
What is the difference between type and interface?
Do you know what is union inside TypeScript?
type stringOrNumber = string | number;
type LoadingState = {
	state: 'loading';
};

type FailedState = {
	state: 'failed';
	code: number
};

type SuccessState = {
	state: 'success';
	response: {
		title: string;
	};
};

type NetworkState = LoadingState | FailedState | SuccessState;
What do you know about type narrowing?
type stringOrNumber = string | number;

const foo = (value: stringOrNumber) => {
	if (typeof value === 'string') {
		// value is string;
	}
	
	// value is number;
}
type LoadingState = {
	state: 'loading';
};

type FailedState = {
	state: 'failed';
	code: number
};

type SuccessState = {
	state: 'success';
	response: {
		title: string;
	};
};

type NetworkState = LoadingState | FailedState | SuccessState;

const foo = (networkState: NetworkState) => {
	if (networkState.state === 'success') {
		// networkState is of type SuccessState
	} else if (networkState.state == 'failed') {
		// networkState is of type FailedState
	}
	
	// networkState is of type LoadingState
}


const bar = (networkState: NetworkState) => {
	if ('code' in networkState) {
		// networkState is of type FailedState
	} else if ('response' in networkState) {
		// networkState is of type SuccessState
	}
	
	// networkState is of type LoadingState
}
const foo = (value: Date | string) => {
	if (value instanceOf Date) {
		// value is of type Date
	}
	
	// value is of type string;
}
What is the difference between unknown and any types, and why would you use one over the other?
How does TypeScript handle null and undefined, and what are the implications of the strictNullChecks option in the tsconfig.json?

In TypeScript null and undefined are distinct types. By default, variables can be assigned either null or undefined, but when you enable strictNullChecks, TypeScript requires you to explicitly allow these values. This means that if a variable is not declared to accept null or undefined, trying to assign them will result in a type error. This helps catch potential bugs and makes the code more predictable.

Can you explain the concept of “declaration merging” in TypeScript and how it applies to interfaces? Additionally, how does this differ from the behavior of types?

Declaration merging is a feature in TypeScript that allows you to declare same interface multiple times, and TypeScript will merge these declarations into a single interface. This is handy for extending interfaces in different parts of your code, like adding new properties to an existing interface.

question.model.ts

export interface Question {
	readonly caption: [string, string] | [string, string, string];
	readonly answers: string[]
}

helpers.ts

declare module "question.model.ts" {
	interface Question {
		readonly correctIndex: number;
	}
}

On the other hand, types do not support declaration merging. If you try to declare the same type twice, you'll get an error. This means that types are more static and can't be extended in the same way that interfaces can.

Can you explain what generics are in TypeScript and give an example of how they can be used to create a reusable function or class?

Generics in TypeScript are a way to create reusable components that can work with multiple types while maintaining type safety. They allow you to write code that can operate on different types without sacrificing the benefits of TypeScript's type checking.

Generics are like 'type variables' - placeholders that get filled in with actual types when you use the function or class. They're denoted using angle brackets <T> where T is a conventional name for the generic type parameter.

// without generics - only works with strings
function identityString(arg: string): string {
	return arg;
}

// with generics - works with any type
function identity<T>(arg: T): T {
	return arg;
}

// Usage
const stringResult = identity<string>('hello'); // type: string
const numberResult = identity<number>(42); // type: number
const booleanResult = identity<boolean>(true); // type: boolean


You can further constrained a generic using extends, ie.

function stringOrNumber<T extends string | number>(arg: T): T {
	return arg;
}

const isString = stringOrNumber('hello'); // type: string
const isNumber = stringOrNumber(24); // type: number
const isObject = stringOrNumber({ id: 1}); // throws error
What is the difference between private, protected, and public access modifiers in classes, and how do they affect the accessibility of class members?
What is the purpose of the as keyword, and how does it differ from type assertions using angle brackets?

The as keyword in TypeScript is used for type assertions, and it tells the compiler to treat a value as a certain type. This can be especially useful when you know more about the value than the compiler does. The difference between using as and angle brackets <> for type assertions is mostly syntactic. The as syntax is generally preferred in JSX and can be a bit clearer, especially in complex scenarios.

What is the difference between function types and callable objects, and how can you define a function that has properties and methods in addition to being callable?

In TypeScript, function type is essentially a type that describes the shape of a function, including its parameters and return type. It doesn't have any additional properties beyond those defined for the function itself.

On the other hand, a callable object is an object that can be called like a function but also has additional properties and methods. Essentially, it combines the behavior of both an object and a function.

To define a callable object, you can use an interface or a type that includes both a call signature and additional properties. For example, you can define an interface that describes a function signature and then also add properties to it.

This way, the object can be called like a function and still have its own properties and methods.

Example:


interface MyObject { 
	id: number; 
	name: string; 
	(): string; 
} 

function createMyObject(id: number, name: string): MyObject { 
	// Create the callable function 
	const callable = (): string => { 
		return `Hello, I'm ${name} with ID ${id}`; 
	}; 
	
	// Add properties to the function 
	callable.id = id; 
	callable.name = name; 
	
	// Type assert to satisfy the interface 
	return callable as MyObject; 
} 

// Usage 
const obj = createMyObject(1, "Alice"); 
console.log(obj()); // "Hello, I'm Alice with ID 1" 
console.log(obj.id); // 1 
console.log(obj.name); // "Alice"
interface CallableObject {
  (x: number, y: number): number; // This is the call signature
  description: string; // Additional property
  increment: () => void; // Additional method
}

const myCallableObject: CallableObject = ((x: number, y: number) => {
  return x + y;
}) as CallableObject;

myCallableObject.description = "This is a simple adder function";
myCallableObject.increment = () => {
  console.log("Increment function called");
};

console.log(myCallableObject(2, 3)); // Outputs: 5
console.log(myCallableObject.description); // Outputs: This is a simple adder function

myCallableObject.increment(); // Calls the increment method

Can you explain the difference between never and void in TypeScript, and give examples of when you’d use each?

Examples:

function logMessage(msg: string): void {
	console.log(msg);
}

function throwError(msg: string): never {
	throw new Error(msg);
}

function infiniteLoop(): never {
	while (true) {}
}
3. What are mapped types, and how can they be used to transform existing types?

Mapped types is a way to create new types by taking an existing type and transforming its keys

type User = {
	id: number;
	name: string;
	active: boolean
};

type PartialUser = {
	[K in keyof User]?: User[K]
};

type ReadonlyUser = {
	[K in keyof User]: Readonly<User[K]>
}
What is Record utility type?
type UserRoles = "admin" | "editor" | "viewer"
  
const permissions: Record<UserRoles, boolean> = {
  admin: true,
  editor: false,
  viewer: true
}
4. How do conditional types work in TypeScript, and can you give an example of a useful conditional type?
T extends U ? X : Y

type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false


type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
	return { id: 1, name: 'Alice' }
}

type User = ReturnType<typeof getUser>;
// { id: number, name: string }


// nullable handling

type NonNullable<T> = T extends null | undefined ? never : T;

type A = NonNullable<string | null> // string
type B = NonNullable<number | undefined> // number
What does infer do in TypeScript?

infer is a keyword in TypeScript used inside conditional types. It lets you capture and name a type so you can use it later in the same expression.

example:

type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<string[]> // string
type B = ElementType<number[]> // number
type C = ElementType<boolean> // boolean