Unit6 - Subjective Questions
INT219 • Practice Questions with Detailed Answers
Explain the difference between Interface and Type aliases in TypeScript. When should you prefer one over the other?
In TypeScript, both Interfaces and Type Aliases can be used to describe the shape of an object or a function signature, but they have distinct differences:
1. Interfaces
- Declaration Merging: Interfaces are open for extension. If you declare the same interface twice, TypeScript will merge them.
- Extensibility: Interfaces generally provide better error messages and performance during compilation.
- Syntax:
typescript
interface User {
name: string;
id: number;
}
2. Type Aliases
- Flexibility: Types can define primitives, unions, tuples, and intersections, which interfaces cannot directly represent.
- No Declaration Merging: You cannot re-declare a type to add properties.
- Syntax:
typescript
type ID = string | number; // Union type
type User = {
name: string;
id: ID;
};
When to use which?
- Use Interfaces for defining the shape of objects, especially public APIs and libraries, to allow consumers to extend them via declaration merging.
- Use Types for defining unions (e.g.,
Status = 'success' | 'error'), tuples, or complex mapped types.
Describe the concept of Generics in TypeScript. How do they contribute to creating reusable component logic? Provide a code example.
Generics allow you to create reusable code components that work with a variety of types rather than a single one. They act as variables for types, allowing you to capture the type provided by the user (e.g., when a function is called) and use that information later.
Contribution to Reusability
Generics ensure type safety without losing the information about the specific data type being used. Instead of using any (which loses type details), Generics preserve the relationship between input and output types.
Example
A generic identity function that returns the same type it receives:
typescript
// T captures the type passed in
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // Type is string
let output2 = identity<number>(100); // Type is number
In a frontend context (e.g., React), this is often used for Props:
typescript
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
What are TypeScript Utility Types? Explain the usage of Partial<T>, Pick<T, K>, and Omit<T, K> with examples.
TypeScript provides several utility types to facilitate common type transformations. These help in creating flexible type definitions without code duplication.
1. Partial<T>
Constructs a type with all properties of Type T set to optional. This is useful for updating state where only a subset of fields is required.
typescript
interface Todo {
title: string;
description: string;
}
// fields in update are optional
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) { ... }
2. Pick<T, K>
Constructs a type by picking the set of properties K from T.
typescript
interface User {
id: number;
name: string;
email: string;
}
type UserPreview = Pick<User, "id" | "name">;
// UserPreview only has id and name
3. Omit<T, K>
Constructs a type by picking all properties from T and then removing K.
typescript
type UserWithoutEmail = Omit<User, "email">;
// UserWithoutEmail has id and name
Explain the strategy for typing API responses in a TypeScript project. How do you ensure the data received from the backend matches the expected type?
Typing API responses involves defining interfaces or types that mirror the JSON structure returned by the backend.
Strategy
-
Define Interfaces: Create specific interfaces for the expected data models.
typescript
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User { id: number; name: string; } -
Generic Fetch Wrapper: Create a wrapper around
fetchoraxiosthat accepts a generic type.
typescript
async function http<T>(request: RequestInfo): Promise<T> {
const response = await fetch(request);
return await response.json();
} -
Usage:
typescript
const user = await http<ApiResponse<User>>('/api/user');
// user.data is strictly typed as User
Runtime Validation
TypeScript types exist only at compile time. To ensure runtime safety (if the backend changes the schema without warning), developers often use runtime validation libraries like Zod or Yup. These libraries validate the actual JavaScript object against a schema and infer the TypeScript type automatically.
Discuss the process of integrating TypeScript into an existing JavaScript frontend project. What is the recommended migration strategy?
Migrating a large JavaScript project to TypeScript is usually done incrementally to avoid halting development.
Migration Strategy
- Installation: Install
typescriptand generate atsconfig.jsonfile. - Configuration (Loose Mode):
- Set
"allowJs": trueto allow JS and TS files to coexist. - Set
"checkJs": falseinitially to prevent an overwhelming number of errors in existing JS files. - Set
"noImplicitAny": falsetemporarily.
- Set
- Rename Files: Start identifying low-level utility files or independent components and rename them from
.js/.jsxto.ts/.tsx. - Fix Errors: Fix type errors in the newly renamed files. Use
anystrictly as a temporary measure if a type is too complex to resolve immediately. - Strict Mode: Once a significant portion of the codebase is converted, enable
"strict": trueintsconfig.jsonfor better type safety. - CI/CD Integration: Add a type-checking step (
tsc --noEmit) to the build pipeline to prevent regression.
What is Static Code Analysis? Explain the roles of ESLint and Prettier in maintaining code quality.
Static Code Analysis is the process of debugging source code without running the program. It analyzes the code structure to detect patterns, bugs, potential security leaks, and adherence to coding standards.
ESLint (Linter)
- Role: Focuses on code quality and logic.
- Function: It identifies problematic patterns like unused variables, unreachable code, dangerous type coercions, or violation of framework-specific rules (e.g., React hooks rules).
- Output: It provides warnings or errors and can auto-fix many logic-based syntax issues.
Prettier (Formatter)
- Role: Focuses on code style and formatting.
- Function: It enforces a consistent style guide regarding indentation, spacing, quotes, and line length. It parses the code and reprints it with its own rules.
- Integration: Often used together; ESLint handles the logic, while Prettier handles the visual layout, ensuring the codebase looks like it was written by a single person.
Explain the importance of Source Maps in the debugging workflow of a TypeScript application.
Source Maps are JSON files that create a mapping between the minified/transpiled code (production code running in the browser) and the original source code (TypeScript authored by the developer).
Importance in Debugging
- Translation: Browsers execute JavaScript, not TypeScript. Without source maps, if an error occurs, the stack trace points to a line in the compiled
.jsbundle, which is often unreadable (minified) or significantly different from the original logic. - Developer Experience: Source maps allow developers to open the browser's Developer Tools (Sources tab) and see their original
.tsor.tsxfiles. - Breakpoints: They enable setting breakpoints directly in the TypeScript code within the browser, allowing for step-by-step debugging of variables and logic as it was written.
In tsconfig.json, this is enabled via:
{
"compilerOptions": {
"sourceMap": true
}
}
Compare Unit Testing and End-to-End (E2E) Testing in the context of frontend development. Mention one popular tool for each.
| Feature | Unit Testing | End-to-End (E2E) Testing |
|---|---|---|
| Scope | Tests individual functions, components, or modules in isolation. | Tests the entire application flow from the user's perspective (browser to backend). |
| Speed | Very fast execution (milliseconds). | Slower execution (seconds to minutes) as it requires a browser instance. |
| Dependencies | Mocks external dependencies (APIs, databases). | Uses real dependencies or a fully integrated test environment. |
| Purpose | Verifies logic correctness and edge cases of small units. | Verifies that the system works as a whole and critical user journeys function correctly. |
| Popular Tool | Jest or Vitest | Cypress or Playwright |
What are Discriminated Unions in TypeScript, and how do they improve type safety in state management (e.g., Redux or generic reducers)?
Discriminated Unions (also known as Tagged Unions) are a pattern where a union of object types all share a common literal property (the "discriminant" or "tag"). TypeScript uses this tag to narrow down the specific type.
Improvement in State Management
In state management like Redux, actions often have different payload shapes depending on the action type.
Example:
typescript
interface LoadAction { type: 'LOAD'; }
interface SuccessAction { type: 'SUCCESS'; payload: string[]; }
interface FailAction { type: 'FAIL'; error: string; }
type Action = LoadAction | SuccessAction | FailAction;
function reducer(state: State, action: Action) {
switch (action.type) { // 'type' is the discriminant
case 'LOAD':
// TS knows action is LoadAction here
return { loading: true };
case 'SUCCESS':
// TS knows action is SuccessAction (has payload)
return { loaded: action.payload };
case 'FAIL':
// TS knows action is FailAction (has error)
return { error: action.error };
}
}
This prevents accessing payload when the action is FAIL, preventing runtime crashes.
Describe the Arrange-Act-Assert (AAA) pattern in testing fundamentals.
Arrange-Act-Assert (AAA) is a standard pattern for formatting code in unit tests. It helps improve the readability and maintainability of tests by clearly separating the test logic.
1. Arrange
Set up the conditions for the test. This includes creating objects, setting up mocks, and defining input variables.
Example: const calculator = new Calculator(); const input = 5;
2. Act
Execute the code or function being tested using the arranged parameters.
Example: const result = calculator.square(input);
3. Assert
Verify that the result matches the expectation. This determines if the test passes or fails.
Example: expect(result).toBe(25);
Mathematical Representation in Test:
If testing , checking if .
What is Code Coverage, and why is it an important metric for Quality Assurance? Define Statement, Branch, and Function coverage.
Code Coverage is a metric used to measure the percentage of source code executed during automated testing. It indicates how much of the application is actually verified by the test suite.
Importance
It helps identify untested parts of the codebase (blind spots), reducing the likelihood of undetected bugs in production. However, high coverage does not guarantee bug-free code, only executed code.
Types of Coverage
- Statement Coverage: The percentage of executable statements in the source code that have been executed at least once.
- Branch Coverage: Ensures that each branch of control structures (e.g.,
if,else,case) has been executed. If there is anif (x > 0), branch coverage checks both true and false paths. - Function Coverage: The percentage of defined functions that have been called.
Explain the SOLID principles and how they apply to frontend component design and maintainability.
SOLID is an acronym for five design principles intended to make software designs more understandable, flexible, and maintainable.
- Single Responsibility Principle (SRP): A component should do one thing properly.
- Frontend: A
UserProfilecomponent should display data, while a separate hook/service handles fetching that data.
- Frontend: A
- Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification.
- Frontend: Designing a generic
Buttoncomponent that acceptspropsorchildrento change appearance without rewriting the component code.
- Frontend: Designing a generic
- Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types.
- Frontend: If a component accepts a
Userinterface, it should work correctly with any specific type of user (e.g.,AdminUser) that extends it.
- Frontend: If a component accepts a
- Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use.
- Frontend: Instead of passing a giant
Userobject to a generic UI card, pass only the specific props needed (name,image).
- Frontend: Instead of passing a giant
- Dependency Inversion Principle (DIP): Depend on abstractions, not concretions.
- Frontend: Components should depend on context or hooks (abstractions) rather than importing explicit fetch logic directly inside the UI layer.
Define Technical Debt. How does strict TypeScript configuration help in reducing technical debt over time?
Technical Debt refers to the implied cost of additional rework caused by choosing an easy or limited solution now instead of using a better approach that would take longer. It accumulates like financial debt; if not paid back (refactored), it makes future development harder.
How TypeScript Reduces Technical Debt
- Self-Documentation: Explicit types act as live documentation. Developers don't need to guess what properties an object has, reducing the cognitive load and "knowledge debt."
- Refactoring Confidence: Strict typing allows developers to rename variables or restructure data with confidence. If a change breaks a usage elsewhere, the compiler catches it immediately, unlike JS where it breaks at runtime.
- Preventing "Any" Creep: Strict configuration (
noImplicitAny) prevents lazy coding practices where developers bypass type checks, ensuring the code remains robust. - Early Bug Detection: Catching type-related bugs at compile time prevents them from becoming production incidents, which are the most expensive form of debt to fix.
What are Code Smells? List three common code smells in frontend applications and how to resolve them.
Code Smells are surface-level indications that usually correspond to a deeper problem in the system. They aren't bugs, but they indicate weaknesses in design that may slow down development or increase the risk of bugs or failures in the future.
Common Frontend Code Smells:
- God Component / Large Class:
- Issue: A component that handles too many responsibilities (UI, State, API calls, Form validation).
- Resolution: Break it down into smaller sub-components or extract logic into custom hooks (SRP).
- Prop Drilling:
- Issue: Passing data through many layers of components just to reach a deeply nested child.
- Resolution: Use Component Composition, React Context API, or State Management libraries (Redux/Zustand).
- Magic Numbers/Strings:
- Issue: Using hardcoded values like
if (status === 2)or specific hex colors repeatedly. - Resolution: Extract them into named constants or Enums (e.g.,
enum Status { Success = 2 }) to give semantic meaning.
- Issue: Using hardcoded values like
Explain the concept of Mocking in testing. Why is it necessary when testing frontend components that interact with APIs?
Mocking is the practice of replacing real dependencies (like API clients, databases, or complex functions) with simulated versions (mocks) that mimic the behavior of the real ones in a controlled way.
Necessity in Frontend Testing:
- Isolation: Unit tests should test the component logic, not the backend server. Mocking ensures tests don't fail just because the backend is down.
- Speed: Real API calls take time (network latency). Mocks return data instantly, keeping the test suite fast.
- Deterministic Results: Real data changes. Mocks allow you to return specific data scenarios (e.g., empty list, specific error code 500) to test how the UI handles edge cases consistently.
- Cost: Avoids unnecessary load or costs associated with calling third-party services (e.g., payment gateways) during testing.
What are the key configuration options in tsconfig.json that control the strictness and output of a TypeScript project? Explain strict, target, and module.
The tsconfig.json file dictates how the TypeScript compiler (tsc) transforms code.
-
strict:- A master switch that enables a wide range of type-checking behavior that results in stronger guarantees of program correctness. It turns on
noImplicitAny,strictNullChecks,strictFunctionTypes, etc. It is highly recommended for new projects to ensure high code quality.
- A master switch that enables a wide range of type-checking behavior that results in stronger guarantees of program correctness. It turns on
-
target:- Specifies the ECMAScript target version for the output JavaScript (e.g.,
ES5,ES6/ES2015,ESNext). If set toES5, TypeScript will compile arrow functions down tofunctionkeywords for older browser compatibility.
- Specifies the ECMAScript target version for the output JavaScript (e.g.,
-
module:- Specifies the module system for the generated code. Common values are
CommonJS(for Node.js) orESNext/ES2020(for modern bundlers like Webpack or Vite). This ensures imports and exports are handled correctly for the runtime environment.
- Specifies the module system for the generated code. Common values are
Discuss the workflow for Code Reviews in a team environment. What specific things should a reviewer look for in a TypeScript pull request?
Code Review is a quality assurance practice where code changes are examined by other developers before being merged.
Workflow
- Open PR: Developer pushes code and opens a Pull Request (PR) with a description of changes.
- Automated Checks: CI tools run linting and tests.
- Manual Review: Peers review the logic and style.
- Feedback/Iterate: Comments are addressed.
- Approval/Merge: Code is merged into the main branch.
TypeScript Specific Checklist
- Type Safety: Are
anytypes avoided? Can the types be more specific? - Correct Usage of Generics: Is the code reusable? Are generics used correctly or are they over-engineered?
- Interface Naming: Do interface names clearly describe the data model?
- Handling
null/undefined: Does the code handle optional properties safely (e.g., using Optional Chaininguser?.name)? - Type Inference: Is the code cluttered with unnecessary type annotations where TS could infer them automatically?
Derive the benefits of using Test Driven Development (TDD). How does the TDD cycle function?
Test Driven Development (TDD) is a software development process where tests are written before the actual code.
The TDD Cycle (Red-Green-Refactor)
- Red: Write a test for a specific functionality that does not exist yet. Run the test; it must fail (compilation error or assertion failure).
- Green: Write the minimum amount of code necessary to make the test pass. Do not worry about code quality or elegance yet.
- Refactor: Clean up the code (remove duplication, improve structure) while ensuring the test still passes.
Benefits
- Design Focus: Forces developers to think about the API design and requirements before implementation.
- Reduced Bugs: Since code is written to satisfy tests, high test coverage is guaranteed from the start.
- Documentation: The tests serve as live documentation of how the system is supposed to behave.
- Maintainability: Refactoring is safer because the test suite immediately alerts if a change breaks existing functionality.
Explain the difference between unknown and any in TypeScript. Why is unknown considered the type-safe counterpart of any?
Both any and unknown are top types in TypeScript (meaning any value can be assigned to them), but they behave very differently when used.
any
- Behavior: It effectively disables type checking. You can access any property, call it as a function, or assign it to any other type.
- Risk: It defeats the purpose of TypeScript.
obj.method()will compile even ifobjis a number, leading to runtime crashes.
unknown
- Behavior: You can assign anything to it, BUT you cannot perform operations on it (like accessing properties or calling methods) without first checking its type.
- Safety: It forces the developer to perform Type Narrowing.
Example
typescript
let value: unknown;
value = "hello";
// value.toUpperCase(); // Error: Object is of type 'unknown'.
if (typeof value === "string") {
console.log(value.toUpperCase()); // OK, TS knows it's a string here
}
unknown is safer because it requires validation before usage.
How does Continuous Integration (CI) support Quality Assurance in frontend projects? Describe a typical CI pipeline for a TypeScript application.
Continuous Integration (CI) is the practice of automating the integration of code changes from multiple contributors into a single software project. It supports QA by ensuring that every change is verified automatically, preventing "integration hell."
Typical TypeScript CI Pipeline Steps:
- Checkout: The CI server (e.g., GitHub Actions, Jenkins) pulls the latest code from the repository.
- Install Dependencies: Runs
npm installoryarnto set up the environment. - Linting: Runs
npm run lint(ESLint) to check for code style and potential errors. Fails if rules are violated. - Type Checking: Runs
tsc --noEmit. This is crucial as it checks for TypeScript errors that Babel/Webpack might ignore during a build. - Testing: Runs
npm test(Jest/Vitest) to execute unit and integration tests. - Build: Runs
npm run buildto ensure the application compiles correctly for production.
If any step fails, the merge is blocked, maintaining the quality of the main codebase.