Interview Questions – CoderPad https://coderpad.io Online IDE for Technical Interviews Wed, 13 Aug 2025 11:46:51 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://coderpad.io/wp-content/uploads/2024/01/cropped-coderpad-favicon-32x32.png Interview Questions – CoderPad https://coderpad.io 32 32 Angular https://coderpad.io/interview-questions/angular-interview-questions/ Tue, 11 Apr 2023 14:00:27 +0000 https://coderpad.io/?post_type=interview-questions&p=32750 The Google-developed Angular consistently ranks as a top front-end framework due to its built-in TypeScript support, two-way data binding, and powerful CLI.

According to the CoderPad 2024 Developer survey, Angular 2+ is the second-most in-demand front-end framework among technical recruiters.

The following sections offer a collection of pragmatic coding tasks and interview inquiries designed to assess a developer’s proficiency in Angular during coding interviews.

Furthermore, a compilation of recommended practices is incorporated to guarantee the accurate measurement of candidates’ Angular expertise through your interview questions.

Angular example question

Angular directives project

You have two tasks to perform on this simple Angular project:

  • Create a method in src/directives/textchange.directive.ts to add an element to the DOM whose text is a random number.
  • Create a method in src/directives/basiccol.directive.ts to create a box beneath that becomes visible upon generating a random number. There should be a color change when the box is hovered over.

Hint: the Renderer2 class is Angular’s most recommended method for manipulating the DOM. Ensure to use it while creating your functions.

Junior Angular interview questions

Question:
The following Angular component is not correctly defining the AppComponent class. Identify and correct the issue.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<h1>Hello, {{ name }}</h1>',
})
export class AppComponent {
  name: string = 'John Doe';

Answer:
The issue is that the AppComponent class is missing the closing curly brace (}). To fix it, add the missing curly brace at the end of the class definition as follows:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<h1>Hello, {{ name }}</h1>',
})
export class AppComponent {
  name: string = 'John Doe';
}

With this correction, the AppComponent class is properly defined.

Question:
What is dependency injection in Angular and why is it important?

Answer:
Dependency injection is a design pattern used in Angular to manage the dependencies of an application. It allows you to provide instances of classes or services to components or other services that need them. By using dependency injection, you can easily replace dependencies, write more modular and testable code, and promote code reusability.

Question:
The following Angular template is not correctly binding to the items property of the component. Identify and correct the issue.

import { Component } from '@angular/core';

@Component({
  selector: 'app-item-list',
  template: `
    <ul>
      <li *ngFor="let item of items">{{ item }}</li>
    </ul>
  `,
})
export class ItemListComponent {
  items: string[] = ['Item 1', 'Item 2', 'Item 3'];
}

Answer:
The issue is that the items property is not declared as an input property using the @Input decorator. To fix it, modify the component as follows:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-item-list',
  template: `
    <ul>
      <li *ngFor="let item of items">{{ item }}</li>
    </ul>
  `,
})
export class ItemListComponent {
  @Input() items: string[] = ['Item 1', 'Item 2', 'Item 3'];
}

Question:
Explain the concept of Angular modules and their purpose in an application.

Answer:
Angular modules are used to organize and encapsulate related functionality within an Angular application. They serve as containers for components, directives, pipes, and services that belong to a specific feature or functionality. Modules help in organizing the codebase, promoting reusability, and managing dependencies. They also enable lazy loading and improve performance by loading only the required modules on demand.

Question:
The following Angular component has a mistake in the constructor that prevents the service dependency from being injected. Identify and correct the issue.

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-my-component',
  template: '<h1>Data: {{ data }}</h1>',
})
export class MyComponent {
  data: string;

  constructor(dataService: DataService) {
    this.data = dataService.getData();
  }
}

Answer:
The issue is that the dataService parameter in the constructor is missing the access modifier private. To fix it, modify the constructor as follows:

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-my-component',
  template: '<h1>Data: {{ data }}</h1>',


})
export class MyComponent {
  data: string;

  constructor(private dataService: DataService) {
    this.data = dataService.getData();
  }
}

Question:
What is Angular routing and how does it work?

Answer:
Angular routing is a mechanism that allows navigation between different components and views within an application. It enables users to move from one page to another without requiring a full page reload. Angular routing uses the Angular Router module to define routes, map them to components, and handle navigation. Routes are configured with specific URLs, and when a user navigates to a URL, Angular Router loads the associated component and updates the view.

Question:
The following Angular template has a typo that causes a compilation error. Identify and correct the issue.

import { Component } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: '<p>Counter: {{ count }}<p>',
})
export class CounterComponent {
  count = 0;
}

Answer:
The issue is that the closing </p> tag is missing the forward slash /. To fix it, modify the template as follows:

<p>Counter: {{ count }}</p>

Question:
What are Angular directives? Provide examples of built-in directives and explain their usage.

Answer:
Angular directives are markers on DOM elements that tell Angular to attach specific behaviors or apply transformations to those elements. They are used to manipulate the structure and behavior of components and templates.

Examples of built-in directives in Angular include:

  • ngIf: Conditionally renders an element based on a condition.
  • ngFor: Iterates over a collection and generates DOM elements for each item.
  • ngStyle: Binds CSS styles to an element based on specified expressions.
  • ngClass: Binds CSS classes to an element based on specified conditions.

These directives are used within templates to dynamically control the rendering, styling, and behavior of elements based on application logic.

Question:
The following Angular component is missing the necessary imports for Component and Input. Identify and correct the issue.

@Component({
  selector: 'app-product',
  template: '<h1>{{ name }}</h1>',
})
export class ProductComponent {
  @Input() name: string;
}

Answer:
The issue is that the Component and Input imports are missing. To fix it, add the necessary import statements as follows:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-product',
  template: '<h1>{{ name }}</h1>',
})
export class ProductComponent {
  @Input() name: string;
}

Question:
Theoretical: What is the purpose of Angular services and how are they used within components?

Answer:
Angular services are used to organize and share code across multiple components or throughout an application. They provide a way to encapsulate and manage application logic, data retrieval, communication with servers, and other common tasks. Services are typically used within components by injecting them as dependencies. This allows components to access the functionality and data provided by the service, promoting code separation, reusability, and testability.

Intermediate Angular interview questions

Question:
The following Angular component is not correctly subscribing to a data service and fetching the data. Identify and correct the issue.

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of items">{{ item.name }}</li></ul>',
})
export class DataComponent implements OnInit {
  items: any[];

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.getData().subscribe((data: any[]) => {
      this.items = data;
    });
  }
}

Answer:

The issue is that the component is not importing the DataService correctly. To fix it, ensure that the correct import statement is added at the top of the file:

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of items">{{ item.name }}</li></ul>',
})
export class DataComponent implements OnInit {
  items: any[];

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.getData().subscribe((data: any[]) => {
      this.items = data;
    });
  }
}

Question:
Theoretical: What is the purpose of Angular CLI? How does it simplify Angular development?

Answer:
Angular CLI (Command Line Interface) is a command-line tool used for developing Angular applications. It provides a set of powerful features that simplify and automate various development tasks, such as creating new projects, generating components, services, and modules, running tests, optimizing the build, and more. Angular CLI abstracts away the complex configuration and setup required for an Angular project, allowing developers to focus on writing code and improving productivity.

Question:
The following Angular component is not correctly handling an HTTP error. Identify and correct the issue.

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of items">{{ item.name }}</li></ul>',
})
export class DataComponent implements OnInit {
  items: any[];
  error: string;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get('https://api.example.com/data').subscribe(
      (data: any[]) => {
        this.items = data;
      },
      (error) => {
        this.error = 'An error occurred while fetching the data.';
      }
    );
  }
}

Answer:
The issue is that the error handling is not properly implemented. To fix it, add the error parameter in the error callback function and assign the error message to the error property:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of items">{{ item.name }}</li></ul>',
})
export class DataComponent implements OnInit {
  items: any[];
  error: string;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get('https://api.example.com/data').subscribe(
      (data: any[]) => {
        this.items = data;
      },
      (error) => {
        this.error = 'An error occurred while fetching the data: ' + error.message;


      }
    );
  }
}

Question:
What are Angular guards and how are they used in routing?

Answer:
Angular guards are a feature of Angular’s routing module that allow developers to control and manage navigation to and from routes. Guards can be used to implement various security and authentication checks, route access permissions, and other pre-navigation checks. There are different types of guards, including CanActivate, CanActivateChild, CanDeactivate, and CanLoad. They are implemented as classes that can be attached to routes in the route configuration to control access and navigation behavior.

Question:
The following Angular component is not correctly using the async pipe to handle an asynchronous data stream. Identify and correct the issue.

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { DataService } from './data.service';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of items | async">{{ item.name }}</li></ul>',
})
export class DataComponent {
  items: any[];

  constructor(private dataService: DataService) {
    this.items = this.dataService.getData();
  }
}

Answer:
The issue is that the items property is not correctly assigned an observable value. To fix it, update the assignment to use the getData() method as an observable:

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { DataService } from './data.service';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of items | async">{{ item.name }}</li></ul>',
})
export class DataComponent {
  items: Observable<any[]>;

  constructor(private dataService: DataService) {
    this.items = this.dataService.getData();
  }
}

Question:
Theoretical: What is Angular’s HttpClient module and how is it used for making HTTP requests?

Answer:
Angular’s HttpClient module is a built-in module that provides a simplified way to make HTTP requests from an Angular application. It offers a higher-level API compared to the older Http module. HttpClient provides methods such as get(), post(), put(), delete(), etc., to perform various HTTP operations. It supports request and response interception, error handling, progress tracking, and configuration options like headers and query parameters. HttpClient returns observables that can be subscribed to in order to retrieve response data.

Question:
The following Angular component is not correctly implementing a reactive form. Identify and correct the issue.

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-form',
  template: `
    <form [formGroup]="form">
      <label>Name:</label>
      <input formControlName="name" placeholder="Enter your name" />
    </form>
    <p>You entered: {{ form.controls.name.value }}</p>
  `,
})
export class FormComponent {
  form: FormGroup;

  constructor() {
    this.form = new FormGroup({
      name: new FormControl(),
    });
  }
}

Answer:
The issue is that the form control binding is missing the formControlName attribute in the input element. To fix it, add the formControlName attribute with the corresponding control name:

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-form',
  template: `
    <form [formGroup]="form">


 <label>Name:</label>
      <input formControlName="name" placeholder="Enter your name" />
    </form>
    <p>You entered: {{ form.controls.name.value }}</p>
  `,
})
export class FormComponent {
  form: FormGroup;

  constructor() {
    this.form = new FormGroup({
      name: new FormControl(),
    });
  }
}

Question:
Explain the concept of lazy loading in Angular and its benefits in large applications.

Answer:
Lazy loading is a technique in Angular where modules are loaded only when they are needed, rather than loading the entire application upfront. In large applications, lazy loading can significantly improve performance by reducing the initial bundle size and decreasing the load time. It allows splitting the application into smaller feature modules that can be loaded on-demand, as the user navigates to specific routes. Lazy loading helps optimize resource utilization, improves the user experience, and reduces the time required for the initial application load.

Question:
The following Angular component is not correctly implementing a custom directive. Identify and correct the issue.

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[appCustomDirective]',
})
export class CustomDirective {
  constructor(private elementRef: ElementRef) {
    elementRef.nativeElement.style.backgroundColor = 'red';
    elementRef.nativeElement.style.color = 'white';
  }
}

Answer:
The issue is that the code is missing the necessary import statement for ElementRef. To fix it, add the import statement at the top of the file:

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[appCustomDirective]',
})
export class CustomDirective {
  constructor(private elementRef: ElementRef) {
    elementRef.nativeElement.style.backgroundColor = 'red';
    elementRef.nativeElement.style.color = 'white';
  }
}

Question:
What are Angular templates and how are they used to define the user interface in Angular applications?

Answer:
Angular templates are HTML-based views that define the user interface of an Angular component. They provide a way to structure and render the desired content on the screen. Angular templates can include HTML elements, data bindings, directives, event bindings, and template expressions. They are used in conjunction with component classes to define the behavior and presentation logic of the application. Templates support features like structural directives (*ngFor, *ngIf), property binding ([property]), event binding ((event)), and interpolation ({{ expression }}), enabling dynamic and interactive rendering of content.

Senior Angular interview questions

Question:
The following Angular component is not correctly implementing lazy loading for a feature module. Identify and correct the issue.

import { Component } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

@Component({
  selector: 'app-root',
  template: `
    <router-outlet></router-outlet>
  `,
})
export class AppComponent {}

const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: './dashboard/dashboard.module',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Answer:
The issue is that the loadChildren property is not correctly specifying the path to the feature module. To fix it, update the loadChildren value to include the correct module path:

const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
  },
];

Question:
Explain the concept of AOT (Ahead-of-Time) compilation in Angular and its benefits.

Answer:
AOT (Ahead-of-Time) compilation is a compilation technique used in Angular to convert Angular templates and components into highly optimized JavaScript code during the build process. Unlike JIT (Just-in-Time) compilation, which compiles templates at runtime in the browser, AOT compilation precompiles templates and components before the application is deployed. The benefits of AOT compilation include improved application performance, smaller bundle sizes, early error detection, and better security by eliminating the need to ship the Angular compiler to the client.

Question:
The following Angular component is not correctly implementing change detection. Identify and correct the issue.

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user',
  template: '<h1>{{ user.name }}</h1>',
})
export class UserComponent {
  @Input() user: any;
}

Answer:
The issue is that the UserComponent is missing the change detection strategy. To fix it, add the ChangeDetectionStrategy property to the component and specify the desired strategy:

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-user',
  template: '<h1>{{ user.name }}</h1>',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserComponent {
  @Input() user: any;
}

Question:
What are Angular schematics? How can they be used to generate and modify code in an Angular project?

Answer:
Angular schematics are generators and transformers of code and files in an Angular project. They provide a way to automate common development tasks, such as generating components, services, modules, and other artifacts, as well as modifying existing code. Schematics can be executed using the Angular CLI or directly through the Schematics API. They allow developers to define custom blueprints and templates, apply code transformations, and enforce project-specific conventions and best practices, thus enhancing development efficiency and consistency.

Question:
The following Angular component is not correctly using RxJS operators to handle data streams. Identify and correct the issue.

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of

 items$ | async">{{ item }}</li></ul>',
})
export class DataComponent {
  items$: Observable<string[]>;

  constructor() {
    this.items$ = this.getData().pipe(
      map((data: any[]) => {
        return data.map(item => item.name);
      })
    );
  }

  getData(): Observable<any[]> {
    // Simulated HTTP request
    return new Observable(observer => {
      setTimeout(() => {
        observer.next([{ name: 'Item 1' }, { name: 'Item 2' }, { name: 'Item 3' }]);
        observer.complete();
      }, 1000);
    });
  }
}

Answer:
The issue is that the import statement for the map operator is incorrect. To fix it, update the import statement to import map from the correct location:

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-data',
  template: '<ul><li *ngFor="let item of items$ | async">{{ item }}</li></ul>',
})
export class DataComponent {
  items$: Observable<string[]>;

  constructor() {
    this.items$ = this.getData().pipe(
      map((data: any[]) => {
        return data.map(item => item.name);
      })
    );
  }

  getData(): Observable<any[]> {
    // Simulated HTTP request
    return new Observable(observer => {
      setTimeout(() => {
        observer.next([{ name: 'Item 1' }, { name: 'Item 2' }, { name: 'Item 3' }]);
        observer.complete();
      }, 1000);
    });
  }
}

Question:
What is NgRx and how does it facilitate state management in Angular applications?

Answer:
NgRx is a library for reactive state management in Angular applications. It is based on the principles of Redux, a popular state management pattern and library. NgRx provides a set of building blocks, including actions, reducers, selectors, and effects, to manage the state of an application in a predictable and scalable way. It enables developers to centralize and manage application state, make state changes explicit and traceable, handle complex asynchronous operations, and easily share state between components. NgRx follows a unidirectional data flow and encourages immutability and pure functions for state updates.

Question:
The following Angular component is not correctly implementing server-side rendering (SSR). Identify and correct the issue.

import { Component, OnInit } from '@angular/core';
import { TransferState, makeStateKey } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  template: '<h1>Welcome to {{ title }}</h1>',
})
export class HomeComponent implements OnInit {
  title: string;

  constructor(private transferState: TransferState) {}

  ngOnInit() {
    const key = makeStateKey<string>('title');
    this.title = this.transferState.get(key, '');
    this.transferState.set(key, 'Angular SSR Example');
  }
}

Answer:
The issue is that the code is missing the necessary import statement for TransferState and makeStateKey. To fix it, add the import statements at the top of the file:

import { Component, OnInit } from '@angular/core';
import { TransferState, makeStateKey } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  template: '<h1>Welcome to {{ title }}</h1>',
})
export class HomeComponent implements OnInit {
  title: string;

  constructor(private transferState: TransferState) {}

  ngOnInit() {


 const key = makeStateKey<string>('title');
    this.title = this.transferState.get(key, '');
    this.transferState.set(key, 'Angular SSR Example');
  }
}

Question:
What are Angular interceptors and how are they used in HTTP requests?

Answer:
Angular interceptors are a feature of Angular’s HttpClient module that allow developers to intercept and modify HTTP requests and responses. Interceptors provide a way to add custom logic or behaviors, such as request authentication, error handling, caching, logging, or adding headers, to HTTP communications. They can be used to implement cross-cutting concerns and avoid duplicating code across multiple HTTP requests. Interceptors are implemented as classes that can be registered globally or locally in the HttpClient configuration to intercept HTTP requests and responses at various stages of the communication process.

Question:
The following Angular component is not correctly implementing component communication using a service. Identify and correct the issue.

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-parent',
  template: `
    <h1>Parent Component</h1>
    <button (click)="updateData()">Update Data</button>
  `,
})
export class ParentComponent {
  constructor(private dataService: DataService) {}

  updateData() {
    this.dataService.setData('New data');
  }
}

@Component({
  selector: 'app-child',
  template: '<h2>{{ data }}</h2>',
})
export class ChildComponent {
  data: string;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.data = this.dataService.getData();
  }
}

@Injectable()
export class DataService {
  private data: string;

  setData(value: string) {
    this.data = value;
  }

  getData(): string {
    return this.data;
  }
}

Answer:
The issue is that the DataService is not registered as a provider in the module. To fix it, add the DataService to the providers array in the module:

import { Component, Injectable } from '@angular/core';

@Injectable()
export class DataService {
  private data: string;

  setData(value: string) {
    this.data = value;
  }

  getData(): string {
    return this.data;
  }
}

@Component({
  selector: 'app-parent',
  template: `
    <h1>Parent Component</h1>
    <button (click)="updateData()">Update Data</button>
  `,
})
export class ParentComponent {
  constructor(private dataService: DataService) {}

  updateData() {
    this.dataService.setData('New data');
  }
}

@Component({
  selector: 'app-child',
  template: '<h2>{{ data }}</h2>',
})
export class ChildComponent {
  data: string;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.data = this.dataService.getData();
  }
}

Question:
Explain the concept of Angular modules and their role in organizing and structuring an Angular application.

Answer:
Angular modules are a way to organize and structure an Angular application into logical and reusable units. Modules encapsulate related components, directives, services, and other artifacts, providing a boundary for their scope and visibility. They help manage dependencies, define the compilation context, and enable modularization of an application. Angular modules can be used for feature modules that encapsulate specific functionality, as well as for shared modules that provide common functionality and services across the application. Modules are defined using the @NgModule decorator and can be imported and exported to establish relationships between modules.

More Angular interview resources

For more guides on improving your knowledge of Angular and acing interviews, we have outlined helpful blog posts below:

1,000 Companies use CoderPad to Screen and Interview Developers

Best interview practices for Angular roles

The Angular interview experience can vary greatly depending on factors such as the particular engineering role and the candidate’s level of expertise. To optimize the efficacy of your Angular interview questions, we recommend adhering to these best practices when interacting with your candidates:

  • Formulate technical questions that pertain to real-life situations within your organization – this method is not only more captivating for the candidate but also more effectively showcases the alignment of their skills with your team.
  • Cultivate a cooperative atmosphere by encouraging the candidate to ask questions throughout the exercise.
  • If you’re evaluating Angular as part of a full-stack role, ensure that candidates are familiar with integrating the front end specificities in the rest of the stack.

Additionally, it is vital to observe standard interview procedures when conducting Angular interviews – tailor the difficulty of interview questions based on the candidate’s development skill level, offer prompt feedback about their position in the hiring process, and set aside ample time for candidates to ask questions about the assessment or the experience of collaborating with you and your team.

]]>
Backend https://coderpad.io/interview-questions/backend-interview-questions/ Thu, 16 Nov 2023 14:52:51 +0000 https://coderpad.io/?post_type=interview-questions&p=36441 Backend developers, renowned for their in-depth mastery of server-side technologies, stand as vital components in nearly all application development teams.

According to the CoderPad 2024 Developer survey, backend developers are the the second-most in-demand job role technical recruiters are looking to fill.

In the subsequent sections, you will find an array of practical coding tasks and interview questions designed to assess the technical competence of backend developer candidates during evaluations.

Additionally, we have included a selection of endorsed best practices to aid in a reliable assessment of candidates’ backend skills through your interview queries.

Backend example questions

Question 1: Create a CRUD API

The goal of this exercise is to retrieve data from an external source, store it in an appropriate database structure, and create a CRUD RESTful API to interface with the database

Goals

1. Read the data from this graphql endpoint: https://swapi-graphql.netlify.app/.netlify/functions/index with the following query:

query Query {allPlanets{planets{name population terrains climates}}}

(View the shape of the data here.)

2. Store the data from the graphql endpoint into the database and create appropriate models

3. Write RESTful Create, Read, Update, and Delete endpoints to interact with the database

Question 2: Shopping List

This question tests a candidate’s ability to create model(s) for the data structure, insert hard-coded data into a database, modify tables and fields in a database, and write endpoints to interact with data.

Part 1

Create a model for the shopping lists and items located in app/data/data.ts.

Part 2

Store the shopping lists and items located in app/data/data.ts in the database.

Part 3

Modify the database such that a price field can be stored for each item.

Part 4

Multiply the quantity of each item in the shopping lists by two.

Part 5

Create API endpoints to dynamically get a list of shopping lists and items.

Assessing the candidate

The candidate should create models for a shopping list and items. They should then write code to create the tables necessary and insert the data into the tables. Additionally, the candidate can be asked to edit the data in the tables and add additional fields. Finally, endpoints to interact with the data can be created.

lightning bolt

Backend skills to assess

Junior backend interview questions

Question: What’s the difference between a framework and a library?

Answer: A library is a collection of functions/methods/classes that you can call from your own code. You’re in charge of the flow of the application; you decide when to call the library. In contrast, with a framework, the flow is determined by the framework itself, invoking your code at specific points (Inversion of Control). Examples include Flask as a framework and NumPy as a library in Python.

Question: Consider the following Python code snippet. Can you identify what’s wrong with it?

   def add_numbers(a, b):
       return a + b

   result = add_numbers("5", 3)

Answer: The function add_numbers is trying to add a string (“5”) and an integer (3) together, which will raise a TypeError in Python. To fix it, one must ensure that both variables are of the same datatype, by converting the string to an integer.

Question: Explain what a JOIN operation is in SQL.

Answer: A JOIN operation in SQL is used to combine rows from two or more tables based on a related column between them. The most common types of JOINs include INNER JOIN, LEFT JOIN, RIGHT JOIN, and FULL JOIN.

Question: Look at this SQL query and explain if there is any vulnerability:

   query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "';"

Answer: This query is vulnerable to SQL injection. An attacker can provide malicious input in username or password to modify the query and potentially access or harm the database. To prevent this, one should use parameterized queries or prepared statements.

Question: Describe the concept of statelessness in RESTful APIs.

Answer: Statelessness means that each HTTP request from a client to a server must contain all the information needed to understand and process the request. The server should not store any context between requests, ensuring that each request can be understood in isolation.

Question: What’s the output of the following Python function?

   def mystery_function(a, b=[]):
       b.append(a)
       return b

   print(mystery_function(1))
   print(mystery_function(2))

Answer: The output will be:

   [1]
   [1, 2]

The mutable default argument (b=[]) is a common Python gotcha. The list b is created only once when the function is defined, so successive calls to the function reuse the same list.

Question: What is the most Pythonic way to check if a list is empty (considering you are sure the variable to test is a list) ?

Answer:

if my_list:
    # do something when list is not empty
else:
    # do something when list is empty

It is not necessary to compare it with == [] or to call len(my_list). When used in a condition, the empty tuples, lists, dictionaries and sets are treated as False.

Question: Given this simple express.js middleware, what is its purpose?

   app.use((req, res, next) => {
       res.setHeader('Cache-Control', 'no-cache');
       next();
   });

Answer: This middleware sets the Cache-Control header to no-cache for all responses. It instructs the browser (or caching servers) not to cache the content of the response, ensuring that the client always fetches the latest content from the server.

Question: How can you ensure data integrity in a relational database?

Answer: Data integrity can be maintained through the use of constraints such as primary keys, foreign keys, unique constraints, and check constraints. Regular backups, validations, and normalization can also help maintain data integrity.

Question: In the following Node.js code, what issue might arise and how can you fix it?

   const fs = require('fs');

   fs.readFile('file.txt', 'utf8', (err, data) => {
       if (err) throw err;
       console.log(data);
   });

   fs.unlinkSync('file.txt');

Answer: The code is trying to read the content of ‘file.txt’ asynchronously while it’s being deleted synchronously. There’s a race condition; the file might be deleted before the readFile operation completes, leading to an error. To fix it, the deletion should be moved inside the callback of readFile to ensure the read operation completes before the file is deleted.

Intermediate backend developer questions

Question: Describe the differences between horizontal and vertical scaling.

Answer:

  • Horizontal scaling means adding more machines to a system. This is often referred to as scaling out. For example, adding more nodes to a distributed database or adding more servers in a load-balanced environment.
  • Vertical scaling means increasing the resources of an existing machine, such as adding more RAM, CPU, or storage. This is often referred to as scaling up.

Question: Analyze the potential issues with this piece of code handling database connections:

   def get_data(query):
       connection = create_db_connection()
       data = connection.execute(query)
       connection.close()
       return data

Answer: There are a couple potential issues:

  • There’s no error handling. If the connection fails or the query has issues, the code could break.
  • Each time data is fetched, a new database connection is established and closed. This is resource-intensive and can slow down applications, especially if get_data is called frequently.

Question: What are microservices, and what are their benefits over a monolithic architecture?

Answer: Microservices is an architectural style that structures an application as a collection of loosely coupled, independently deployable services. Each service corresponds to a business capability and often has its own database. Benefits include better scalability, flexibility in technology choices for each service, easier maintenance and upgrades, and enhanced fault isolation.

Question: Examine the following endpoint in a Flask application and suggest improvements:

   @app.route('/users/<id>', methods=['GET'])
   def get_user(id):
       user = db.session.query(User).filter_by(id=id).first()
       return jsonify(user)

Answer:

  • The function doesn’t handle the case where the user might not be found, potentially leading to a None object being passed to jsonify. To fix this, Replace the function first by the function one. Exceptions will be raised and handled by the rest of the code if there is not exactly one record found.
  • Directly serializing the ORM object may expose sensitive fields. It’s better to use a serialization method or library to ensure only required fields are exposed.
  • No input validation or type checking for id.

Question: What is the potential bottleneck in the following database query?

   SELECT * FROM orders ORDER BY creation_date DESC LIMIT 10;

Answer: If there is no index on the creation_date column, sorting the table can be resource-intensive, especially as the orders table grows in size. To improve this, an index on the creation_date column should be considered.

Question: Explain the concept of eventual consistency.

Answer: Eventual consistency refers to a system state where all replicas of data might not be immediately consistent but will become consistent over time. It is a relaxation of the strong consistency model to achieve higher availability and scalability in distributed systems.

Question: Consider the following asynchronous Node.js function. What’s wrong with it?

   async function fetchData() {
       let data = await someAsyncFunction();
       if (!data) {
           throw new Error("Data not found");
       }
       return processData(data);
   }

Answer: The function lacks error handling for potential failures of someAsyncFunction(). A try-catch block should be added around the await statement to handle any errors that might arise from that asynchronous call.

Question: Look at this Redis caching function and identify any issues:

   def cache_data(key, value):
       redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
       redis_client.set(key, value)

Answer: The function initializes a new Redis connection each time it’s called. This can be inefficient, especially if caching operations are frequent. It’s better to maintain a persistent Redis connection or use a connection pool.

Question: Analyze this Django ORM query for optimization:

   books = Book.objects.all()
   for book in books:
       print(book.author.name)

Answer: This code suffers from the “N+1 queries problem”. For each book, a separate query is made to fetch the author’s name, leading to N+1 queries in total (1 to fetch all books + N to fetch the authors). The optimized version would use select_related:

   books = Book.objects.select_related('author').all()
   for book in books:
       print(book.author.name)

Question: How does a message queue like RabbitMQ or Kafka improve system architecture?

Answer: Message queues decouple components in a system, allowing for asynchronous processing. This means that a component can send a message to the queue without waiting for it to be processed. This has several benefits:

  • Improved performance and responsiveness, as components don’t block waiting for tasks to complete.
  • Enhanced system reliability, as the message queue can act as a buffer during high traffic or if a component fails and needs to restart.
  • Better scalability, as workers can be easily added or removed based on the workload.

Senior backend developer questions

Question: Explain the principles and benefits of the Twelve-Factor App methodology.

Answer: The Twelve-Factor App is a set of best practices for building modern, scalable, maintainable software-as-a-service apps. Some key principles include:

  1. Codebase: One codebase tracked in revision control, with many deploys.
  2. Dependencies: Explicitly declare and isolate dependencies.
  3. Config: Store configuration in the environment.
  4. Backing Services: Treat backing services as attached resources.
  5. Build, Release, Run: Strictly separate build and run stages.
  6. Processes: Execute the app as one or more stateless processes.
  7. Port Binding: Export services via port binding.
  8. Concurrency: Scale out via the process model.
  9. Disposability: Maximize robustness with fast startup and graceful shutdown.
  10. Dev/Prod Parity: Keep development, staging, and production as similar as possible.
  11. Logs: Treat logs as event streams.
  12. Admin Processes: Run admin/management tasks as one-off processes.

Benefits include portability across execution environments, minimized divergence between development and production, and scalability.

Question: Explain the CAP theorem and its implications in distributed systems design.

Answer: The CAP theorem states that it’s impossible for a distributed system to simultaneously provide all three of the following guarantees:

  • Consistency: Every read receives the most recent write.
  • Availability: Every request receives a (non-error) response, without a guarantee that it contains the most recent version.
  • Partition tolerance: The system continues to operate despite arbitrary message loss or failure of part of the system.

In the face of a network partition, a system must choose between consistency and availability. This theorem guides the design and trade-offs of distributed databases and systems.

Question: Examine the following piece of Python code and identify potential issues related to concurrency:

   counter = 0

   def increment_counter():
       global counter
       for _ in range(1000000):
           counter += 1

Answer: If increment_counter is called concurrently from multiple threads or processes, there could be race conditions leading to the counter not being accurately incremented. A synchronization mechanism like locks or semaphores should be employed to protect the critical section.

Question: Given the following SQL query, how can it be optimized for a large dataset?

   SELECT orders.id, products.name 
   FROM orders 
   INNER JOIN products ON orders.product_id = products.id 
   WHERE products.category = 'electronics';

Answer:

  • Ensure there are indices on the columns involved in the JOIN operation (orders.product_id and products.id).
  • Add an index on products.category since it’s used in the WHERE clause.
  • Consider denormalization or materialized views if this is a frequently executed query.
  • Think about reworking the database schema. The field products.category could be changed to a numeric field and the correspondance between num_category and str_category could be stored in an intermediate table.

Question: Analyze this cache invalidation strategy and suggest improvements:

   def update_product(product):
       db.save(product)
       cache.delete(f'product-{product.id}')

Answer: While the function updates a product in the database and invalidates the cache, a race condition could occur where the cache is repopulated with old data before the database update completes. A better strategy might be to use cache versioning or to update the cache with the new data directly after the database update, ensuring fresh data is always available.

Question: How do you handle versioning in RESTful APIs?

Answer: There are multiple strategies:

  • URI Versioning: Include the version in the URI, e.g., /v1/users.
  • Header Versioning: Use custom headers, e.g., Accept-version: v1.
  • Accept Header: Use the Accept header with a versioned media type, e.g., Accept: application/json; Version=2.
  • Query Parameter: Include the version in a query parameter, e.g., /users?version=1.

It’s essential to choose a method that aligns with the organization’s goals, and it’s also vital to provide proper documentation for clients.

Question: Here’s a simplified code to manage tasks in a queue. Identify potential pitfalls:

   tasks = []

   def add_task(task):
       tasks.append(task)

   def process_next_task():
       if tasks:
           task = tasks.pop(0)
           # process task

Answer:

  • The list data structure in Python is not efficient for pop operations from the start of the list. This operation is O(n). Using a data structure like deque from the collections module would be more efficient.
  • If used in a multithreaded environment, race conditions can occur, leading to tasks being processed multiple times or not at all. Proper synchronization or thread-safe collections should be used.

Question: Consider the following Kafka consumer code snippet. What could be problematic about this?

   consumer = KafkaConsumer('my_topic')
   for message in consumer:
       process_message(message)
       consumer.commit()

Answer: The consumer commits after processing each message, which can be inefficient and slow down the message processing rate. Depending on the process_message function, failures might result in message loss if a message is committed but not successfully processed. It might be better to batch the commits or use a commit strategy based on time or message count.

Question: Describe the advantages and challenges of transitioning a monolithic application to a microservices architecture.

Answer:
Advantages:

  • Scalability: Each service can be scaled independently based on its requirements.
  • Flexibility: Different services can use different technologies.
  • Maintainability: Smaller codebases are easier to understand and maintain.
  • Faster Time to Market: Teams can work on different services simultaneously, leading to faster feature releases.

Challenges:

  • Network Complexity: Increased inter-service communication can introduce latency and complexity.
  • Data Consistency: With services having their own databases, ensuring data consistency can be challenging.
  • Operational Overhead: More services mean more things to deploy, monitor, and manage.
  • Service Discovery: Services need to discover and communicate with each other.

More backend interview resources

For more guides on improving your knowledge of software engineer hiring, we have outlined helpful blog posts below:

Additionally, we offer the following backend framework interview questions:

1,000 Companies use CoderPad to Screen and Interview Developers

Best interview practices for backend roles

The backend interview process can often be quite diverse, influenced by various elements including the specific engineering role in focus and the applicant’s level of expertise in backend technologies. To enhance the effectiveness of your backend interview questions, consider adopting the following strategies when interacting with potential recruits:

  • Develop technical queries that reflect actual scenarios encountered in your organization – this approach is not only more engaging for the candidate but also serves as a more accurate gauge of how their skill set aligns with your team’s needs.
  • Encourage a participatory environment by allowing the candidate to pose questions throughout the process.
  • Given that backend candidates may sometimes need to collaborate with front-end teams, it is advantageous for them to have a foundational understanding of the principles that govern user interface and user experience design.

Furthermore, adhering to standard interview protocols remains crucial during backend interview sessions – adjust the complexity of interview questions based on the candidate’s evolving expertise, provide timely feedback regarding their status in the recruitment process, and offer sufficient opportunities for candidates to discuss the evaluation or delve into the nuances of collaborating with your team.

]]>
C https://coderpad.io/interview-questions/c-interview-questions/ Mon, 17 Apr 2023 12:40:07 +0000 https://coderpad.io/?post_type=interview-questions&p=33048 Despite its age — it has been around since the 1970’s — C continues to be a popular first-time programming language and is used in a range of applications of every type, from game development to systems programming and embedded systems.

According to the CoderPad 2024 Developer survey, C is the 10th most in-demand language among technical recruiters and hiring managers.

We have developed practical coding tasks and interview questions tailored to evaluate developers’ C skills during coding interviews. Furthermore, we have compiled a set of best practices to ensure that your interview questions accurately gauge the candidates’ proficiency in C.

C example question

Help us design a parking lot

Hey candidate! Welcome to your interview. Boilerplate is provided. Feel free to change the code as you see fit. To run the code at any time, please hit the run button located in the top left corner.

Goals: Design a parking lot using object-oriented principles

Here are a few methods that you should be able to run:

  • Tell us how many spots are remaining
  • Tell us how many total spots are in the parking lot
  • Tell us when the parking lot is full
  • Tell us when the parking lot is empty
  • Tell us when certain spots are full e.g. when all motorcycle spots are taken
  • Tell us how many spots vans are taking up

Assumptions:

  • The parking lot can hold motorcycles, cars and vans
  • The parking lot has motorcycle spots, car spots and large spots
  • A motorcycle can park in any spot
  • A car can park in a single compact spot, or a regular spot
  • A van can park, but it will take up 3 regular spots
  • These are just a few assumptions. Feel free to ask your interviewer about more assumptions as needed

Junior C interview questions

Question:
What is the purpose of the #include directive in C, and how is it used?

Answer:
The #include directive is used to include header files in a C program. Header files contain declarations of functions, variables, and data types that are used in the program. The #include directive allows us to reuse code from other files by including their header files in our program.

Question:
There is a bug in the following code that is causing a segmentation fault. Can you identify and fix it?

#include <stdio.h>

int main() {
    int arr[5];

    for (int i = 0; i <= 5; i++) {
        arr[i] = i;
    }

    for (int i = 0; i <= 5; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

Answer:
The bug in the code is an out-of-bounds access when assigning values to the arr array. In C, array indices start from 0, so the valid indices for an array of size 5 are from 0 to 4. To fix the code, we need to change the condition in both loops to i < 5 to stay within the bounds of the array.

#include <stdio.h>

int main() {
    int arr[5];

    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }

    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

Question:
What is the difference between the malloc() and calloc() functions in C for dynamic memory allocation?

Answer:
The malloc() function is used to dynamically allocate memory in C. It takes the size of the memory block to allocate as a parameter and returns a pointer to the allocated memory. The malloc() function does not initialize the allocated memory, so the contents of the allocated memory block are undefined.

The calloc() function, on the other hand, is also used for dynamic memory allocation. It takes two parameters: the number of elements to allocate and the size of each element. The calloc() function initializes the allocated memory to zero, unlike malloc().

Question:
The following code is intended to swap the values of two variables. Identify and fix the issue.

#include <stdio.h>

void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 5;
    int y = 10;

    swap(x, y);

    printf("x = %d, y = %d", x, y);

    return 0;
}

Answer:
The issue with the code is that the swap() function is using pass-by-value, so the modifications made to a and b within the function do not affect the original variables x and y in the main() function. To fix this, we need to use pass-by-reference by passing the addresses of x and y to the swap() function using pointers.

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5;


 int y = 10;

    swap(&x, &y);

    printf("x = %d, y = %d", x, y);

    return 0;
}

Question:
What is the purpose of the typedef keyword in C, and how is it used?

Answer:
The typedef keyword in C is used to create an alias or a new name for an existing data type. It allows us to define custom type names that are more meaningful and self-explanatory. The typedef keyword is typically used in conjunction with structure, union, and enum declarations, but it can also be used with basic data types.

For example, we can use typedef to create a new name for int called CustomInt:

typedef int CustomInt;

After this typedef, CustomInt can be used as a synonym for int, and variables of type CustomInt can be declared.

Question:
The following code is intended to print all the even numbers between 1 and 10. Identify and fix the issue.

#include <stdio.h>

int main() {
    for (int i = 1; i <= 10; i++) {
        if (i % 2 == 0)
            printf("%d ", i);
    }

    return 0;
}

Answer:
The issue with the code is that the printf() statement is not enclosed in curly braces, so only the immediately following statement is considered part of the loop. To fix this, we need to add curly braces around the printf() statement to ensure that it is executed for each iteration of the loop.

#include <stdio.h>

int main() {
    for (int i = 1; i <= 10; i++) {
        if (i % 2 == 0) {
            printf("%d ", i);
        }
    }

    return 0;
}

Question:
What is the purpose of the sizeof operator in C, and how is it used?

Answer:
The sizeof operator in C is used to determine the size, in bytes, of a data type or a variable. It can be used to calculate the memory space occupied by a particular data type or to allocate the appropriate amount of memory dynamically.

For example, to determine the size of an integer (int), we can use sizeof(int), and to determine the size of a variable x, we can use sizeof(x).

Question:
Fix the code: The following code is intended to calculate the factorial of a number. Identify and fix the issue.

#include <stdio.h>

int factorial(int n) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
}

int main() {
    int num = 5;
    int result = factorial(num);

    printf("Factorial of %d is %dn", num, result);

    return 0;
}

Answer:
The code has a correct implementation for calculating the factorial of a number. However, there is no base case or handling for negative numbers. To fix this, we can add a check for negative numbers and return an error condition or handle it appropriately.

#include <stdio.h>

int factorial(int n) {
    if (n < 0) {
        printf("Error: Factorial not defined for negative numbersn");
        return -1; // or any other appropriate error value
    }
    else if (n == 0)
        return 1;
    else
        return n

 * factorial(n - 1);
}

int main() {
    int num = 5;
    int result = factorial(num);

    if (result != -1) {
        printf("Factorial of %d is %dn", num, result);
    }

    return 0;
}

Question:
What is the difference between ++i and i++ in C?

Answer:
Both ++i and i++ are increment operators in C. The difference lies in their behavior:

  • ++i is the pre-increment operator. It increments the value of i and then evaluates to the incremented value.
  • i++ is the post-increment operator. It increments the value of i but evaluates to the original value before the increment.

For example, if i is 5, then ++i would evaluate to 6 and i++ would evaluate to 5. The value of i after these operations would be 6 in both cases.

Question:
Fix the code: The following code is intended to print the ASCII values of lowercase letters. Identify and fix the issue.

#include <stdio.h>

int main() {
    char letter = 'a';

    while (letter <= 'z') {
        printf("ASCII value of %c is %dn", letter, letter);
        letter++;
    }

    return 0;
}

Answer:
The issue with the code is that the format specifier %d is used to print the ASCII value of the letter variable. However, the %c format specifier should be used to print the actual character. To fix this, we need to change the format specifier to %c in the printf() statement.

#include <stdio.h>

int main() {
    char letter = 'a';

    while (letter <= 'z') {
        printf("ASCII value of %c is %cn", letter, letter);
        letter++;
    }

    return 0;
}

These questions cover the basics of C and should provide a good starting point for a junior C developer.

Intermediate C interview questions

Question:
What is a function pointer in C? Provide an example of how it can be used.


Answer:
A function pointer is a variable that stores the address of a function in C. It allows us to call a function indirectly by using the function pointer. Here’s an example:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    int (*funcPtr)(int, int) = add; // Declare and initialize a function pointer

    int result = funcPtr(2, 3); // Call the function using the function pointer

    printf("Result: %dn", result);

    return 0;
}


In this example, we declare a function pointer funcPtr that points to the add function. We can then use the function pointer to call the add function indirectly by dereferencing and invoking the function pointer.

Question:
The following code is intended to swap the values of two variables using pointers. Identify and fix the issue.

#include <stdio.h>

void swap(int* a, int* b) {
    int* temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 5;
    int y = 10;

    swap(&x, &y);

    printf("x = %d, y = %dn", x, y);

    return 0;
}


Answer:
The issue with the code is that the `swap()` function is using pointers, but the pointers `a` and `b` are being reassigned to each other instead of swapping the values they point to. To fix this, we need to dereference the pointers and swap the values they point to.

#include <stdio.h>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5;
    int y = 10;

    swap(&x, &y);

    printf("x = %d, y = %dn", x, y);

    return 0;
}


Question:
Explain the concept of dynamic memory allocation in C. How is it different from static memory allocation?

Answer:
Dynamic memory allocation in C allows us to allocate memory at runtime, rather than at compile-time like static memory allocation. It enables us to allocate memory dynamically based on the program’s needs.

In C, dynamic memory allocation is done using the malloc(), calloc(), and realloc() functions from the <stdlib.h> library. These functions allow us to allocate and deallocate memory as needed, using the heap.

Static memory allocation, on the other hand, occurs at compile-time and involves allocating memory for variables and arrays using keywords like int, char, or float within the program. The allocated memory remains fixed during the execution of the program.

Question:
The following code is intended to read a string of characters from the user. Identify and fix the issue.

#include <stdio.h>

#define MAX_SIZE 20

int main() {
    char str[MAX_SIZE];

    printf("Enter a string: ");
    gets(str);

    printf("Entered string: %sn", str);

    return 0;
}


Answer:
The issue with the code is that the gets() function is used to read a string from the user, which is considered unsafe and deprecated due to the risk of buffer overflow. To fix this, we can use the safer alternative fgets() function.

#include <stdio.h>

#define MAX_SIZE 20

int main() {
    char str[MAX_SIZE];

    printf("Enter a string: ");
    fgets(str, MAX_SIZE, stdin);

    printf("Entered string: %sn", str);

    return 0;
}


Question:
What is the purpose of the volatile keyword in C? Provide an example scenario where it can be useful.

Answer:
The volatile keyword in C is used to indicate that a variable’s value can be changed unexpectedly by external sources, such as hardware or concurrent threads. It tells the compiler not to optimize or cache the variable.

One scenario where volatile can be useful is when working with memory-mapped I/O devices. The values of such devices can change asynchronously, and using the volatile keyword ensures that the compiler always fetches the latest value from memory instead of using a cached value.

#include <stdio.h>

volatile int* deviceAddress = (int*)0x12345678; // Memory-mapped I/O device address

int main() {
    int value = *deviceAddress; // Read the value from the device

    printf("Value: %dn", value);

    return 0;
}


In this example, deviceAddress is declared as a volatile pointer to an int, indicating that the value at that memory location can change unexpectedly. By using the volatile keyword, we ensure that the latest value is always read from the device.

Question:
The following code is intended to find the length of a string. Identify and fix the issue.

#include <stdio.h>

int stringLength(char str[]) {
    int length = 0;

    while (str[length] != "")
        length++;

    return length;
}

int main() {
    char str[] = "Hello, World!";
    int length = stringLength(str);
    printf("Length: %d\n", length);
    return 0;
}


Answer:

Strings in C are mainly arrays of characters, with the null character '\0' to indicate where the string ends.

The function stringLength tries to compare a single character (str[length]) with the value "". This value is an empty string: an array of character whose first element is the null character.

Comparing a single character with an array of characters may end with unpredictable results. The correct way is to compare two single characters, one of them is the null character : '\0'

#include <stdio.h>

int stringLength(char str[]) {
    int length = 0;

    while (str[length] != '\0')
        length++;

    return length;
}

int main() {
    char str[] = "Hello, World!";
    int length = stringLength(str);
    printf("Length: %d\n", length);
    return 0;
}


Question:
Explain the concept of recursion in C. Provide an example of a recursive function.

Answer:
Recursion is a programming technique where a function calls itself to solve a problem by breaking it down into smaller subproblems. In C, a recursive function consists of a base case and a recursive case.

Here’s an example of a recursive function to calculate the factorial of a number:

#include <stdio.h>

int factorial(int n) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
}

int main() {
    int num = 5;
    int result = factorial(num);

    printf("Factorial of %d is %dn", num, result);

    return 0;
}


In this example, the factorial() function calls itself with a smaller value (n - 1) until it reaches the base case (n == 0). The function calculates the factorial by multiplying n with the factorial of n - 1.

Question:
Fix the code: The following code is intended to reverse an integer array. Identify and fix the issue.

#include <stdio.h>

#define SIZE 6

void reverseArray(int arr[], int size) {
    int temp;
    for (int i = 0; i <= size / 2; i++) {
        temp = arr[i];
        arr[i] = arr[size - i];
        arr[size - i] = temp;
    }
}

int main() {
    int arr[SIZE] = {1, 2, 3, 4, 5, 6};

    reverseArray(arr, SIZE);

    printf("Reversed array: ");
    for (int i = 0; i < SIZE; i++)
        printf("%d ", arr[i]);

    printf("\n");
    return 0;
}

Answer:
The issue with the code is that the loop in the reverseArray() function swaps elements incorrectly. The loop should iterate until i < size / 2, not i <= size / 2. Additionally, when swapping the elements, the index for the second element should be size - i - 1, not size - i. These fixes ensure that the swapping is done correctly.

#include <stdio.h>

#define SIZE 6

void reverseArray(int arr[], int size) {
    int temp;
    for (int i = 0; i < size / 2; i++) {
        temp = arr[i];
        arr[i] = arr[size - i - 1];
        arr[size - i - 1] = temp;
    }
}

int main() {
    int arr[SIZE] = {1, 2, 3, 4, 5, 6};

    reverseArray(arr, SIZE);

    printf("Reversed array: ");
    for (int i = 0; i < SIZE; i++)
        printf("%d ", arr[i]);

    printf("\n");
    return 0;
}

Question:
Explain the concept of pointers in C. Provide an example of how pointers can be used to pass values by reference.

Answer:
Pointers in C are variables that store memory addresses. They allow us to indirectly access and manipulate data in memory. Pointers are useful for tasks like dynamic memory allocation, passing values by reference, and working with complex data structures.

Here’s an example of using pointers to pass values by reference:

#include <stdio.h>

void increment(int* num) {
    (*num)++;
}

int main() {
    int value = 5;

    printf("Before increment: %dn", value);

    increment(&value);

    printf("After increment: %dn", value);

    return 0;
}


In this example, the increment() function takes a pointer to an integer as a parameter. Inside the function, the value at the memory address pointed to by num is incremented by dereferencing the pointer. By passing the address of value using the & operator, we can modify the original value directly.

Question:
Fix the code: The following code is intended to check if a number is prime. Identify and fix the issue.

#include <stdio.h>
#include <stdbool.h>

bool isPrime(int num) {
    if (num < 2)
        return false;

    for

 (int i = 2; i < num; i++) {
        if (num % i == 0)
            return false;
    }

    return true;
}

int main() {
    int number = 17;

    if (isPrime(number))
        printf("%d is prime.n", number);
    else
        printf("%d is not prime.n", number);

    return 0;
}


Answer:
The issue with the code is in the for loop condition inside the isPrime() function. The loop should iterate until i <= num / 2, not i < num. Additionally, there is an optimization we can make to stop the loop earlier by checking until the square root of num.

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

bool isPrime(int num) {
    if (num < 2)
        return false;

    for (int i = 2; i <= sqrt(num); i++) {
        if (num % i == 0)
            return false;
    }

    return true;
}

int main() {
    int number = 17;

    if (isPrime(number))
        printf("%d is prime.n", number);
    else
        printf("%d is not prime.n", number);

    return 0;
}


In this corrected code, the isPrime() function uses the sqrt() function from the <math.h> library to limit the loop condition, improving the efficiency of prime number checking.

Senior C interview questions

Question:
The following code is intended to swap two integers using bitwise XOR. Identify and fix the issue.

#include <stdio.h>

void swap(int a, int b) {
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
}

int main() {
    int x = 10, y = 20;

    printf("Before swap: x = %d, y = %dn", x, y);

    swap(x, y);

    printf("After swap: x = %d, y = %dn", x, y);

    return 0;
}

Answer:
The issue with the code is that the swap() function is swapping the values of the local variables a and b, not the original variables x and y. To fix this, we need to pass the addresses of x and y as pointers and dereference them in the swap() function.

#include <stdio.h>

void swap(int* a, int* b) {
    *a = *a ^ *b;
    *b = *a ^ *b;
    *a = *a ^ *b;
}

int main() {
    int x = 10, y = 20;

    printf("Before swap: x = %d, y = %dn", x, y);

    swap(&x, &y);

    printf("After swap: x = %d, y = %dn", x, y);

    return 0;
}

In this corrected code, the swap() function takes pointers to a and b as parameters. By dereferencing the pointers using the * operator, we can modify the values of x and y directly.

Question:
Explain the concept of dynamic memory allocation in C. Provide an example of dynamically allocating memory for an integer array.

Answer:
Dynamic memory allocation in C allows programs to allocate memory at runtime as opposed to compile time. It is useful when the size of data structures or arrays is unknown or needs to be determined dynamically.

Here’s an example of dynamically allocating memory for an integer array:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int size;
    printf("Enter the size of the array: ");
    scanf("%d", &size);

    int* arr = (int*)malloc(size * sizeof(int));

    if (arr == NULL) {
        printf("Memory allocation failed.n");
        return 1;
    }

    for (int i = 0; i < size; i++) {
        printf("Enter element %d: ", i);
        scanf("%d", &arr[i]);
    }

    printf("Array elements: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    free(arr);

    return 0;
}

In this example, the program asks the user to enter the size of the array. It then dynamically allocates memory using malloc() and assigns the starting address of the allocated memory to the pointer variable arr. The user is prompted to enter the elements of the array, which are stored in the dynamically allocated memory. Finally, the memory is freed using free() to prevent memory leaks.

Question:
The following code is intended to read a binary file and print its contents as hexadecimal. Identify and fix the issue.

#include <stdio.h>

int main() {
    FILE* file = fopen

("data.bin", "rb");

    if (file == NULL) {
        printf("Failed to open the file.n");
        return 1;
    }

    unsigned char buffer[16];
    int bytesRead;

    while ((bytesRead = fread(buffer, sizeof(char), 16, file)) != 0) {
        for (int i = 0; i < bytesRead; i++) {
            printf("%02X ", buffer[i]);
        }

        printf("n");
    }

    fclose(file);

    return 0;
}

Answer:
The issue with the code is that the file is not closed if an error occurs during the fread() operation. To fix this, we need to add a fclose() statement inside the if block where the file is checked for NULL.

#include <stdio.h>

int main() {
    FILE* file = fopen("data.bin", "rb");

    if (file == NULL) {
        printf("Failed to open the file.n");
        return 1;
    }

    unsigned char buffer[16];
    int bytesRead;

    while ((bytesRead = fread(buffer, sizeof(char), 16, file)) != 0) {
        for (int i = 0; i < bytesRead; i++) {
            printf("%02X ", buffer[i]);
        }

        printf("n");
    }

    fclose(file);

    return 0;
}

In this corrected code, the fclose() function is called before the program exits in both the normal execution path and the error path, ensuring that the file is closed in all cases.

Question:
Explain the concept of a linked list in C. Provide an example of creating a linked list and traversing its elements.

Answer:
A linked list is a data structure that consists of nodes where each node contains a value and a pointer to the next node. The last node points to NULL, indicating the end of the list. Linked lists provide dynamic memory allocation, efficient insertions and deletions, and flexible size.

Here’s an example of creating a linked list and traversing its elements:

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node* next;
} Node;

void printList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("n");
}

int main() {
    Node* head = NULL;
    Node* second = NULL;
    Node* third = NULL;

    head = (Node*)malloc(sizeof(Node));
    second = (Node*)malloc(sizeof(Node));
    third = (Node*)malloc(sizeof(Node));

    head->data = 1;
    head->next = second;

    second->data = 2;
    second->next = third;

    third->data = 3;
    third->next = NULL;

    printList(head);

    free(head);
    free(second);
    free(third);

    return 0;
}

In this example, we define a Node struct that contains an integer data field and a pointer to the next node. We create three nodes dynamically using malloc() and assign values to their data fields. We link the nodes together by setting the next pointers accordingly. Finally, we pass the head of the linked list to the printList() function, which traverses the list and prints the values. We must remember to free the dynamically allocated memory using free() to avoid memory leaks.

Question:
The following code is intended to sort an array of integers in ascending order using the bubble sort algorithm. Identify and fix the issue.

#include <stdio.h>

void bubbleSort(int arr[], int size) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {5, 2, 8, 1, 6};
    int size = sizeof(arr) / sizeof(arr[0]);

    printf("Before sorting: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    bubbleSort(arr, size);

    printf("nAfter sorting: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

Answer:
The issue with the code is in the inner loop condition of the bubbleSort() function. The loop should iterate until j < size - i - 1, not j < size - i. This is because we are accessing arr[j + 1] in the loop body, so we need to ensure that j + 1 is a valid index.

#include <stdio.h>

void bubbleSort(int arr[], int size) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {5, 2, 8, 1, 6};
    int size = sizeof(arr) / sizeof(arr[0]);

    printf("Before sorting: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    bubbleSort(arr, size);

    printf("nAfter sorting: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

In this corrected code, the inner loop condition is fixed to j < size - i - 1, ensuring that we don’t access elements beyond the array boundaries.

Question:
Explain the concept of dynamic memory allocation in C. Provide an example of allocating memory for an array dynamically and freeing it.

Answer:
Dynamic memory allocation in C allows for the allocation and deallocation of memory during runtime. The functions malloc(), calloc(), and realloc() are used for dynamic memory allocation, and free() is used to release the allocated memory.

Here’s an example of dynamically allocating memory for an array:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int size;
    printf("Enter the size of the array: ");
    scanf("%d", &size);

    int* arr = (int*)malloc(size * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed.n");
        return 1;
    }

    for (int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }

    printf("Array elements: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("n");

    free(arr);

    return 0;
}

In this example, the user enters the size of the array. The malloc() function allocates memory dynamically based on the size provided by the user. We check if the memory allocation is successful before proceeding. We then assign values to the array elements and print them. Finally, we free the allocated memory using free().

Question:
What are function pointers in C? Provide an example demonstrating the use of function pointers.

Answer:
Function pointers in C are variables that store addresses of functions. They allow functions to be treated as data and passed as arguments to other functions or stored in data structures.

Here’s an example demonstrating the use of function pointers:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
    int (*operation)(int, int);  // Declare a function pointer

    operation = add;  // Assign the address of the add() function

    int result = operation(5, 3);  // Call the function through the pointer

    printf("Result: %dn", result);

    operation = subtract;  // Assign the address of the subtract() function

    result = operation(5, 3);  // Call the function through the pointer

    printf("Result: %dn", result);

    return 0;
}

In this example, we declare a function pointer operation that can point to functions accepting two integers and returning an integer. We assign the address of the add() function to the pointer and call the function through the pointer. We then reassign the pointer to the subtract() function and call it again. This allows us to switch between different functions using a single function pointer.

Question:
Explain the concept of a binary search tree (BST) and its operations. Provide an example of inserting nodes into a BST and traversing it.

Answer:
A binary search tree (BST) is a binary tree data structure where each node has a key greater than all keys in its left subtree and less than all keys in its right subtree. It allows efficient searching, insertion, and deletion operations.

Here’s an example of inserting nodes into a BST and traversing it in-order:

#include

 <stdio.h>
#include <stdlib.h>

struct Node {
    int key;
    struct Node* left;
    struct Node* right;
};

struct Node* createNode(int key) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->key = key;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}

struct Node* insert(struct Node* root, int key) {
    if (root == NULL) {
        return createNode(key);
    }

    if (key < root->key) {
        root->left = insert(root->left, key);
    }
    else if (key > root->key) {
        root->right = insert(root->right, key);
    }

    return root;
}

void inorderTraversal(struct Node* root) {
    if (root != NULL) {
        inorderTraversal(root->left);
        printf("%d ", root->key);
        inorderTraversal(root->right);
    }
}

int main() {
    struct Node* root = NULL;

    root = insert(root, 50);
    insert(root, 30);
    insert(root, 20);
    insert(root, 40);
    insert(root, 70);
    insert(root, 60);
    insert(root, 80);

    printf("Inorder traversal: ");
    inorderTraversal(root);
    printf("n");

    return 0;
}

In this example, we define a structure Node representing a node in the BST. The createNode() function creates a new node with the given key. The insert() function inserts a new node into the BST based on the key value. The inorderTraversal() function performs an in-order traversal of the BST and prints the keys in ascending order. We create a BST by inserting nodes with keys 50, 30, 20, 40, 70, 60, and 80. The output of the program will be:

Inorder traversal: 20 30 40 50 60 70 80

Question:
Explain the concept of multithreading in C. Provide an example of creating and synchronizing multiple threads.

Answer:
Multithreading in C allows a program to have multiple threads of execution running concurrently. Each thread represents a separate flow of control within the same program, allowing for parallel execution.

Here’s an example of creating and synchronizing multiple threads:

#include <stdio.h>
#include <pthread.h>

#define NUM_THREADS 5

void* threadFunc(void* arg) {
    int threadID = *((int*)arg);
    printf("Thread %d: Hello, world!n", threadID);
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int threadIDs[NUM_THREADS];

    for (int i = 0; i < NUM_THREADS; i++) {
        threadIDs[i] = i + 1;
        int result = pthread_create(&threads[i], NULL, threadFunc, &threadIDs[i]);
        if (result != 0) {
            printf("Failed to create thread %dn", i + 1);
            return 1;
        }
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    printf("All threads completed.n");

    return 0;
}

In this example, we define a thread function threadFunc() that takes an argument representing the thread ID. Each thread prints a greeting message with its ID. We create an array of thread IDs and use a loop to create multiple threads. The

pthread_create() function creates a new thread, and we pass the thread ID as an argument. The pthread_join() function is used to wait for all threads to complete before proceeding. The output of the program may vary, but it will resemble:

Thread 1: Hello, world!
Thread 2: Hello, world!
Thread 3: Hello, world!
Thread 4: Hello, world!
Thread 5: Hello, world!
All threads completed.

Question:
Explain the concept of a linked list in C. Provide an example of creating a linked list, inserting elements, and traversing it.

Answer:
A linked list in C is a linear data structure where each element (node) contains a value and a pointer to the next node. It allows for efficient insertion and deletion operations, but random access is slower compared to arrays.

Here’s an example of creating a linked list, inserting elements, and traversing it:

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node* next;
};

void insert(struct Node** head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = NULL;

    if (*head == NULL) {
        *head = newNode;
    } else {
        struct Node* temp = *head;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = newNode;
    }
}

void traverse(struct Node* head) {
    struct Node* temp = head;
    while (temp != NULL) {
        printf("%d ", temp->data);
        temp = temp->next;
    }
    printf("n");
}

int main() {
    struct Node* head = NULL;

    insert(&head, 10);
    insert(&head, 20);
    insert(&head, 30);

    traverse(head);

    return 0;
}

In this example, we define a structure Node representing a node in the linked list. The insert() function inserts a new node at the end of the list. If the list is empty, it assigns the new node as the head. Otherwise, it traverses to the end of the list and appends the new node. The traverse() function prints the data in each node by traversing the list. We create a linked list by inserting elements with values 10, 20, and 30. The output of the program will be:

10 20 30

These questions cover various advanced topics in the C language and their applications. They aim to assess the knowledge and experience of a senior C developer.

More C interview resources

For more guides on improving your knowledge of C and acing interviews, we have outlined helpful blog posts below:

1,000 Companies use CoderPad to Screen and Interview Developers

Best interview practices for C roles

To carry out successful C interviews, it is vital to consider various factors such as the candidate’s background and the specific engineering role. To ensure a fruitful interview experience, we recommend adopting the following best practices:

  • Develop technical questions that reflect real-world business scenarios within your organization. This approach will effectively engage the candidate and assist in evaluating their compatibility with your team.
  • Cultivate a cooperative environment by encouraging candidates to ask questions throughout the interview.
  • As C is a low-level programming language, make sure your candidates have a grasp of low-level programming concepts like registers, memory addressing, and bitwise operations.

Moreover, adhering to established interview procedures is essential when conducting C interviews. This encompasses adjusting the complexity of the questions to match the candidates’ skills, promptly informing them about the status of their application, and providing them with the opportunity to ask about the evaluation process and collaboration with you and your team.

]]>
C# https://coderpad.io/interview-questions/csharp-interview-questions/ Tue, 11 Apr 2023 13:59:20 +0000 https://coderpad.io/?post_type=interview-questions&p=32745 Known primarily for its use in a variety of Microsoft platforms and products, C# is also popular because of its gentle learning curve, strong developer community, and utilization by the Unity game engine.

According to the CoderPad 2024 Developer survey, C# is the 4th most in-demand language among technical recruiters and hiring managers.

We have created practical coding exercises and interview questions below to assess developers’ C# proficiency during coding interviews.

Furthermore, we have established a list of top practices to guarantee that your interview questions precisely measure the candidates’ C# expertise.

C# example question

Help us design a parking lot

Hey candidate! Welcome to your interview. Boilerplate is provided. Feel free to change the code as you see fit. To run the code at any time, please hit the run button located in the top left corner.

Goals: Design a parking lot using object-oriented principles

Here are a few methods that you should be able to run:

  • Tell us how many spots are remaining
  • Tell us how many total spots are in the parking lot
  • Tell us when the parking lot is full
  • Tell us when the parking lot is empty
  • Tell us when certain spots are full e.g. when all motorcycle spots are taken
  • Tell us how many spots vans are taking up

Assumptions:

  • The parking lot can hold motorcycles, cars and vans
  • The parking lot has motorcycle spots, car spots and large spots
  • A motorcycle can park in any spot
  • A car can park in a single compact spot, or a regular spot
  • A van can park, but it will take up 3 regular spots
  • These are just a few assumptions. Feel free to ask your interviewer about more assumptions as needed

Junior C# interview questions

Question: What is the difference between a value type and a reference type in C#? Can you provide an example of each?
Answer: Value types store their value directly in memory, while reference types store a reference to the object in memory. An example of a value type is an integer (int), while an example of a reference type is a string.

Question: What is the purpose of the using statement in C#? Can you provide an example?
Answer: The using statement is used to ensure that an object is disposed of properly, even if an exception is thrown. An example would be using a FileStream object to read from a file:

using (FileStream fileStream = new FileStream("file.txt", FileMode.Open))
{
    // Code to read from file here
}

Question: You are given the following code. Can you identify and fix the issue?

int x = 5;
int y = 2;
int result = x / y;

Answer: The issue is that the division operator is used with two integers, which will result in integer division. To fix this, one of the operands should be cast to a double or float:

int x = 5;
int y = 2;
double result = (double)x / y;

Question: What is a constructor in C#? How is it used?
Answer:
A constructor is a special method that is used to initialize an object when it is created. It is called automatically when an object is instantiated using the new keyword. An example of a constructor is:

public class MyClass
{
    public int x;

    public MyClass()
    {
        x = 5;
    }
}

MyClass myObject = new MyClass();
Console.WriteLine(myObject.x); // Outputs 5

Question: What is the difference between a private and public access modifier in C#?
Answer:
Private members can only be accessed within the same class, while public members can be accessed from any code that has access to the class. An example of a private member is a private variable:

public class MyClass
{
    private int x = 5;

    public void PrintX()
    {
        Console.WriteLine(x); // Can only be accessed within the class
    }
}

Question: You are given the following code. Can you identify and fix the issue?

int[] myArray = new int[3];
myArray[3] = 5;

Answer: The issue is that the array index is out of bounds. Arrays in C# are zero-indexed, so the highest valid index for an array of length 3 is 2. To fix this, the array should be resized or the index should be changed:

int[] myArray = new int[4]; // Resizing the array
myArray[3] = 5;

or

int[] myArray = new int[3];
myArray[2] = 5; // Changing the index

Question: What is the difference between an abstract class and an interface in C#?
Answer:
An abstract class is a class that cannot be instantiated and can contain both abstract and non-abstract members, while an interface is a contract that defines a set of methods and properties that a class must implement. An example of an abstract class is:

public abstract class MyAbstractClass
{
    public abstract void MyAbstractMethod();

    public void MyNonAbstractMethod()
    {
        // Code here
    }
}

An example of an interface is:

public interface IMyInterface
{
    void MyInterfaceMethod();
}

Question: You are given the following code. Can you identify and fix the issue?

public class MyClass
{
    public void MyMethod()
    {
        Console.WriteLine("My method");
    }
}

MyClass myObject = null;
myObject.MyMethod();

Answer: The issue is that the myObject variable is null and cannot be used to call the MyMethod method. To fix this, the myObject variable must be instantiated:

MyClass myObject = new MyClass();
myObject.MyMethod();

Question: What is the difference between a static and non-static member in C#?
Answer:
A static member belongs to the class itself and not to any instance of the class, while a non-static member belongs to an instance of the class. An example of a static member is a static variable:

public class MyClass
{
    public static int x = 5;

    public void MyMethod()
    {
        Console.WriteLine(x); // Accessing a static variable
    }
}

An example of a non-static member is a non-static method:

public class MyClass
{
    public int x = 5;

    public void MyMethod()
    {
        Console.WriteLine(x); // Accessing a non-static variable
    }
}

Question: What is the purpose of a try-catch block in C#? Can you provide an example?
Answer:
A try-catch block is used to handle exceptions that may occur during the execution of a block of code. The try block contains the code that may throw an exception, and the catch block contains the code that handles the exception. An example of a try-catch block is:

try
{
    int x = 5;
    int y = 0;
    int result = x / y; // This will throw a DivideByZeroException
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Cannot divide by zero");
}

Intermediate C# interview questions

Question: The following code snippet throws a NullReferenceException. Can you fix it and explain what caused the exception?

string name = null;
int length = name.Length;

Answer: The exception is thrown because name is null, and you cannot call the Length property on a null reference. To fix the code, you can check whether name is null before accessing its Length property, like this:

string name = null;
int length = name?.Length ?? 0;

This code uses the null-conditional operator ?. to guard against null references, and the null-coalescing operator ?? to provide a default value of 0 if name is null.

Question: What is the difference between a value type and a reference type in C#?
Answer:
A value type is a type that holds its value in its own memory space, while a reference type is a type that holds a reference to its value in a separate memory space. Value types are stored on the stack, while reference types are stored on the heap.

Question: The following code snippet throws a System.InvalidCastException. Can you fix it and explain what caused the exception?

object obj = "Hello";
int length = (int)obj;

Answer: The exception is thrown because you are trying to cast a string object to an integer, which is not a valid cast. To fix the code, you can use the Convert.ToInt32 method to convert the string to an integer, like this:

object obj = "Hello";
int length = Convert.ToInt32(obj);

This code uses the Convert.ToInt32 method to perform the conversion, which can handle various types of inputs and return a default value if the input is null or cannot be converted.

Question: What is the difference between a delegate and an event in C#?
Answer:
A delegate is a type that represents a reference to a method, while an event is a language construct that allows you to publish and subscribe to notifications of certain actions or changes in the state of an object. An event is based on a delegate type that specifies the signature of the methods that can handle the event.

Question:The following code snippet uses a foreach loop to iterate over a List. Can you suggest an alternative way to iterate over the list?

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
foreach (int number in numbers)
{
    Console.WriteLine(number);
}

Answer: An alternative way to iterate over the list is to use a for loop with an index variable, like this:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
for (int i = 0; i < numbers.Count; i++)
{
    int number = numbers[i];
    Console.WriteLine(number);
}

This code uses the Count property of the list to determine the number of elements, and the indexer [] to access each element by its index.

Question: What is a lambda expression, and how is it different from an anonymous method in C#?
Answer:
A lambda expression is an anonymous function that can be used to create delegates, expression trees, or function objects. Lambda expressions are often used in LINQ queries or to define event handlers. An anonymous method is a method without a name that can be used to create delegates.

Question: The following code snippet declares a nullable integer variable and assigns it a null value. Can you suggest two ways to check whether the variable is null and retrieve its value if it is not null?

int? number = null;

Answer: Two ways to check whether the variable is null and retrieve its value if it is not null are:

  • Using the null-conditional operator ?. and the null-coalescing operator ??:
  int value1 = number?.Value ?? 0;

This code checks whether number is null using the null-conditional operator ?. and returns its value using the Value property if it is not null. If number is null, the null-coalescing operator ?? provides a default value of 0.

  • Using the GetValueOrDefault method:
  int value2 = number.GetValueOrDefault();

This code returns the value of number if it is not null, or the default value of 0 if it is null. The GetValueOrDefault method is a shorthand for the null-coalescing operator ?? with a default value of the type’s default value.

Question: What is a generic type in C#, and how is it different from a non-generic type?
Answer:
A generic type is a type that is parameterized by one or more type parameters, which allow the same type to be used with different types of arguments. Generic types are often used to create reusable code that can work with any type, such as collections or algorithms. Non-generic types are types that do not have any type parameters and can only work with a specific type or set of types.

Question: The following code snippet defines a class with a public property and a private field. Can you suggest a way to make the property read-only without changing its accessibility modifier?

class Person
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

Answer: One way to make the property read-only without changing its accessibility modifier is to remove the setter and initialize the private field in the constructor, like this:

class Person
{
    private readonly string name;

    public Person(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

This code uses the readonly keyword to make the private field read-only, which means it can only be initialized in the constructor. The property is still publicly accessible, but the setter has been removed, which makes it read-only.

Question: What is the difference between an abstract class and an interface in C#?
Answer:
An abstract class is a class that cannot be instantiated directly and can contain abstract methods, which are methods without an implementation that must be implemented by derived classes. An abstract class can also contain non-abstract methods with an implementation, fields, properties, and events. An interface is a type that defines a set of members that must be implemented by any class that implements the interface. An interface cannot contain fields or implementations, only method signatures, properties, and events. A class can inherit from multiple interfaces, but only from one abstract class.

Senior C# interview questions

Question: Fix the following C# code:

class Program
{
    static void Main(string[] args)
    {
        List<string> fruits = new List<string>();
        fruits.Add("Apple");
        fruits.Add("Banana");
        fruits.Add("Orange");
        Console.WriteLine(fruits[3]);
    }
}

Answer: The code above attempts to access an index that is out of bounds in the fruits list, since the list only has three elements. To fix this, we can change the last line to Console.WriteLine(fruits[2]); to print the last element of the list.

Question: What is a monad, and how can it be used in C#?

Answer: A monad is a design pattern used in functional programming that provides a way to encapsulate a computation with a specific set of rules, such as error handling, state management, or non-determinism. In C#, monads can be implemented using the LINQ query syntax, which provides a way to compose operations on collections, such as filtering, mapping, and grouping, in a declarative and composable way.

Question: Fix the following C# code:

class Calculator
{
    public static void Main()
    {
        int result = Add(2, 3);
        Console.WriteLine(result);
    }

    public static int Add(int x, int y)
    {
        return x - y;
    }
}

Answer: The Add method in the code above returns the subtraction of x and y, instead of their sum. To fix this, we can change the return statement to return x + y;.

Question: Fix the following C# code:

class Program
{
    static void Main(string[] args)
    {
        int[] numbers = { 1, 2, 3 };
        Console.WriteLine(Sum(numbers));
    }

    public static int Sum(params int[] numbers)
    {
        int sum = 0;
        foreach (int number in numbers) {
            sum += numbers;
        }
        return sum;
    }
}

Answer: The Sum method in the code above attempts to add the numbers array to itself in the loop, instead of adding the number variable to the sum variable. To fix this, we can change the sum += numbers; statement to sum += number;.

Question: Can you explain the concept of lazy evaluation, and how it can be used in C#?

Answer: Lazy evaluation is a programming technique that defers the evaluation of an expression until it is needed, usually for performance or memory reasons. In C#, lazy evaluation can be implemented using the Lazy<T> class, which provides a way to create an object that is initialized on first use. This can be useful for delaying expensive or time-consuming operations, avoiding unnecessary calculations, and improving startup time and memory usage. Lazy evaluation can also be used in conjunction with functional programming techniques, such as currying and partial application, to create composable and reusable code.

Question: Fix the following C# code:

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, int> ages = new Dictionary<string, int>();
        ages["Alice"] = 25;
        ages["Bob"] = 30;
        ages["Charlie"] = 35;
        Console.WriteLine(ages["David"]);
    }
}

Answer: The code above attempts to access a key that does not exist in the ages dictionary, which results in a KeyNotFoundException. To fix this, we can either add a key-value pair for David to the dictionary, or use the TryGetValue method to check if the key exists before accessing its value, like this:

int age;
if (ages.TryGetValue("David", out age)) {
    Console.WriteLine(age);
} else {
    Console.WriteLine("David is not in the dictionary.");
}

Question: Can you explain what functional programming is, and what are some benefits and drawbacks of using it in C#?

Answer: Functional programming is a programming paradigm that emphasizes the use of functions to solve problems, rather than mutable state and imperative control flow. Some benefits of using functional programming in C# include better code modularity, testability, and parallelism, as well as more concise and expressive code. Some drawbacks of using functional programming in C# include a steeper learning curve, potential performance overhead, and difficulties in working with mutable data structures.

Question: Fix the following C# code:

class Program
{
    static void Main(string[] args)
    {
        string[] names = { "Alice", "Bob", "Charlie" };
        foreach (string name in names) {
            Console.WriteLine(names[0]);
        }
    }
}

Answer: The foreach loop in the code above always prints the first element of the names array, instead of the current element. To fix this, we can change the Console.WriteLine statement to Console.WriteLine(name);.

Question: Can you explain the SOLID principles of object-oriented design, and how they can be applied in C#?

Answer: The SOLID principles are a set of guidelines for designing object-oriented systems that are easy to maintain, extend, and reuse. They stand for:

  • Single Responsibility Principle (SRP): a class should have only one reason to change.
  • Open/Closed Principle (OCP): a class should be open for extension but closed for modification.
  • Liskov Substitution Principle (LSP): a derived class should be substitutable for its base class without changing the correctness of the program.
  • Interface Segregation Principle (ISP): a class should not be forced to depend on methods it does not use.
  • Dependency Inversion Principle (DIP): high-level modules should not depend on low-level modules, but both should depend on abstractions.

In C#, the SOLID principles can be applied using techniques such as dependency injection, inversion of control, and interfaces to decouple modules, reduce dependencies, and increase testability and flexibility.

Question: Fix the following C# code:

class Program
{
    static void Main(string[] args)
    {
        int[] numbers = { 1, 2, 3 };
        Console.WriteLine(Sum(numbers));
    }

    public static int Sum(params int[] numbers)
    {
        int sum = 0;
        foreach (int number in numbers) {
            sum += numbers;
        }
        return sum;
    }
}

Answer: The Sum method in the code above attempts to add the numbers array to itself in the loop, instead of adding the number variable to the sum variable. To fix this, we can change the sum += numbers; statement to sum += number;.

Question: Can you explain what the factory pattern is, and how it can be used in C#?

Answer: The factory pattern is a creational design pattern that provides an interface for creating objects, but allows subclasses to decide which class to instantiate. In C#, the factory pattern can be implemented using a factory class or method that returns an instance of a specific class based on some input parameters or conditions. This can be useful for encapsulating object creation logic, decoupling client code from concrete classes, and providing a way to switch between different implementations without changing the client code.

1,000 Companies use CoderPad to Screen and Interview Developers

Best interview practices for C# roles

To carry out successful C# interviews, it is crucial to take into account various aspects, such as the applicant’s background and the specific engineering role. To guarantee a productive interview experience, we suggest implementing the following best practices:

  • Formulate technical questions that correspond to real-life business situations within your organization. This strategy will effectively engage the applicant and aid in evaluating their suitability for your team.
  • Foster a collaborative atmosphere by encouraging the applicant to ask questions during the interview.
  • If you’re using C# for web development, also make sure to assess candidates for comprehension of ASP.NET Core and its models (MVC, Web API, AJAX, …), as well as knowledge of related web technologies like HTML, CSS and JavaScript.

Furthermore, adhering to standard interview procedures is essential when administering C# interviews. This entails adjusting the difficulty of the questions to align with the applicant’s abilities, offering timely updates on their application progress, and presenting them with the chance to inquire about the assessment process and collaboration with you and your team.

]]>
C++ https://coderpad.io/interview-questions/cpp-interview-questions/ Tue, 11 Apr 2023 13:58:30 +0000 https://coderpad.io/?post_type=interview-questions&p=32743 A feature-rich version of the classic C programming language, C++ is the language-of-choice for game development and the most popular operating systems – including Windows, Mac OS, and Linux.

According to the CoderPad 2024 Developer survey, C++ is the 6th most in-demand language among technical recruiters and hiring managers.

To evaluate the C++ skills of developers during coding interviews, we’ve provided realistic coding exercises and interview questions below. Additionally, we’ve outlined a set of best practices to ensure that your interview questions accurately assess the candidates’ C++ skills.

C++ example question

Anagram creator function

Implement a function which takes a list of words as an argument and finds all anagrams within the list. Anagrams are words which contain the exact same letters, like CREATED/CATERED/REACTED.

Create a vector for each group of 2 or more anagrams, then return the vector of all groups. You can put the results in any order.

Junior C++ developer interview questions

Question: What is the difference between a pointer and a reference in C++? Write a function that takes a reference to an integer and adds 5 to it.

Answer: A pointer is a variable that stores the memory address of another variable, while a reference is an alias for another variable. Here’s an example function:

void addFive(int& num) {
    num += 5;
}

Question: Explain the difference between pass by value, pass by reference, and pass by pointer in C++. Write a function that takes an integer by reference and sets it to zero.

Answer: Pass by value creates a copy of the argument, pass by reference passes a reference to the argument, and pass by pointer passes a pointer to the argument. Here’s an example function:

void setToZero(int& num) {
    num = 0;
}

Question: Fix the following code to properly iterate over the elements of the vector vec:

vector<int> vec = {1, 2, 3, 4, 5};
for (int i = 0; i < vec.length(); i++) {
    cout << vec[i] << endl;
}

The length function should be replaced with size. Here’s an example fix:

vector<int> vec = {1, 2, 3, 4, 5};
for (int i = 0; i < vec.size(); i++) {
    cout << vec[i] << endl;
}

Question: What is the difference between a class and a struct in C++? Write a class called Person that has a private member variable name and a public method getName that returns the name.

Answer: In C++, a class and a struct are almost identical, with the only difference being the default access level (public for struct and private for class). Here’s an example class:

class Person {
private:
    string name;
public:
    string getName() {
        return name;
    }
};

Question: Fix the following code to properly print out the elements of the array arr:

int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.size(); i++) {
    cout << arr[i] << endl;
}

Answer: The arr array does not have a size method in C++. Here’s an example fix:

int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
    cout << arr[i] << endl;
}

Question: Explain the difference between the prefix and postfix versions of the increment and decrement operators in C++. Write a function that takes an integer by reference and increments it using the postfix increment operator.

Answer: The prefix increment and decrement operators increment or decrement the variable and return a reference to the updated variable, while the postfix operators return a copy of the variable before it was updated. Here’s an example function:

void postfixIncrement(int& num) {
    num++;
    num++;
}

Question: What is a reference variable in C++? Write a function that takes a reference to an integer as a parameter and adds 10 to it.

Answer: A reference variable in C++ is an alias for an existing variable. It allows the original variable to be accessed or modified using a different name. Here’s an example function:

void addTen(int& num) {
    num += 10;
}

int main() {
    int x = 5;
    addTen(x);
    cout << x << endl; // Output: 15
    return 0;
}

Question: Fix the following code to properly sort the elements of the vector vec in descending order:

vector<int> vec = {5, 2, 7, 1, 9};
sort(vec.begin(), vec.end());
reverse(vec.begin(), vec.end());
for (int i = 0; i < vec.size(); i++) {
    cout << vec[i] << endl;
}

Answer: The sort function sorts the vector in ascending order, so the reverse function needs to be used to reverse the order. Here’s an example fix:

vector<int> vec = {5, 2, 7, 1, 9};
sort(vec.begin(), vec.end(), greater<int>());
for (int i = 0; i < vec.size(); i++) {
    cout << vec[i] << endl;
}

Question: What is the difference between a stack and a heap in C++ memory management? Write a function that allocates an integer on the heap and returns a pointer to it.

Answer: The stack is used to store local variables and function calls, while the heap is used to store dynamically allocated memory. Here’s an example function:

int* allocateInt() {
    int* num = new int;
    return num;
}

Question: Fix the following code to properly concatenate the two strings “str1” and “str2”:

string str1 = "Hello";
string str2 = "world!";
string str3 = str1 + str2;
cout << str3 << endl;

Answer: The + operator is used to concatenate strings. Here’s an example fix:

string str1 = "Hello";
string str2 = "world!";
string str3 = str1 + " " + str2;
cout << str3 << endl;

Intermediate C++ developer interview questions

Question: What is inheritance in object-oriented programming? Write a class hierarchy for different types of animals.

Answer: Inheritance is a mechanism in object-oriented programming that allows a class to inherit properties and behavior from another class. Here’s an example class hierarchy for different types of animals:

class Animal {
public:
    virtual void speak() = 0;
};

class Mammal : public Animal {
public:
    virtual void giveBirth() = 0;
};

class Dog : public Mammal {
public:
    void speak() override { cout << "Woof!" << endl; }
    void giveBirth() override { cout << "Giving birth to puppies." << endl; }
};

class Bird : public Animal {
public:
    void speak() override { cout << "Chirp!" << endl; }
};

Question: Fix the following code to properly swap the values of the two integer variables x and y:

int x = 5;
int y = 10;
int temp;
temp = x;
x = y;
y = temp;
cout << x << " " << y << endl;

Answer: The temporary variable temp is used to swap the values. Here’s the fixed code:

int x = 5;
int y = 10;
int temp = x;
x = y;
y = temp;
cout << x << " " << y << endl;

Question: What is a template in C++? Write a function template that returns the maximum value in an array of any type.

Answer: A template is a mechanism in C++ that allows for generic programming. It enables writing functions and classes that can work with any type, without having to write separate versions for each type. Here’s an example function template:

template <typename T>
T maxElement(T arr[], int size) {
    T max = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    return max;
}

int main() {
    int arrInt[] = {1, 2, 3, 4, 5};
    double arrDouble[] = {1.1, 2.2, 3.3, 4.4, 5.5};
    cout << maxElement(arrInt, 5) << endl; // Output: 5
    cout << maxElement(arrDouble, 5) << endl; // Output: 5.5
    return 0;
}

Question: What is the difference between a pointer and a reference in C++? Write a function that takes a pointer to an integer and doubles its value.

Answer: A pointer is a variable that stores the memory address of another variable, while a reference is an alias for an existing variable. Here’s an example function that takes a pointer to an integer and doubles its value:

void doubleValue(int* numPtr) {
    *numPtr *= 2;
}

int main() {
    int x = 5;
    int* xPtr = &x;
    doubleValue(xPtr);
    cout << x << endl; // Output: 10
    return 0;
}

Question: What is polymorphism in object-oriented programming? Write an example of polymorphism using function overloading.

Answer: Polymorphism is a feature of object-oriented programming that allows a single interface to be implemented in multiple ways. Function overloading is an example of polymorphism. Here’s an example:

void print(int num) {
    cout << "Printing integer: " << num << endl;
}

void print(double num) {
    cout << "Printing double: " << num << endl;
}

int main() {
    print(5);
    print(3.14);
    return 0;
}

Question: What is a linked list data structure? Write a function that inserts a new node at the end of a linked list.

Answer: A linked list is a data structure that consists of a sequence of nodes, where each node stores a value and a pointer to the next node in the list. Here’s an example function that inserts a new node at the end of a linked list:

struct Node {
    int data;
    Node* next;
};

void insertEnd(Node** head, int value) {
    Node* newNode = new Node;
    newNode->data = value;
    newNode->next = NULL;
    if (*head == NULL) {
        *head = newNode;
    } else {
        Node* current = *head;
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = newNode;
    }
}

int main() {
    Node* head = NULL;
    insertEnd(&head, 1);
    insertEnd(&head, 2);
    insertEnd(&head, 3);
    Node* current = head;
    while (current != NULL) {
        cout << current->data << " ";
        current = current->next;
    }
    return 0;
}

Question: What is a virtual function in C++? Write an example of using a virtual function to implement polymorphism.

Answer: A virtual function is a function that is declared in a base class and can be overridden in a derived class. It enables runtime polymorphism. Here’s an example:

class Shape {
public:
    virtual double area() = 0;
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) {
        radius = r;
    }
    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width;
    double height;
public:
    Rectangle(double w, double h) {
        width = w;
        height = h;
    }
    double area() override {
        return width * height;
    }
};

int main() {
    Shape* shape1 = new Circle(5);
    Shape* shape2 = new Rectangle(3, 4);
    cout << "Area of circle: " << shape1->area() << endl;
    cout << "Area of rectangle: " << shape2->area() << endl;
    delete shape1;
    delete shape2;
    return 0;
}

Question: Fix the following code to properly initialize the string variable “name”:

string name = "Alice";
name[0] = 'a';
cout << name << endl;

Answer: The string class in C++ does not allow direct modification of its characters. Here’s a fixed version that uses the “replace” method instead:

string name = "Alice";
name.replace(0, 1, "a");
cout << name << endl;

Question: What is a smart pointer in C++? Write an example of using a unique_ptr to manage dynamic memory.

Answer: A smart pointer is a class template that is designed to manage dynamic memory in C++. It is called “smart” because it automatically handles the allocation and deallocation of memory, helping to prevent memory leaks and other errors. Here’s an example of using a unique_ptr to manage dynamic memory:

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> ptr(new int(42));
    std::cout << *ptr << std::endl;
    return 0;
}

In this example, we create a unique_ptr object that points to an integer value of 42. The unique_ptr takes ownership of the memory and automatically deallocates it when the object goes out of scope. The dereference operator is used to access the value stored in the memory location pointed to by the unique_ptr.

Question: Fix the following code to properly initialize the elements of the 2D array “matrix”:

#include <iostream>

int main() {
    int matrix[3][3];
    for(int i = 0; i < 3; i++) {
        for(int j = 0; i < 3; j++) {
            matrix[i][j] = i * j;
        }
    }
    return 0;
}

Answer: The code has a small typo in the nested for-loop condition. Instead of checking for the value of j, it accidentally checks for the value of i again. To fix this, we can change the second condition in the nested loop to j < 3. Additionally, we can add a line to print out the contents of the matrix for verification purposes. Here’s the corrected code:

#include <iostream>

int main() {
    int matrix[3][3];
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 3; j++) {
            matrix[i][j] = i * j;
        }
    }

    // Print out the contents of the matrix
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 3; j++) {
            std::cout << matrix[i][j] << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

This code correctly initializes each element of the matrix array to the product of its row and column indices, and then prints out the contents of the matrix for verification.

Senior C++ developer interview questions

Question: What is the Rule of Five in C++? Provide an example of a class that requires the Rule of Five.

Answer: The Rule of Five in C++ states that if a class requires one of the following functions: copy constructor, copy assignment operator, move constructor, move assignment operator, or destructor, then it should implement all of them. An example of a class that requires the Rule of Five is a class that manages dynamic memory. Here is an example of a class that requires the Rule of Five:

class MyString {
public:
    MyString();
    MyString(const char* str);
    MyString(const MyString& other);
    MyString& operator=(const MyString& other);
    MyString(MyString&& other) noexcept;
    MyString& operator=(MyString&& other) noexcept;
    ~MyString();

private:
    char* m_data;
    size_t m_size;
};

This class manages a character array that represents a string. It has a default constructor, a constructor that takes a C-style string as input, a copy constructor, a copy assignment operator, a move constructor, a move assignment operator, and a destructor. All of these functions must be implemented because the class manages dynamic memory.

Question: What is the purpose of the noexcept specifier in C++11? Provide an example of its use.

Answer: The noexcept specifier in C++11 indicates that a function is not expected to throw any exceptions. It is used for optimization purposes, because it allows the compiler to generate more efficient code in certain cases. Here is an example of its use:

class MyClass {
public:
    void myFunction() noexcept;
};

void MyClass::myFunction() noexcept {
    // Function body
}

In this example, myFunction is declared with the noexcept specifier, which tells the compiler that this function will not throw any exceptions. This can help the compiler generate more efficient code for this function.

Question: What is move semantics in C++? Provide an example of its use.

A: Move semantics in C++ is a feature that allows objects to be moved from one location in memory to another, rather than being copied. This can be more efficient for certain types of objects, such as those that manage dynamic memory. Here is an example of move semantics:

class MyVector {
public:
    MyVector(size_t size);
    MyVector(const MyVector& other);
    MyVector(MyVector&& other) noexcept;
    MyVector& operator=(const MyVector& other);
    MyVector& operator=(MyVector&& other) noexcept;
    ~MyVector();

private:
    int* m_data;
    size_t m_size;
};

MyVector::MyVector(MyVector&& other) noexcept
    : m_data(other.m_data), m_size(other.m_size)
{
    other.m_data = nullptr;
    other.m_size = 0;
}

MyVector& MyVector::operator=(MyVector&& other) noexcept {
    if (this != &other) {
        delete[] m_data;
        m_data = other.m_data;
        m_size = other.m_size;
        other.m_data = nullptr;
        other.m_size = 0;
    }
    return *this;
}

In this example, the MyVector class implements move semantics by providing a move constructor and a move assignment operator. When an object is moved, its data is simply transferred to the new object, rather than being copied. This can be more efficient than copying, especially for large objects.

Question: What is a lambda express in C++? Provide an example of its use.

Answer: A lambda expression in C++ is a way to define a small, anonymous function inline. It allows you to write code that is more concise and easier to read, especially when dealing with algorithms that take functions as parameters. Here is an example of its use:

std::vector<int> numbers = { 1, 2, 3, 4, 5 };
int sum = 0;

std::for_each(numbers.begin(), numbers.end(), [&sum](int n) {
    sum += n;
});

std::cout << "Sum of numbers: " << sum << std::endl;

In this example, we use a lambda expression with the std::for_each algorithm to calculate the sum of a vector of integers. The lambda expression takes an integer as input and adds it to a variable called sum, which is captured by reference. The result is printed to the console.

Question: What is RAI in C++? Explain how it can be used to improve memory safety.

Answer: RAI stands for Resource Acquisition Is Initialization, which is a design pattern in C++ that helps ensure proper resource management. The basic idea is to allocate and deallocate resources in the constructor and destructor of a class, respectively. This guarantees that the resource will be properly released, even if an exception is thrown during the lifetime of the object. For example:

class MyFile {
public:
    MyFile(const std::string& filename)
        : m_file(fopen(filename.c_str(), "r"))
    {
        if (!m_file) {
            throw std::runtime_error("Unable to open file");
        }
    }

    ~MyFile() {
        if (m_file) {
            fclose(m_file);
        }
    }

    // Other methods...

private:
    FILE* m_file;
};

In this example, the MyFile class uses RAI to ensure that the file resource is properly released when the object goes out of scope. The file is opened in the constructor, and closed in the destructor, which guarantees that it will be released even if an exception is thrown.

Question: What is the difference between std::shared_ptr and std::unique_ptr in C++? Provide an example of when you would use each.

A: std::shared_ptr and std::unique_ptr are both smart pointers in C++ that help manage dynamic memory. The main difference is that std::shared_ptr uses reference counting to keep track of how many pointers are pointing to the same object, while std::unique_ptr does not allow multiple pointers to the same object. Here is an example of when you would use each:

// Using std::unique_ptr
std::unique_ptr<MyClass> p1(new MyClass());
std::unique_ptr<MyClass> p2 = std::move(p1);

// Using std::shared_ptr
std::shared_ptr<MyClass> p3(new MyClass());
std::shared_ptr<MyClass> p4 = p3;

In this example, we create two instances of MyClass using std::unique_ptr and std::shared_ptr. With std::unique_ptr, we can transfer ownership of the object from p1 to p2 using std::move. This ensures that there is only one pointer to the object at any given time. With std::shared_ptr, we can create multiple pointers to the same object, which is useful when we need to share ownership of the object among multiple parts of our program.

Question: What is a move constructor in C++? When is it invoked?
Answer:
A move constructor is a special type of constructor that is used to move the resources owned by an object into another object of the same type. It is invoked when an object is being constructed using an rvalue (i.e., a temporary object). The move constructor is used to efficiently transfer the resources owned by the temporary object into the new object, rather than making a copy of the resources.

Question: What is perfect forwarding in C++? Provide an example.
Answer:
Perfect forwarding is a technique used to forward arguments to another function or constructor, while preserving their value category (i.e., whether they are an lvalue or an rvalue). This allows the function or constructor being called to take ownership of the arguments, while avoiding unnecessary copies or moves. An example of perfect forwarding in C++ is the std::forward function, which is used to forward arguments to another function or constructor. For example, consider a template function that takes an argument by reference, and needs to forward that argument to another function:

template<typename T>
void some_function(T&& arg) {
    // Forward the argument to another function
    other_function(std::forward<T>(arg));
}

In this example, the std::forward function is used to forward the arg argument to the other_function function, while preserving its value category.

Question: What is the difference between a const pointer and a pointer to a const in C++? Provide an example of when you would use each.
Answer:
A const pointer is a pointer that cannot be modified to point to a different object, while a pointer to a const is a pointer that can be modified to point to a different object, but cannot be used to modify the object it points to.

You would use a const pointer when you want to ensure that the pointer itself is not modified, for example when declaring a function parameter that should not modify the object being pointed to:

void some_function(const int* const ptr);

In this example, the const pointer ptr cannot be modified to point to a different object, and the object it points to cannot be modified.

You would use a pointer to a const when you want to ensure that the object being pointed to is not modified, but the pointer itself can be modified to point to a different object, for example when passing a read-only object to a function:

void some_function(const int* ptr);

In this example, the pointer ptr can be modified to point to a different object, but the object it points to cannot be modified.

Question: What is the difference between the new and malloc functions in C++? When should you use one over the other, and why? Can you provide an example of each?

Answer: The new operator and the malloc() function are both used for dynamic memory allocation in C++, but they work differently and have some important differences.

The new operator is a type-safe way to allocate memory dynamically in C++. It not only allocates memory but also constructs objects in that memory. new returns a pointer to the newly allocated memory, which can be assigned to a pointer variable of the appropriate type. For example:

int* p = new int;

This allocates a single int on the heap and returns a pointer to it, which is stored in the variable p.

The malloc() function, on the other hand, is a C-style function that simply allocates a block of memory of a specified size. It does not call the constructor for any objects that may be stored in that memory block. malloc() returns a void* pointer to the allocated memory, which must be cast to the appropriate type before it can be used. For example:

int* p = (int*)malloc(sizeof(int));

This allocates a single int on the heap and returns a pointer to it, which is stored in the variable p.

One important difference between new and malloc() is that new throws an exception if the allocation fails, whereas malloc() returns a null pointer. This means that you need to check the return value of malloc() to make sure the allocation was successful, but with new, you can use a try-catch block to handle any allocation failures.

Another important difference is that new calls the constructor for any objects stored in the allocated memory, and delete calls the destructor when the memory is deallocated, whereas malloc() and free() do not. This means that if you are working with objects that require initialization and cleanup, you should use new and delete instead of malloc() and free().

In general, it is recommended to use new and delete for dynamic memory allocation in C++, since they are type-safe and handle object initialization and cleanup automatically. However, malloc() and free() can still be useful in certain situations, such as when working with legacy code or when interoperating with C libraries that use malloc() and free().

More C++ interview question resources

1,000 Companies use CoderPad to Screen and Interview Developers

Best interview practices for C++ roles

To conduct successful C++ interviews, it is important to consider several factors, such as the candidate’s experience and the specific engineering position. To ensure an effective interview experience, we recommend incorporating the following best practices:

  • Develop technical questions that relate to real-world business scenarios within your company. This approach will engage the candidate effectively and help you assess their fit for your team.
  • Encourage collaboration by inviting the candidate to ask questions throughout the interview.
  • Errors happen! Make sure candidates can handle C++ exceptions gracefully.

In addition, it is important to follow standard interview practices when conducting C++ interviews. This includes tailoring the complexity of the questions to match the candidate’s skill set, providing prompt updates on their application status, and giving them the opportunity to ask questions about the evaluation process and working with you and your team.

]]>
Clojure https://coderpad.io/interview-questions/clojure-interview-questions/ Tue, 18 Apr 2023 14:58:47 +0000 https://coderpad.io/?post_type=interview-questions&p=33259 Clojure is a Lisp descendent that utilizes a functional programming paradigm and incorporates popular technologies like the Java Virtual Machine (JVM), JavaScript engines, and the .NET runtime. It’s often used for web development, data processing and analysis, and distributed systems.

Like other Lisp dialects, Clojure is homoiconic, meaning that its code is represented as data structures within the language itself. This property allows for powerful metaprogramming capabilities, enabling developers to create macros and domain-specific languages (DSLs) that extend the language in unique and expressive ways.

Our team has crafted pragmatic coding tasks and interview inquiries specifically designed to gauge the Clojure abilities of developers during coding interviews. Moreover, we have assembled a collection of top guidelines to guarantee that your interview questions effectively measure the candidates’ expertise in Clojure.

Clojure example question

Help us design a parking lot

Hey candidate! Welcome to your interview. Boilerplate is provided. Feel free to change the code as you see fit. To run the code at any time, please hit the run button located in the top left corner.

Goals: Design a parking lot using object-oriented principles

Here are a few methods that you should be able to run:

  • Tell us how many spots are remaining
  • Tell us how many total spots are in the parking lot
  • Tell us when the parking lot is full
  • Tell us when the parking lot is empty
  • Tell us when certain spots are full e.g. when all motorcycle spots are taken
  • Tell us how many spots vans are taking up

Assumptions:

  • The parking lot can hold motorcycles, cars and vans
  • The parking lot has motorcycle spots, car spots and large spots
  • A motorcycle can park in any spot
  • A car can park in a single compact spot, or a regular spot
  • A van can park, but it will take up 3 regular spots
  • These are just a few assumptions. Feel free to ask your interviewer about more assumptions as needed

Junior Clojure interview questions

Question:

Fix the code:

(defn add-numbers [a b]
  (+ a b c))

Answer:
The code has a syntax error because it references an undeclared symbol c in the add-numbers function. To fix it, you can either remove the c or provide a default value for c. Here are two possible fixes:

Fix 1: Remove the c from the code:

(defn add-numbers [a b]
  (+ a b))

Fix 2: Provide a default value for c:

(defn add-numbers [a b]
  (let [c 0]
    (+ a b c)))

Question:

What is the purpose of immutability in Clojure?

Answer:
Immutability is a fundamental concept in Clojure. It means that once a value is assigned to a variable, it cannot be changed. The purpose of immutability in Clojure is to ensure predictable and reliable code. Immutable data structures eliminate the need for defensive copying and make it easier to reason about the state of the program. Immutability enables functional programming paradigms and encourages the use of pure functions, which have no side effects and produce the same output for the same input every time they are called.

Question:
Fix the code:

(defn multiply-list [numbers factor]
  (map (* numbers factor)))

Answer:
The code has a syntax error because the map function expects a sequence as its first argument, but numbers is already a sequence, and the * function is expected to be called with two arguments, not a sequence. To fix it, you need to modify the code to map the multiplication operation over each element of the numbers sequence. Here’s a possible fix:

(defn multiply-list [numbers factor]
  (map #( * % factor) numbers))

In this fixed code, the #(* % factor) is an anonymous function that multiplies each element (%) of the numbers sequence by the factor.

Question:

What is the difference between a vector and a list in Clojure?

Answer:
In Clojure, both vectors and lists are used to represent collections of items, but they have some important differences:

  1. Structure: A vector is represented by square brackets [ ], while a list is represented by parentheses ( ).
  2. Random Access: Vectors allow efficient random access to elements using indexes, while lists don’t. Retrieving an element from a vector by index takes constant time, whereas in a list, it takes time proportional to the index.
  3. Modification: Vectors are efficient for appending and updating elements at the end, but modifying elements at the beginning or middle requires creating new vectors with modified portions. Lists, on the other hand, support efficient modifications at both ends. Adding or removing elements at the beginning or end of a list can be done in constant time.
  4. Persistent Data Structure: Vectors are persistent data structures, meaning that modifying a vector creates a new vector while reusing most of the original structure. Lists, however, are immutable, so any modification results in a new list.

In general, vectors are often used when random access or efficient appending is required, while lists are more commonly used for sequential operations and efficient modification at both ends.

Question:
Fix the code:

(defn is-even? [n]
  (if (zero? (rem n 2))
    true
    false))

Answer:
The code is correct in terms of functionality, but it can be simplified. The if expression already returns a Boolean value (true or false), so there’s no need to explicitly return true or false in each branch. Here’s a simplified version of the code:

(defn is-even? [n]
  (zero? (rem n 2)))

In this fixed code, the is-even? function directly returns the result of the (zero? (rem n 2)) expression, which is a Boolean indicating whether n is even or not.

Question:

What are the advantages of using a persistent data structure in Clojure?

Answer:
Persistent data structures are a key feature of Clojure and offer several advantages:

  1. Immutability: Persistent data structures are immutable, meaning they cannot be modified after creation. This immutability ensures predictable and reliable code, as it eliminates the risk of accidental modifications or shared mutable state.
  2. Structural Sharing: When a persistent data structure is modified, it shares as much of its existing structure as possible with the new version. This sharing allows efficient memory usage, as most of the original structure is reused, and only the modified parts need to be allocated.
  3. Functional Programming: Immutable persistent data structures align well with functional programming principles. They encourage writing pure functions that don’t have side effects and produce consistent results for the same inputs, making code easier to reason about and test.
  4. Efficient Modifications: Despite being immutable, persistent data structures provide efficient ways to modify them. Instead of modifying the original structure, modifications create new versions that share most of their data with the previous versions. This approach ensures that most operations can be performed in a time and memory-efficient manner.
  5. Thread Safety: Immutable persistent data structures are inherently thread-safe. Since no mutation occurs, multiple threads can safely access and use the same data structure without the need for locks or synchronization.

Overall, persistent data structures in Clojure promote a functional programming style, simplify concurrency, and enable efficient and reliable code.

Question:

(defn calculate-sum [numbers]
  (apply + numbers))

Answer:
The code is almost correct, but it is missing parentheses around the + function. The apply function expects a function as its first argument, and the + function should be wrapped in parentheses. Here’s the fixed code:

(defn calculate-sum [numbers]
  (apply + numbers))

In this fixed code, the apply function applies the + function to the elements of the numbers sequence, resulting in their sum.

Question:

What is the purpose of a macro in Clojure?

Answer:
In Clojure, macros are a powerful feature that allows developers to define new language constructs and extend the language syntax. Macros operate on the code itself, transforming and generating new code during the compilation phase.

The main purpose of macros is to provide a way to abstract and automate repetitive or complex code transformations that cannot be easily achieved with functions. They allow developers to define their own domain-specific language (DSL) or syntactic shortcuts, making code more expressive and concise.

Macros are evaluated at compile-time and expand into their transformed code before runtime. This enables the generation of code that may involve conditional evaluations, loops, or other constructs that are not possible with regular functions.

By using macros, Clojure developers can write code that is more declarative, expressive, and tailored to the problem domain. Macros provide a mechanism for metaprogramming, allowing developers to shape the language to fit their specific needs.

Question:
Fix the code:

(defn reverse-string [str

]
  (apply str (reverse str)))

Answer:
The code has a small issue that can lead to unexpected results. The reverse function returns a sequence, but the apply str expects individual arguments rather than a sequence. To fix it, we need to convert the reversed sequence back into a string. Here’s the fixed code:

(defn reverse-string [str]
  (apply str (seq (reverse str))))

In this fixed code, the (seq (reverse str)) expression converts the reversed sequence into a sequence of characters, which can then be passed to apply str to produce the reversed string.

Question:

Explain the concept of laziness in Clojure and how it can be beneficial.

Answer:
Laziness is a fundamental concept in Clojure and refers to the delayed evaluation of expressions until their results are actually needed. In other words, lazy evaluation allows computations to be deferred until they are required, rather than immediately computing and storing the results.

In Clojure, lazy sequences are one of the key ways laziness is implemented. A lazy sequence is a sequence whose elements are computed on-demand, as they are accessed or requested. The elements are computed one at a time, and only when needed.

The benefits of laziness in Clojure are as follows:

  1. Efficiency: Lazy evaluation allows for more efficient memory utilization since computations are performed on an as-needed basis. It avoids unnecessary computations for elements that are not accessed, reducing memory consumption and improving performance.
  2. Infinite Sequences: Laziness enables the creation and manipulation of infinite sequences. Since the elements of a lazy sequence are computed on-demand, it becomes possible to work with sequences that are conceptually infinite.
  3. Composition: Lazy sequences can be easily composed and combined with other sequences or transformations, allowing for powerful data processing pipelines. Laziness allows operations to be chained together without generating intermediate data structures, resulting in more concise and efficient code.
  4. Control Flow: Laziness provides a convenient way to express control flow constructs, such as conditional branching or looping. By utilizing lazy sequences, you can express complex algorithms and computations in a more declarative and expressive manner.

Intermediate Clojure interview questions

Question:
Fix the code:

(defn get-unique-values [coll]
  (set coll))

Answer:
The code has a small issue. While using (set coll) to convert a collection into a set is a valid approach, it won’t necessarily preserve the original order of the elements. If maintaining the order is important, we can use the distinct function instead. Here’s the fixed code:

(defn get-unique-values [coll]
  (distinct coll))

In this fixed code, the distinct function is used to return a sequence of the unique elements from coll, preserving their original order.

Question:

What is the difference between a protocol and a record in Clojure?

Answer:
In Clojure, protocols and records are both mechanisms for defining abstractions and data structures, but they serve different purposes:

  1. Protocols: Protocols are used to define a set of related functions that can be implemented by different types. They enable polymorphism and provide a way to define shared behavior for different data types. Protocols allow you to define a contract or interface that specifies a set of functions that should be implemented by any type that satisfies the protocol. Types can then be extended to implement the protocol, providing specific implementations for those functions.
  2. Records: Records are used to define custom data types with named fields. They provide a way to define structured data with associated functions. Records are typically used to model stateful or data-centric concepts. Records are defined using the defrecord form and automatically generate a constructor function and accessor functions for each field.

In summary, protocols are used for defining behaviors or interfaces that can be implemented by different types, whereas records are used for defining custom data types with named fields.

Question:
Fix the code:

(defn calculate-average [numbers]
  (/ (apply + numbers) (count numbers)))

Answer:
The code is almost correct, but it has a potential division-by-zero error when the numbers sequence is empty. To handle this case, we need to add a conditional check before performing the division. Here’s the fixed code:

(defn calculate-average [numbers]
  (if (seq numbers)
    (/ (apply + numbers) (count numbers))
    0)) ; or any other appropriate default value

In this fixed code, we use (seq numbers) to check if numbers is not empty before performing the division. If numbers is empty, a default value of 0 (or any other appropriate value) is returned to handle the edge case.

Question:

What are transducers in Clojure and how do they differ from regular sequence operations?

Answer:
Transducers are a powerful feature introduced in Clojure that provide a composable way to transform and process sequences without creating intermediate data structures. They are similar to sequence operations like map and filter, but with some key differences:

  1. Composability: Transducers are composable transformation functions that can be combined together to create more complex transformations. Unlike regular sequence operations, transducers can be efficiently combined using the comp function without creating intermediate sequences. This leads to better performance and reduced memory usage, especially when dealing with large data sets.
  2. Separation of Concerns: Transducers separate the transformation logic from the sequence being transformed. They operate on a “recipe” of transformations that can be applied to any sequence. This separation allows transducers to be reused across different data sources, such as vectors, lists, or custom data structures.
  3. Late Execution: Transducers are lazy and operate on-demand. They don’t perform any computation until a terminal operation like into or reduce is called on the transducer. This lazy evaluation allows for more efficient and selective processing of elements, as only the required transformations are performed.
  4. Performance Benefits: Transducers can often outperform regular sequence operations due to their composability and laziness. By avoiding intermediate sequence allocations, transducers can significantly reduce memory usage and improve performance, especially when dealing with large or infinite data sets.

In summary, transducers provide a flexible and efficient way to transform and process sequences by separating the transformation logic, enabling composability, and optimizing memory usage and performance.

Question:
Fix the code:

(defn merge-maps [map1 map2]
  (merge map1 map2))

Answer:
The code is correct in terms of functionality. However, if you want to ensure that the resulting map is immutable, you can use the into function with an empty map as the target. Here’s the fixed code:

(defn merge-maps [map1 map2]
  (into {} (merge map1 map2)))

In this fixed code, the into function is used to merge map1 and map2 into an empty map {}. The resulting map will be a new immutable map.

Question:

What is the purpose of the do expression in Clojure, and how does it differ from let?

Answer:
In Clojure, the do expression is used to group multiple expressions together and execute them in sequence. It is commonly used when you need to perform side effects or when you want to create a block of code that evaluates to the value of the last expression within the block.

The main purpose of do is to allow the execution of multiple expressions and control the order of evaluation without introducing any additional binding. It is particularly useful when you want to execute a series of expressions for their side effects or when you need to perform actions in a specific order.

On the other hand, let is used for introducing local bindings within a lexical scope. It allows you to bind values to symbols and use them in subsequent expressions within the let block. Unlike do, let is primarily used for introducing bindings rather than controlling the order of evaluation.

To summarize, the main differences between do and let are:

  • do is used for sequencing expressions and controlling the order of evaluation, while let is used for introducing local bindings.
  • do does not introduce any bindings, whereas let introduces local bindings that can be referred to within the let block.
  • do evaluates to the value of the last expression within the block, while let evaluates to the value of the last expression in its body.

Question:
Fix the code:

(defn multiply-by-index [coll]
  (map-indexed (fn [idx value]
                 (* idx value))
               coll))

Answer:
The code is correct and functional. It uses the map-indexed function to iterate over the elements of coll along with their indices and multiplies each element by its index. The result is a lazy sequence of the multiplied values.

Question:

What is the purpose of a transducer in Clojure, and how does it differ from a reducer?

Answer:
In Clojure, a transducer is a composable transformation that operates on sequences and performs a series of transformations without creating intermediate data structures. It represents a transformation recipe that can be applied to different data sources.

The main purpose of a transducer is to enable efficient and flexible data transformation pipelines. By composing transducers together using the comp function, you can define complex transformations without creating intermediate sequences. Transducers separate the transformation logic from the underlying sequence, allowing for greater reusability and composability.

On the other hand, a reducer is a function used to combine elements of a sequence into an accumulated result. Reducers are typically used with the reduce function to perform aggregations or computations on sequences. Reducers operate on individual elements of a sequence and accumulate a result by applying a combining function repeatedly.

The key difference between transducers and reducers is their focus and purpose. Transducers focus on transforming sequences in a composable and efficient manner, while reducers focus on aggregating or reducing sequences into a final result. Transducers operate on sequences and provide a way to define complex transformations, while reducers operate on individual elements of a sequence and accumulate a result.

Question:
Fix the code:

(defn split-by-odd-even [coll]
  (partition-by odd? coll))

Answer:
The code is correct and functional. It uses the partition-by function to split the coll into sub-collections based on the odd/even property of the elements. Elements with the same odd/even property are grouped together.

Question:

Explain the concept of multimethods in Clojure and how they can be beneficial.

Answer:
Multimethods are a feature in Clojure that allow you to define different behavior for a function based on the type or other attributes of its arguments. They provide a way to dispatch a function call to a specific implementation based on the runtime type or characteristics of the arguments.

The concept of multimethods provides several benefits:

  1. Polymorphism: Multimethods enable polymorphic behavior by allowing you to define different implementations of a function for different types. This allows you to write code that adapts to different data types or conditions, providing flexibility and extensibility.
  2. Separation of Concerns: Multimethods allow you to separate the logic for different types or conditions into distinct implementations. This promotes modularity and helps keep the codebase organized and maintainable.
  3. Open Extension: Multimethods support open extension, meaning that new implementations can be added to existing multimethods without modifying the original code. This is particularly useful when you have a library or framework that provides a multimethod, and you can add your own implementations for your specific types or requirements.
  4. Dynamic Dispatch: Multimethods provide dynamic dispatch, allowing the dispatch function to determine the specific implementation at runtime. This is different from static dispatch, where the specific implementation is determined at compile-time. Dynamic dispatch allows for greater flexibility and adaptability in choosing the appropriate implementation based on runtime conditions.

Overall, multimethods in Clojure provide a powerful mechanism for polymorphism, separation of concerns, open extension, and dynamic dispatch. They allow you to write code that can handle different types or conditions gracefully and can be extended without modifying existing code.

Senior Clojure interview questions

Question:
Fix the code:

(defn greet [name]
  (println "Hello" name))

Answer:
The code is correct and functional. It defines a function called greet that takes a name parameter and prints a greeting message with the provided name.

Question:

Explain the concept of lazy sequences in Clojure and how they differ from eager sequences.

Answer:
Lazy sequences in Clojure are sequences that are computed on-demand, as their elements are accessed. They are created using functions like range, filter, and map, which generate sequences that are only evaluated as needed. Lazy sequences provide several benefits:

  1. Efficiency: Lazy sequences allow for efficient use of memory and computation resources. They compute and generate elements as they are accessed, avoiding the need to compute the entire sequence upfront.
  2. Infinite Sequences: Lazy sequences can represent infinite sequences, where elements are generated as needed without exhausting memory or computation resources. This enables working with potentially infinite collections, such as generating prime numbers or infinite series.
  3. Composition: Lazy sequences can be easily composed using sequence functions like map, filter, take, and drop. This allows for a declarative and composable style of programming, where complex transformations can be applied to sequences without immediate evaluation.

In contrast, eager sequences in Clojure are evaluated eagerly, meaning that their elements are computed upfront when the sequence is created. They consume memory and computation resources even if not all elements are accessed.

Question:
Fix the code:

(defn factorial [n]
  (if (= n 0)
    1
    (* n (factorial (- n 1)))))

Answer:
The code is correct and functional. It defines a recursive function called factorial that calculates the factorial of a given number n.

Question:

Explain the purpose of the :pre and :post conditions in Clojure functions and how they help with assertions and debugging.

Answer:
The :pre and :post conditions in Clojure functions are used for assertions and debugging purposes.

The :pre condition is a pre-condition that is checked before the function body is executed. It allows you to define assertions about the input arguments of a function. If a pre-condition fails, an exception is thrown, indicating that the function was called with invalid arguments.

The :post condition is a post-condition that is checked after the function body is executed. It allows you to define assertions about the return value or the state after the function execution. If a post-condition fails, an exception is thrown, indicating that the function did not produce the expected result or state.

By using :pre and :post conditions, you can add assertions to your functions that help catch and report errors early during development. They act as contracts, documenting the expected behavior of the function and providing automatic validation. This can aid in debugging, as errors can be traced back to the exact function call where the pre or post-condition failed.

Question:
Fix the code:

(defn merge-maps [maps]
  (apply merge maps))

Answer:
The code is correct and functional. It defines a function called merge-maps that takes a sequence of maps and merges them using the merge function.

Question:

Explain the concept of STM (Software Transactional Memory) in Clojure and how it helps with concurrent programming.

Answer:
STM, or Software Transactional Memory,

is a concurrency control mechanism provided by Clojure to manage shared state in a concurrent environment. STM helps ensure consistent and coordinated access to shared state while avoiding issues such as race conditions, deadlocks, and inconsistent states.

In Clojure, STM is implemented using the ref and dosync constructs. The ref function is used to create a reference to a piece of mutable state. Multiple threads can read and modify the state referred to by a ref. The dosync block is used to define a transaction, which represents a set of coordinated operations on one or more ref objects.

The key features and benefits of STM in Clojure include:

  1. Atomicity: Transactions in STM are atomic, meaning that they either succeed as a whole or are rolled back entirely. This ensures that the shared state remains consistent, even in the presence of concurrent modifications.
  2. Coordinated Changes: STM allows multiple threads to coordinate changes on shared state by using the dosync block. Inside a transaction, changes made to ref objects are not visible to other threads until the transaction commits.
  3. Automatic Rollback: If a conflict is detected during the execution of a transaction (e.g., due to concurrent modifications by other threads), the transaction is automatically rolled back and retried. This ensures that conflicting modifications are resolved and consistent results are obtained.
  4. Consistency and Isolation: STM provides a consistent and isolated view of shared state to each transaction. It guarantees that each transaction sees a consistent snapshot of the shared state, as if it were executed sequentially.

By using STM in Clojure, concurrent programs can safely manage shared state without the need for explicit locks or manual synchronization. STM simplifies concurrent programming by providing a higher-level abstraction that handles the complexities of coordination and synchronization automatically.

Question:
Fix the code:

(defn calculate-sum [numbers]
  (reduce + 0 numbers))

Answer:
The code is correct and functional. It defines a function called calculate-sum that takes a sequence of numbers and calculates their sum using the reduce function with the + operator as the combining function and an initial value of 0.

Question:

Explain the purpose and advantages of using persistent data structures in Clojure.

Answer:
Persistent data structures are a fundamental concept in Clojure that provide efficient immutability and structural sharing. They are designed to support efficient updates while maintaining the benefits of immutability.

The purpose and advantages of using persistent data structures in Clojure include:

  1. Immutability: Persistent data structures in Clojure are immutable, meaning that they cannot be modified after creation. Any operation on a persistent data structure returns a new modified version, leaving the original data structure unchanged. This simplifies reasoning about the code, enables safer concurrent programming, and facilitates easy undo/redo or time-traveling debugging.
  2. Efficiency: Persistent data structures in Clojure achieve efficient updates by using structural sharing. When a modification is made to a persistent data structure, most of the original structure is reused, minimizing memory consumption and reducing the cost of copying or creating new versions. This makes persistent data structures efficient for a wide range of operations, including updates, lookups, and traversals.
  3. Functional Programming: Persistent data structures align well with the functional programming paradigm. They are ideal for representing and manipulating data in a functional and declarative manner. Persistent data structures promote a style of programming that emphasizes immutability, pure functions, and transformations, leading to code that is easier to reason about, test, and compose.
  4. Performance: Persistent data structures provide efficient performance characteristics for many common operations. They offer excellent time and space efficiency for updates, lookups, and insertions. The structural sharing mechanism ensures that only the necessary parts of the data structure are copied or modified, reducing the overhead compared to fully copying or modifying the entire structure.

By leveraging persistent data structures, Clojure allows developers to write expressive, immutable, and high-performance code. The advantages of immutability, structural sharing, and efficient updates make persistent data structures a key ingredient in the success of Clojure as a language for building robust and scalable applications.

Question:
Fix the code:

(defn reverse-string [s]
  (apply str (reverse s)))

Answer:
The code is correct and functional. It defines a function called reverse-string that takes a string s and reverses it using the reverse function and then concatenates the characters using apply and str.

Question:

Explain the concept of multimethods in Clojure and how they differ from regular functions.

Answer:
Multimethods in Clojure are a powerful mechanism for polymorphism and dynamic dispatch. They allow different behaviors to be associated with different combinations of arguments, enabling flexible and extensible code.

The key points about multimethods in Clojure are:

  1. Dispatch: Multimethods are defined based on a dispatch function. This dispatch function examines the arguments passed to the multimethod and returns a dispatch value that determines which specific method implementation should be invoked. The dispatch value can be any Clojure data type.
  2. Method Implementation: Each multimethod can have multiple method implementations associated with it. Each method implementation is defined using the defmethod macro, which specifies the multimethod, the dispatch value(s) it handles, and the implementation logic.
  3. Dynamic Dispatch: Unlike regular functions, which have static dispatch based on the function name, multimethods provide dynamic dispatch based on the dispatch value. This means that the specific method implementation to be invoked is determined at runtime based on the arguments passed.
  4. Extensibility: Multimethods promote extensibility by allowing new method implementations to be added to existing multimethods or new multimethods to be defined. This enables code to be easily extended to handle new types or combinations of arguments without modifying existing code.

Compared to regular functions, multimethods provide a more flexible and dynamic approach to polymorphism. Regular functions have static dispatch based on the function name, while multimethods have dynamic dispatch based on the dispatch value(s) returned by the dispatch function. Multimethods allow different behaviors to be associated with different combinations of arguments, providing a powerful mechanism for designing flexible and extensible code.

1,000 Companies use CoderPad to Screen and Interview Developers

Best interview practices for Clojure roles

When conducting successful Clojure interviews, it’s crucial to take into account various aspects, such as the candidate’s background and the particular engineering position. To ensure a productive interview experience, we suggest implementing the following best practices:

  • Design technical questions that mirror real-world business situations within your organization. This method will effectively engage the candidate and help evaluate their fit with your team.
  • Foster a collaborative atmosphere by encouraging candidates to ask questions throughout the interview.
  • If your candidates will be working on full stack applications, it may be helpful for them to have knowledge of ClojureScript.

Furthermore, adhering to standard interview practices is essential when carrying out Clojure interviews. This includes tailoring the question difficulty to suit the candidate’s capabilities, giving prompt updates on their application status, and offering them the chance to inquire about the evaluation process and cooperation with you and your team.

]]>
Dart https://coderpad.io/interview-questions/dart-interview-questions/ Wed, 19 Apr 2023 13:22:39 +0000 https://coderpad.io/?post_type=interview-questions&p=33285 This Google-developed language was primarily created for building web, server-side, and mobile applications with an emphasis on performance, ease of use, and scalability.

Dart is a strongly typed language with a sound type system, which helps to catch potential errors during development. It also features automatic memory management through garbage collection, which simplifies memory handling for developers.

https://dart.dev/guides/language

In order to evaluate the skill level of developers in Dart during programming interviews, we provide a set of hands-on coding exercises and interview questions below. Additionally, we have developed a range of suggested methods to ensure that your interview inquiries accurately measure the candidates’ Dart capabilities.

Dart example question

Help us design a parking lot app

Hey candidate! Welcome to your interview. Boilerplate is provided. Feel free to change the code as you see fit. To run the code at any time, please hit the run button located in the top left corner.

Goals: Design a parking lot using object-oriented principles

Here are a few methods that you should be able to run:

  • Tell us how many spots are remaining
  • Tell us how many total spots are in the parking lot
  • Tell us when the parking lot is full
  • Tell us when the parking lot is empty
  • Tell us when certain spots are full e.g. when all motorcycle spots are taken
  • Tell us how many spots vans are taking up

Assumptions:

  • The parking lot can hold motorcycles, cars and vans
  • The parking lot has motorcycle spots, car spots and large spots
  • A motorcycle can park in any spot
  • A car can park in a single compact spot, or a regular spot
  • A van can park, but it will take up 3 regular spots
  • These are just a few assumptions. Feel free to ask your interviewer about more assumptions as needed

Junior Dart interview questions

Question:
The following Dart code snippet is intended to print the numbers from 1 to 10. However, it is currently not working. Identify and fix the issue.

void main() {
  for (var i = 1; i < 11; i++) {
    print(i);
  }
}

Answer:
The code snippet provided is correct and will print the numbers from 1 to 10. There is no issue with it.

Question:
What is Dart’s main use case, and which platform does it primarily target?

Answer:
Dart is a general-purpose programming language primarily used for building web and mobile applications. It is the language used for developing Flutter applications, which is a UI framework primarily targeting mobile platforms like Android and iOS.

Question:
The following Dart code snippet is supposed to concatenate two strings and print the result. However, it is not producing the expected output. Identify and fix the issue.

void main() {
  String firstName = "John";
  String lastName = "Doe";
  String fullName = firstName + lastName;
  print(fullName);
}

Answer:
To concatenate two strings in Dart, you can use the + operator. However, in the given code snippet, the + operator is not adding a space between the firstName and lastName. To fix this, you can add a space manually between the strings:

void main() {
  String firstName = "John";
  String lastName = "Doe";
  String fullName = firstName + " " + lastName;
  print(fullName);
}

Question:
What is the purpose of using the async and await keywords in Dart?

Answer:
The async and await keywords in Dart are used in asynchronous programming to work with functions that perform potentially time-consuming tasks, such as making network requests or accessing databases. By marking a function as async, it can use the await keyword to wait for the completion of other asynchronous operations without blocking the execution. This allows for more efficient and responsive code, especially in UI-driven applications.

Question:
The following Dart code snippet is supposed to calculate the sum of all elements in the numbers list. However, it is not returning the correct result. Identify and fix the issue.

void main() {
  List<int> numbers = [1, 2, 3, 4, 5];
  int sum = 0;

  for (var i = 0; i <= numbers.length; i++) {
    sum += numbers[i];
  }

  print(sum);
}

Answer:
The issue in the code is with the loop condition in the for loop. The condition should be i < numbers.length instead of i <= numbers.length to avoid accessing an index out of range. Here’s the corrected code:

void main() {
  List<int> numbers = [1, 2, 3, 4, 5];
  int sum = 0;

  for (var i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }

  print(sum);
}

Question:
What is the purpose of using the final keyword in Dart?

Answer:
In Dart, the final keyword is used to declare a variable whose value cannot be changed once assigned. It is similar to const, but the value of a final variable can be determined at runtime. Using final allows you to ensure that a variable remains constant after its initialization, providing immutability and better performance in some cases.

Question:
The following Dart code snippet is intended to calculate the factorial of a given number. However, it is currently not producing the correct result. Identify and fix the issue.

void main() {
  int number = 5;
  int factorial = 1;

  for (var i = 2; i <= number; i++) {
    factorial *= i;
  }

  print("The factorial of $number is $factorial");
}

Answer:
The code snippet provided correctly calculates the factorial of a given number. There is no issue with it.

Question:
What is the difference between a List and a Set in Dart?

Answer:
In Dart, a List is an ordered collection of elements that allows duplicate values. Elements in a list can be accessed using their index. On the other hand, a Set is an unordered collection of unique elements. It does not allow duplicate values, and the order of elements is not guaranteed. Sets are useful when you need to ensure uniqueness and perform operations like union, intersection, and difference on collections.

Question:
The following Dart code snippet is intended to check if a given number is even or odd. However, it is not returning the correct result. Identify and fix the issue.

void main() {
  int number = 7;

  if (number % 2 = 0) {
    print("$number is even");
  } else {
    print("$number is odd");
  }
}

Answer:
In the given code snippet, there is a mistake in the condition of the if statement. Instead of using the assignment operator =, the equality operator == should be used. Here’s the corrected code:

void main() {
  int number = 7;

  if (number % 2 == 0) {
    print("$number is even");
  } else {
    print("$number is odd");
  }
}

Question:
What are some advantages of using Dart for mobile app development compared to other programming languages?

Answer:
Some advantages of using Dart for mobile app development, particularly with Flutter, include:

  1. Cross-platform development: Dart allows you to build mobile apps for both Android and iOS using a single codebase, reducing development time and effort.
  2. Hot Reload: Dart’s Hot Reload feature in Flutter enables developers to see the changes made in the code immediately reflected in the app, allowing for faster iteration and debugging.
  3. Performance: Dart’s Just-in-Time (JIT) compilation during development and Ahead-of-Time (AOT) compilation during production result in efficient and performant mobile apps.
  4. Rich UI and Customization: Dart and Flutter provide a rich set of customizable UI widgets, enabling developers to create visually appealing and highly interactive mobile apps.
  5. Community and Ecosystem: Dart and Flutter have a growing community and ecosystem, with a wide range of packages and libraries available, making it easier to leverage existing solutions for various app requirements.

Intermediate Dart interview questions

Question:
The following Dart code snippet is intended to calculate the sum of two numbers. However, it is not producing the correct result. Identify and fix the issue.

int calculateSum(int a, int b) {
  int sum = a + b;
  return sum;
}

void main() {
  int result = calculateSum(2, 3);
  print(result);
}

Answer:
The code is correct. It correctly calculates the sum of two numbers. The output will be 5.

Question:
What is a constructor in Dart? Explain with an example.

Answer:
A constructor in Dart is a special method used for creating objects of a class. It initializes the object’s state when it is created. Dart supports two types of constructors: default constructors and named constructors.

Example:

class Person {
  String name;
  int age;

  Person(this.name, this.age); // Default constructor

  Person.fromBirthYear(this.name, int birthYear) {
    age = DateTime.now().year - birthYear;
  } // Named constructor

  void sayHello() {
    print('Hello, my name is $name.');
  }
}

void main() {
  var person1 = Person('Alice', 25); // Using the default constructor
  person1.sayHello();

  var person2 = Person.fromBirthYear('Bob', 1990); // Using the named constructor
  person2.sayHello();
}

In the example above, the Person class has a default constructor that takes name and age as parameters. It also has a named constructor Person.fromBirthYear() that takes name and birthYear as parameters and calculates the age based on the current year. Objects can be created using either the default constructor or the named constructor.

Question:
The following Dart code snippet is intended to reverse the order of elements in a list. However, it is not producing the correct result. Identify and fix the issue.

List<T> reverseList<T>(List<T> list) {
  List<T> reversed = list.reversed.toList();
  return reversed;
}

void main() {
  List<int> numbers = [1, 2, 3, 4, 5];
  List<int> reversedNumbers = reverseList(numbers);
  print(reversedNumbers);
}

Answer:
The code is correct. It correctly reverses the order of elements in the list using the reversed method. The output will be [5, 4, 3, 2, 1].

Question:
What are named parameters in Dart? Explain with an example.

Answer:
Named parameters in Dart allow passing arguments to a function by specifying the parameter names. This provides flexibility and improves code readability by making it clear which arguments are being passed.

Example:

void greet({String name, String message = 'Hello'}) {
  print('$message, $name!');
}

void main() {
  greet(name: 'Alice'); // Output: Hello, Alice!
  greet(name: 'Bob', message: 'Hi'); // Output: Hi, Bob!
}

In the example above, the greet() function has named parameters name and message. The name parameter is required, while the message parameter has a default value of 'Hello'. By using named parameters, we can specify the arguments in any order and provide default values if necessary.

Question:

The following Dart code snippet is intended to check if all elements in a list are positive numbers. However, it is not producing the correct result. Identify and fix the issue.

bool areAllPositive(List<int> numbers) {
  bool allPositive = true;

  for (int number in numbers) {
    if (number <= 0) {
      allPositive = false;
      break;
    }
  }

  return allPositive;
}

void main() {
  List<int> numbers = [1, 2, -3, 4, 5];
  bool isAllPositive = areAllPositive(numbers);
  print(isAllPositive);
}

Answer:
The code is incorrect. The condition number <= 0 includes zero as a positive number, which is not intended. To fix this, the condition should be number < 0 to exclude zero from being considered positive.

bool areAllPositive(List<int> numbers) {
  bool allPositive = true;

  for (int number in numbers) {
    if (number < 0) {
      allPositive = false;
      break;
    }
  }

  return allPositive;
}

void main() {
  List<int> numbers = [1, 2, -3, 4, 5];
  bool isAllPositive = areAllPositive(numbers);
  print(isAllPositive);
}

In the corrected code, the condition number < 0 correctly checks if a number is negative, excluding zero from being considered positive.

Question:
What is the purpose of the super keyword in Dart? Provide an example.

Answer:
The super keyword in Dart is used to refer to the superclass or parent class. It allows accessing and invoking members (methods or properties) of the superclass from within a subclass. This is useful when the subclass wants to override a method but still needs to use the implementation from the superclass.

Example:

class Animal {
  String name;

  Animal(this.name);

  void speak() {
    print('Animal speaks');
  }
}

class Dog extends Animal {
  Dog(String name) : super(name);

  @override
  void speak() {
    super.speak(); // Invoke the speak() method of the superclass
    print('$name barks');
  }
}

void main() {
  var dog = Dog('Buddy');
  dog.speak();
}

In the example above, the Dog class extends the Animal class. By using super.speak() inside the speak() method of the Dog class, we invoke the speak() method of the Animal superclass before adding the custom behavior specific to the Dog class.

Question:
The following Dart code snippet is intended to merge two lists into a single list. However, it is not producing the correct result. Identify and fix the issue.

List<T> mergeLists<T>(List<T> list1, List<T> list2) {
  return [...list1, ...list2];
}

void main() {
  List<int> list1 = [1, 2, 3];
  List<int> list2 = [4, 5, 6];
  List<int> mergedList = mergeLists(list1, list2);
  print(mergedList);
}

Answer:
The code is correct. It merges two lists using the spread (...) operator to create a new list that contains all elements from both list1 and list2.

Question:
What is the purpose of the async and await keywords in Dart? Explain with an example.

Answer:
In Dart, the async and await keywords are used in conjunction to work with asynchronous operations. The async keyword is used to mark a function as asynchronous, allowing it to use await to pause execution until an asynchronous operation completes.

Example:

Future<int> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return 42;
}

void main() async {
  print('Fetching data...');
  int data = await fetchData();
  print('Data fetched: $data');
}

In the example above, the fetchData() function is marked as async to indicate that it performs asynchronous operations. Inside the function, await is used to pause the execution until the Future.delayed() completes, simulating an asynchronous delay. The await keyword ensures that the program waits for the result before continuing. The main() function is also marked as async to allow using await inside it.

Question:
The following Dart code snippet is intended to calculate the factorial of a given number. However, it is not producing the correct result. Identify and fix the issue.

int calculateFactorial(int n) {
  if (n == 0) {
    return 1;
  }

  return n * calculateFactorial(n - 1);
}

void main() {
  int number = 5;
  int factorial = calculateFactorial(number);
  print(factorial);
}

Answer:
The code is correct. It uses recursion to calculate the factorial of a given number. The output will be the factorial of 5, which is 120.

Question:
The following Dart code snippet is intended to find the maximum value in a list of integers. However, it is not producing the correct result. Identify and fix the issue.

int findMax(List<int> numbers) {
  int max = 0;

  for (int number in numbers) {
    if (number > max) {
      max = number;
    }
  }

  return max;
}

void main() {
  List<int> numbers = [3, 7, 1, 5, 9];
  int maxNumber = findMax(numbers);
  print(maxNumber);
}

Answer:
The issue in the code is that it assumes all numbers in the list are positive. If the list contains negative numbers, the initial value of max (0) would incorrectly identify the maximum. To fix this, we can initialize max with the first element of the list and then compare the remaining elements to find the actual maximum.

int findMax(List<int> numbers) {
  if (numbers.isEmpty) {
    throw ArgumentError('List cannot be empty');
  }

  int max = numbers[0];

  for (int number in numbers) {
    if (number > max) {
      max = number;
    }
  }

  return max;
}

void main() {
  List<int> numbers = [3, 7, 1, 5, 9];
  int maxNumber = findMax(numbers);
  print(maxNumber);
}

In the corrected code, we added a check to handle the case when the list is empty by throwing an ArgumentError. We also initialized max with the first element of the list (numbers[0]) to correctly find the maximum value.

Senior Dart interview questions

Sure! Here are ten questions for a senior Dart engineer, including five fix-the-code questions and five theoretical questions. The answers are provided beneath each question.

Question:
The following Dart code snippet is intended to handle asynchronous file uploads using the http package. However, it is not handling errors properly. Identify and fix the issue.

import 'dart:io';
import 'package:http/http.dart' as http;

void uploadFile(File file) {
  var url = Uri.parse('https://example.com/upload');

  var request = http.MultipartRequest('POST', url);
  var multipartFile = http.MultipartFile.fromBytes('file', file.readAsBytesSync());

  request.files.add(multipartFile);

  request.send().then((response) {
    if (response.statusCode == 200) {
      print('File uploaded successfully');
    } else {
      print('File upload failed');
    }
  });
}

void main() {
  File file = File('path/to/file.txt');
  uploadFile(file);
}

Answer:
The issue in the code is that it is not handling errors properly. To fix this, add an error handling function using .catchError() to handle any errors that occur during the file upload process.

import 'dart:io';
import 'package:http/http.dart' as http;

void uploadFile(File file) {
  var url = Uri.parse('https://example.com/upload');

  var request = http.MultipartRequest('POST', url);
  var multipartFile = http.MultipartFile.fromBytes('file', file.readAsBytesSync());

  request.files.add(multipartFile);

  request.send().then((response) {
    if (response.statusCode == 200) {
      print('File uploaded successfully');
    } else {
      print('File upload failed');
    }
  }).catchError((error) {
    print('Error occurred during file upload: $error');
  });
}

void main() {
  File file = File('path/to/file.txt');
  uploadFile(file);
}

In the corrected code, an error handling function is added using .catchError() after the then() function. This ensures that any errors that occur during the file upload process are caught and handled appropriately.

Question:
The following Dart code snippet is intended to implement a caching mechanism using a Map to store the results of expensive computations. However, the cache is not working as expected. Identify and fix the issue.

Map<String, int> cache = {};

int calculateExpensiveResult(String input) {
  if (cache.containsKey(input)) {
    return cache[input];
  }

  // Expensive computation
  int result = 0;
  for (int i = 0; i < input.length; i++) {
    result += input.codeUnitAt(i);
  }

  cache[input] = result;
  return result;
}

void main() {
  print(calculateExpensiveResult('abc'));
  print(calculateExpensiveResult('abc'));
}

Answer:
The issue in the code is that the cache is not persisting between different runs of the program. To fix this, declare the cache variable as a global variable outside the main() function.

Map<String, int> cache = {};

int calculateExpensiveResult(String input) {
  if (cache.containsKey(input)) {
    return cache[input];
  }

  // Expensive computation
  int result = 0;
  for (int i = 0; i < input.length; i++) {
    result += input.codeUnitAt(i);
  }

  cache[input] = result;
  return result;
}

void main() {
  print(calculateExpensiveResult('abc'));
  print(calculateExpensiveResult('abc'));
}

In the corrected code, the cache variable is declared as a global variable outside the main() function. This ensures that the cache persists between different runs of the program, allowing the cached results to be retrieved correctly.

Question:
Explain the concept of “asynchronous programming” in Dart and how it helps in building efficient and responsive applications.

Answer:
Asynchronous programming in Dart allows executing tasks concurrently without blocking the execution of other code. It enables building efficient and responsive applications by leveraging non-blocking operations and handling long-running operations such as network requests, file I/O, and computations asynchronously.

In Dart, asynchronous programming is achieved using Futures and async/await syntax. Futures represent the result of an asynchronous operation that may complete with a value or an error in the future. By marking a function with the async keyword, it can use await to pause its execution until a Future completes, without blocking the main event loop.

The benefits of asynchronous programming in Dart include:

  1. Concurrency: Asynchronous programming allows multiple tasks to be executed concurrently, improving the overall performance and responsiveness of the application. It avoids blocking the execution of other code while waiting for long-running operations to complete.
  2. Responsiveness: By handling time-consuming tasks asynchronously, the application remains responsive to user interactions. It prevents the user interface from freezing or becoming unresponsive during long operations, enhancing the user experience.
  3. Resource Efficiency: Asynchronous operations enable more efficient utilization of system resources. While waiting for I/O or network requests, the CPU can perform other tasks, maximizing resource utilization and throughput.
  4. Scalability: Asynchronous programming facilitates handling multiple concurrent operations efficiently. It allows applications to scale and handle increased loads without significant performance degradation.
  5. Modularity and Maintainability: Asynchronous programming promotes modular and maintainable code by separating long-running operations from the main execution flow. It allows developers to write more granular and reusable functions that can be easily composed and tested.

Question:
The following Dart code snippet is intended to read a JSON file and parse its contents. However, it is throwing an error. Identify and fix the issue.

import 'dart:convert';
import 'dart:io';

void readJsonFile(String path) {
  File file = File(path);
  String jsonString = file.readAsStringSync();
  Map<String, dynamic> data = json.decode(jsonString);

  print('Name: ${data['name']}');
  print('Age: ${data['age']}');
}

void main() {
  readJsonFile('path/to/file.json');
}

Answer:
The issue in the code is that the specified JSON file path is incorrect. To fix this, ensure that the correct file path is provided.

import 'dart:convert';
import 'dart:io';

void readJsonFile(String path) {
  File file = File(path);
  String jsonString = file.readAsStringSync();
  Map<String, dynamic> data = json.decode(jsonString);

  print('Name: ${data['name']}');
  print('Age: ${data['age']}');
}

void main() {
  readJsonFile('path/to/file.json');
}

In the corrected code, the readJsonFile() function reads the JSON file using the correct file path, and the contents are successfully parsed and printed.

Question:
Explain the concept of “generics” in Dart and how they can be used to write reusable and type-safe code.

Answer:
Generics in Dart provide a way to write reusable and type-safe code that can work with different types without sacrificing static

type checking. With generics, classes, functions, and interfaces can be parameterized to work with a variety of types, allowing for code reuse and improved type safety.

By using generics, developers can define classes or functions that can operate on a range of types while maintaining compile-time type checking. This helps catch type-related errors early during development, reducing the likelihood of runtime errors and improving code robustness.

Some key benefits of using generics in Dart include:

  1. Code Reusability: Generics enable the creation of classes and functions that can work with multiple types. This promotes code reuse, as the same logic can be applied to different types without duplicating code.
  2. Type Safety: Generics ensure type safety at compile time. The type parameters provided to generic classes or functions allow the compiler to perform static type checks, preventing type-related errors and promoting more reliable code.
  3. Abstraction: Generics provide a level of abstraction that allows developers to write more general-purpose code. This abstraction allows the code to be flexible and adaptable to different data types, increasing the versatility and utility of the code.
  4. Performance Optimization: Generics can help improve performance by eliminating the need for type conversions or boxing/unboxing operations. The compiler can generate specialized code for each specific type, resulting in optimized execution.
  5. Code Readability: By using generics, code becomes more expressive and self-documenting. The type parameters provide additional context and clarity about the expected types, making the code more readable and easier to understand.

These advantages make generics a powerful tool in Dart for creating reusable and type-safe code that can adapt to various types, improving code maintainability, reliability, and performance.

Question:
The following Dart code snippet is intended to sort a list of integers in ascending order using the sort() method. However, the list is not being sorted correctly. Identify and fix the issue.

void main() {
  List<int> numbers = [3, 2, 1, 4, 5];
  numbers.sort();
  print(numbers);
}

Answer:
The issue in the code is that the sort() method is sorting the numbers lexicographically instead of numerically. To fix this, provide a comparison function to the sort() method that compares the integers numerically.

void main() {
  List<int> numbers = [3, 2, 1, 4, 5];
  numbers.sort((a, b) => a.compareTo(b));
  print(numbers);
}

In the corrected code, a comparison function is passed to the sort() method using a lambda expression (a, b) => a.compareTo(b). This ensures that the numbers are sorted correctly in ascending order.

Question:
What is the purpose of using mixins in Dart? Provide an example scenario where mixins can be beneficial.

Answer:
Mixins in Dart provide a way to reuse code across different class hierarchies without using inheritance. A mixin is a class-like construct that allows for code composition and provides additional functionality to classes by “mixing in” methods and properties.

The purpose of using mixins is to promote code reuse, maintainability, and flexibility. Mixins allow developers to extract and encapsulate common behavior and features into separate units that can be easily added to multiple classes.

One example scenario where mixins can be beneficial is in adding functionality to different types of widgets in a user interface framework. For instance, consider a UI framework that supports both buttons and checkboxes. Both widgets may have some common behaviors, such as handling user interaction events or rendering. Instead of duplicating the code for these shared behaviors in each widget class, a mixin can be created to encapsulate the common functionality.

mixin Clickable {
  void handleClick() {
    // Handle click event
  }
}

class Button with Clickable {
  // Button-specific code
}

class Checkbox with Clickable {
  // Checkbox-specific code
}

In the example above, the Clickable mixin defines the handleClick() method, which encapsulates the common behavior of handling a click event. The Button and Checkbox classes use the with keyword to include the Clickable mixin, allowing them to inherit and reuse the handleClick() method.

By using mixins, the code for handling click events is written once in the Clickable mixin and can be easily added to multiple widget classes. This promotes code reuse, reduces duplication, and makes it easier to maintain and extend the functionality of the widgets.

Question:
The following Dart code snippet is intended to perform a matrix transpose operation. However, it is producing incorrect results. Identify and fix the issue.

List<List<int>> transposeMatrix(List<List<int>> matrix) {
  List<List<int>> transposed = [];

  for (int i = 0; i < matrix[0].length; i++) {
    List<int> row = [];
    for (int j = 0; j < matrix.length; j++) {
      row.add(matrix[j][i]);
    }
    transposed.add(row);
  }

  return transposed;
}

void main() {
  List<List<int>> matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
  ];

  List

<List<int>> transposedMatrix = transposeMatrix(matrix);
  print(transposedMatrix);
}

Answer:
The issue in the code is that it assumes a rectangular matrix, where all rows have the same length. However, it doesn’t account for the possibility of irregular matrices where rows have different lengths. To fix this, iterate based on the maximum row length of the matrix.

List<List<int>> transposeMatrix(List<List<int>> matrix) {
  List<List<int>> transposed = [];

  int maxLength = 0;
  for (List<int> row in matrix) {
    if (row.length > maxLength) {
      maxLength = row.length;
    }
  }

  for (int i = 0; i < maxLength; i++) {
    List<int> newRow = [];
    for (int j = 0; j < matrix.length; j++) {
      if (i < matrix[j].length) {
        newRow.add(matrix[j][i]);
      } else {
        newRow.add(0); // Fill with a default value for irregular rows
      }
    }
    transposed.add(newRow);
  }

  return transposed;
}

void main() {
  List<List<int>> matrix = [
    [1, 2, 3],
    [4, 5],
    [7, 8, 9, 10],
  ];

  List<List<int>> transposedMatrix = transposeMatrix(matrix);
  print(transposedMatrix);
}

In the corrected code, the transposeMatrix() function iterates based on the maximum row length of the matrix. It dynamically handles irregular matrices by checking the length of each row and adding a default value (in this case, 0) when accessing elements beyond a row’s length.

Question:
What is the purpose of Dart’s async/await syntax? How does it improve asynchronous programming?

Answer:
Dart’s async/await syntax provides a more readable and intuitive way to write asynchronous code by allowing developers to write code that looks synchronous while actually executing asynchronously. It simplifies working with Futures and promotes a linear and sequential coding style.

The purpose of async/await is to make asynchronous programming more approachable and less error-prone by reducing the complexity of chaining callbacks or using explicit then() functions. It allows developers to write asynchronous code that resembles synchronous code, making it easier to understand and reason about.

With async/await, asynchronous code is structured using the following components:

  • async: The async keyword is used to mark a function as asynchronous. It allows the function to use the await keyword inside its body.
  • await: The await keyword is used to pause the execution of an asynchronous function until a Future completes. It allows the code to wait for the result of an asynchronous operation without blocking the event loop.

The benefits of async/await in Dart include:

  1. Readability: Asynchronous code written with async/await is more readable and easier to follow compared to traditional callback-based code or chained then() functions. It resembles sequential code execution, making it easier to understand the flow of control.
  2. Error Handling: async/await simplifies error handling in asynchronous code. By using try/catch blocks, errors can be caught and handled within the same function scope, making it easier to manage and propagate errors.
  3. Synchronous-Like Programming: async/await enables developers to write asynchronous code in a synchronous style. This makes it easier to write, test, and reason about asynchronous code by using familiar control flow structures such as loops and conditionals.
  4. Composition and Modularity: async/await allows for the composition of asynchronous operations using regular control structures. It promotes modular code design and enables the reuse of asynchronous functions in a straightforward manner.

Overall, async/await syntax in Dart improves the developer experience by simplifying the writing and comprehension of asynchronous code, reducing callback nesting, and making error handling more straightforward.

Question:
The following Dart code snippet is intended to perform an HTTP GET request to a given URL and return the response body as a string. However, it is not working as expected. Identify and fix the issue.

import 'dart:convert';
import 'package:http/http.dart' as http;

String fetchData(String url) {
  http.get(url).then((response) {
    if (response.statusCode == 200) {
      return response.body;
    } else {
      return 'Error: ${response.statusCode}';
    }
  });
}

void main() {
  String url = 'https://api.example.com/data';
  String data = fetchData(url);
  print(data);
}

Answer:
The issue in the code is that the fetchData() function does not return any value explicitly, and the main() function expects a returned value. To fix this, the fetchData() function should use the async/await syntax and return a Future<String> to represent the asynchronous operation.

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<String> fetchData(String url) async {
  http.Response response = await http.get(url);
  if (response.statusCode == 200) {
    return response.body;
  } else {
    return 'Error: ${response.statusCode}';
  }
}

void main() async {
  String url = 'https://api.example.com/data';
  String data = await fetchData(url);
  print(data);
}

In the corrected code, the fetchData() function is declared as async and returns a Future<String>. The await keyword is used to wait for the HTTP response and extract the body. The main() function is also marked as async to allow for the use of await when calling fetchData().

1,000 Companies use CoderPad to Screen and Interview Developers

Interview best practices for Dart roles

To conduct effective Dart interviews, it’s crucial to consider various factors, such as the applicants’ experience levels and the specific engineering role they’re seeking. To ensure your Dart interview questions yield the best results, we recommend adhering to these best practices while interacting with candidates:

  • Develop technical questions that mirror real-life scenarios within your organization. This approach will not only engage the candidate but also enable you to more precisely evaluate their fit for your team.
  • Encourage a cooperative atmosphere by allowing candidates to ask questions throughout the interview.
  • If you’re using Dart for mobile development, make sure your candidates have an understanding of mobile dev concepts such as layout design, UI components, navigation, and handling user input.

Moreover, it’s important to follow standard interview practices when executing Dart interviews. This includes adjusting question difficulty based on the applicant’s skill level, providing timely feedback on their application status, and enabling candidates to ask about the assessment or collaborate with you and your team.

]]>
Data Science https://coderpad.io/interview-questions/data-science-interview-questions/ Mon, 13 Nov 2023 19:52:45 +0000 https://coderpad.io/?post_type=interview-questions&p=36444 The ever-in-demand data scientist possesses a deep understanding of data analysis and machine learning methodologies, making them a pivotal figure in almost every project team.

According to the CoderPad 2024 Developer survey, data science developers are the ninth-most in-demand job role technical recruiters are looking to fill.

The subsequent sections present a variety of hands-on coding challenges and interview questions designed to probe a candidate’s proficiency in data science during technical evaluations.

Furthermore, a collection of suggested best practices has been integrated to assist in a reliable assessment of candidates’ data science capabilities through your interview questions.

Data science example questions

Question 1: Iris Exploratory Analysis

Context

The Iris dataset is a well known, heavily studied dataset hosted for public use by the UCI Machine Learning Repository.

The dataset includes three iris species with 50 samples each as well as some properties about each flower. One flower species is linearly separable from the other two, but the other two are not linearly separable from each other.

The columns in this dataset are:

  • id
  • sepal_length_cm
  • sepal_width_cm
  • petal_length_cm
  • petal_width_cm
  • class this is the species of Iris

The sample CSV data looks like this:

sepal_length_cm,sepal_width_cm,petal_length_cm,petal_width_cm,class
5.1,3.5,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
5.8,2.7,5.1,1.9,Iris-virginica

Directions

Using any analysis method you choose, build either a classifier or produce a data visualization, that shows how the available data can be leveraged to predict the species of Iris.

Initial cell contents

Use this starter code to get started with accessing the Iris dataset in this pad. Feel free to use either Pandas or Native Python for your work.

You may install additional packages by using pip in this Notebook’s terminal.

import pandas as pd
import pprint

# Result as pandas data frame

result_df = pd.read_csv('iris.csv')

# Preview results output as a data frame

result_df.head()

# Result as pythonic list of dictionaries

result = result_df.where(pd.notnull(result_df), None).to_dict('records')

# Preview results output as a native list of dictionaries

pprint.pprint([record for record in result])

Success criteria

At minimum, a candidate should be able to conduct a basic analysis showing that they explored the data and found a way to separate the unique characteristics of each flower from the other.

For example:

  • Does one species of iris have longer petals than the other?
  • Can the candidate pose questions about the dataset and explore the data for answers to those questions?
  • Are the methods the candidate uses to explore the data reasonable? This question primarily requires some basic analysis and data visualization.  If a candidate starts off with a more complex approach, there may be a missed opportunity for fast, early lessons from the data, aka “low-hanging fruit.”
  • Can the candidate support any observations with plots?
  • How does the candidate form inferences from the data and how well does that candidate apply statistics to defend their inferences?

Question 2: Forecasting Future Grocery Store Sales

Context

This example question uses one of the Getting Started competitions on Kaggle. The goal is to forecast future store sales for Corporación Favorita, a large Ecuadorian-based grocery retailer.

Data

  • train.csv: The training data, comprising time series of features store_nbr, family, and on promotion as well as the target sales
  • test.csv: The test data, having the same features as the training data but starts after the ending date of train data and for 15 dates. One has to predict the target sales for the dates in this file
  • stores.csv: This has some stores metadata including city, state, type, and cluster (grouping of similar stores)
  • oil.csv: This has oil price data as Ecuador economy is susceptible to volatility of oil market
  • holidays_events.csv: This has data on holidays and events in Ecuador

Directions

  • You are expected to do at least one completed time series analysis that predicts future sales.
  • You are expected to show any data transformations and exploratory analysis.
  • You have full flexibility to use the provided data as desired, but at minimum the date and sales numbers need to be used.
Initial cell contents

Please review the context and data overview in the Instructions panel in this pad to gain a basic understanding of the available data and this exercise.

# Following code loads useful libraries 

# Useful for out of the box time series function libraries
install.packages('fpp3')
library(fpp3)

library(tsibble)
library(tsibbledata)
library(tidyverse)
library(ggplot2)
# Reading all the input datasets into memory

df_train <- read_csv("/home/coderpad/app/store sales files/train.csv",show_col_types = FALSE) %>%
  mutate(store_nbr = as.factor(store_nbr))

df_test <- read_csv("/home/coderpad/app/store sales files/test.csv",show_col_types = FALSE) %>%
  mutate(store_nbr = as.factor(store_nbr))

df_stores <- read_csv("/home/coderpad/app/store sales files/stores.csv",show_col_types = FALSE) %>%
  mutate(store_nbr = as.factor(store_nbr))

df_transactions <- read_csv("/home/coderpad/app/store sales files/transactions.csv",show_col_types = FALSE)

df_oil <- read_csv("/home/coderpad/app/store sales files/oil.csv",show_col_types = FALSE)

df_holidays_events <- read_csv("/home/coderpad/app/store sales files/holidays_events.csv",show_col_types = FALSE)
# Show training data 
head(df_train)
# Example visual of total daily sales

# Converting data frame into a tsbibble object
train_tsbl <- df_train %>%
  as_tsibble(key = c(store_nbr, family), index = date) %>%
  fill_gaps(.full = TRUE)

train_tsbl[is.na(train_tsbl)] <- 0

# aggregate data by stores
train_tsbl <- train_tsbl %>%
  aggregate_key(store_nbr, sales = sum(sales))

options(repr.plot.width = 18, repr.plot.height = 6)
train_tsbl %>%
  filter(is_aggregated(store_nbr)) %>%
  ggplot(aes(x = date, y = sales)) + 
   geom_line(aes(group=1), colour="dark green") +
  labs(title = "Total Sales")

Success criteria

At minimum, a candidate should be able to conduct a basic time series analysis showing that they explored the data, transformed it appropriately for a time series analysis, considered a confounding factor like seasonality, and interpreted results in a reasonably accurate way.

For example:

  • Does the candidate know to address auto-correlated data?
  • Does the candidate explore the data to find any necessary transformations/clean up needed ahead of the analysis?
  • Can the candidate identify seasonal patterns among store sales?
  • Is the candidate able to justify their analysis approach and conclusions?
lightning bolt

Data science skills to assess

briefcase

Data science job roles

Junior data science interview question

Question: What is the difference between supervised and unsupervised learning?

Answer:

  • Supervised Learning: In this type of learning, an algorithm is trained on labeled data. Meaning, both input and the corresponding desired output is provided. The main goal is to learn a mapping from inputs to outputs. Examples include regression and classification.
  • Unsupervised Learning: Here, the algorithm is trained on data without explicit instructions on what to do. It finds patterns and relationships in the data on its own. Examples include clustering and association.

Question: Write a Python function that takes a list of numbers and returns its mean and sample standard deviation.

Answer:

import statistics

def mean_stddev(nums):
    return (
        statistics.mean(nums),
        statistics.stdev(nums)
    )

Question: What is cross-validation? Why is it useful?

Answer: Cross-validation is a technique used to assess how well a model will generalize to an independent dataset. It involves partitioning the original dataset into training and validation sets, training the model on the training set, and then evaluating it on the validation set. This process is typically repeated multiple times. It helps in understanding the model’s performance beyond the training data, reducing the chance of overfitting.

Question: Given a dataset data (a list of lists), write a Python function to transpose it.

Answer:

It is strongly advised to make sure all the sub-lists have the same number of elements before calling this function. Otherwise, some data will be lost during the transposition, due to the way the function zip works.

   def transpose(data):
       return [list(row) for row in zip(*data)]

Question: What is the difference between a parametric and a non-parametric algorithm?

Answer:

  • Parametric Algorithm: Makes an assumption about the functional form of the data distribution or decision boundary. These models have a fixed number of parameters. Examples include linear regression and logistic regression.
  • Non-parametric Algorithm: Does not make any assumptions about the data’s functional form. They can have a flexible number of parameters, which grow with the data. Examples include decision trees and kernel SVM.

Question: How would you calculate the median of a list of numbers?

Answer:

We can use the function statistics.median from the standard library:

from statistics import median

Question: What is the bias-variance trade-off?

Answer: The bias-variance trade-off refers to the balance between two sources of errors in models:

  • Bias: The error due to overly simplistic assumptions in the learning algorithm. High bias can lead to the model missing relevant patterns, causing underfitting.
  • Variance: The error due to the model’s complexity. High variance can lead the model to model the random noise in the training data, causing overfitting. Ideally, one wants to achieve a balance, where both bias and variance are minimized.

Question: Given a list of integers, use Python to count the frequency of each integer.

Answer:

from collections import Counter

def count_freq(nums):
    counter = Counter()
    counter.update(nums)
    return dict(counter)

Question: How would you handle missing data in a dataset?

Answer: Handling missing data is crucial for robust data analysis. Some common techniques include:

  • Imputation: Replace missing values with statistical measures like mean, median, or mode.
  • Deletion: Remove rows with missing values, especially if they’re a small subset of the data.
  • Prediction Models: Use algorithms to predict and fill the missing values.
  • Fill methods: For time series, forward fill or backward fill methods might be useful.
    The choice of method often depends on the nature of the data and the problem being addressed.

Question: Write a Python function to split a dataset into training and test sets, with 80% for training and 20% for testing.

Answer:

   import random

   def train_test_split(data):
       random.shuffle(data)
       split_idx = int(0.8 * len(data))
       return data[:split_idx], data[split_idx:]

Intermediate data science interview question

Question: Describe regularization in machine learning. Why is it important?

Answer: Regularization adds a penalty to the complexity of a model, reducing the risk of overfitting. Common techniques include Elastic Net Regression, L1 (Lasso) regularization, and L2 (Ridge) regularization. By constraining the magnitude of model parameters, regularization ensures simpler models and helps in preventing overfitting, especially when the number of features is high.

Question: Implement a Python function for the sigmoid function, often used in logistic regression.

Answer:

   import math

   def sigmoid(x):
       return 1 / (1 + math.exp(-x))

Question: What are ensemble methods and why might they be useful?

Answer: Ensemble methods combine multiple models to improve overall performance. Common techniques include Bagging (e.g., Random Forests) and Boosting (e.g., Gradient Boosted Trees). By aggregating predictions from multiple models, ensemble methods can achieve lower variance and/or bias, leading to better generalization on unseen data.

Question: Given two equal-length lists of true values y_true and predicted values y_pred, write a Python function to compute the Mean Squared Error (MSE).

Answer:

This function exists in scikit-learn:

from sklearn.metrics import mean_squared_error.

But since it is not in the standard python library, it may sometimes be needed to recode it ourselves, which would look something like this:

 def mse(y_true, y_pred):
    n = len(y_true)
    squared_errors = [(y_t - y_p) ** 2 for y_t, y_p in zip(y_true, y_pred)]
    return sum(squared_errors) / n

Question: Describe the differences between a batch gradient descent and a stochastic gradient descent.

Answer:

  • Batch Gradient Descent: Computes the gradient using the entire dataset. It can be computationally expensive for large datasets but provides a stable path towards the minimum.
  • Stochastic Gradient Descent (SGD): Computes the gradient using only a single data point or instance. It’s faster and can escape local minima, but the path to the global minimum is more erratic.

Question: Write a Python function to normalize (min-max scaling) a list of numbers to the range [0, 1].

Answer:

   def normalize(nums):
       min_val, max_val = min(nums), max(nums)
       return [(x - min_val) / (max_val - min_val) for x in nums]

Question: How does a Random Forest differ from a Decision Tree?

Answer: While both are tree-based algorithms, a Random Forest is an ensemble of Decision Trees. Here are key differences:

  • Random Forests build multiple trees, usually with bootstrapped samples of data, and aggregate their predictions. They also randomly select a subset of features at each split.
  • Decision Trees just build a single tree. They might be prone to overfitting, especially with deep trees, while Random Forests, through aggregation, generally yield better performance and are less prone to overfitting.

Question: Implement a Python function that calculates the cosine similarity between two vectors.

Answer: If you’re not using the skikit-learn library, your answer would look something like this:

   def cosine_similarity(v1, v2):
       dot_product = sum(i*j for i, j in zip(v1, v2))
       magnitude_v1 = sum(i*i for i in v1) ** 0.5
       magnitude_v2 = sum(i*i for i in v2) ** 0.5
       return dot_product / (magnitude_v1 * magnitude_v2)

Question: Describe the k-means clustering algorithm. When might it be used?

Answer: k-means is an iterative algorithm that partitions a dataset into ‘k’ distinct non-overlapping clusters based on distance (typically Euclidean). The algorithm assigns each data point to the nearest cluster centroid, then recalculates centroids, and repeats until convergence. It’s used for unsupervised clustering tasks when the number of clusters is known or can be estimated.

Question: Given a Pandas DataFrame df with a column ‘age’, write a Python function to bin this column into three age groups: ‘young’ (age < 30), ‘middle-aged’ (30 <= age < 60), and ‘senior’ (age >= 60).

Answer:

   import pandas as pd

   def bin_age(df):
       age_bins = [0, 30, 60, float('inf')]
       labels = ['young', 'middle-aged', 'senior']
       df['age_group'] = pd.cut(df['age'], bins=age_bins, labels=labels, right=False)
       return df

Senior data science interview question

Question: Given a Pandas DataFrame df that contains missing values, write a Python function to impute missing values using the median for numerical columns and the mode for categorical columns.

Answer:

   import pandas as pd

   def impute_missing(df):
       for col in df.columns:
           if df[col].dtype == 'O':  # Categorical column
               mode = df[col].mode().iloc[0]
               df[col].fillna(mode, inplace=True)
           else:
               median = df[col].median()
               df[col].fillna(median, inplace=True)
       return df

Question: How do you handle the challenge of data imbalance in classification tasks?

Answer: Data imbalance can lead to a model that’s biased towards the majority class. Techniques to handle it include:

  • Resampling: Either oversampling the minority class or undersampling the majority.
  • Using different evaluation metrics: Like F1-score, ROC-AUC instead of accuracy.
  • Algorithms that handle imbalance: Like SMOTE or ADASYN for oversampling.
  • Cost-sensitive learning: Where you assign higher penalties for misclassifying the minority class.

Question: Assuming you have access to popular machine learning Python libraries, how can you compute the Area Under the Receiver Operating Characteristic Curve (AUC-ROC), from two equally sized lists: one with true values and the other with predicted probabilities ?

Answer: This function exists in scikit-learn : sklearn.metrics.roc_auc_score.

Question: How do you determine the number of clusters to be used in a k-means clustering algorithm?

Answer:

  • Elbow method: Plot the sum of squared distances for different numbers of clusters and find the “elbow” point.
  • Silhouette method: Measures how similar an object is to its own cluster compared to other clusters.
  • Gap statistic: Compares the total intra-cluster variation for different values of k with that of a random clustering.

Question: Write a Python function to perform a grid search over given hyperparameters for a Gradient Boosting Classifier, returning the best parameters.

Answer:

   from sklearn.ensemble import GradientBoostingClassifier
   from sklearn.model_selection import GridSearchCV

   def grid_search_gbc(X, y, param_grid):
       gbc = GradientBoostingClassifier()
       grid_search = GridSearchCV(gbc, param_grid, cv=5)
       grid_search.fit(X, y)
       return grid_search.best_params_

Question: What is the concept of “Transfer Learning”? Can you give an example scenario where it can be useful?

Answer: Transfer learning leverages knowledge from a previously trained model on a different but often related task. It’s beneficial when data for the new task is scarce. For instance, a model trained on a large dataset of generic images can be fine-tuned on a smaller dataset of medical images, transferring knowledge of features from general images to the specific task.

Question: Given a Pandas DataFrame df with a column ‘text_data’, write a Python function to tokenize this column into words and remove common stopwords.

Answer:

   import pandas as pd
   from nltk.corpus import stopwords
   from nltk.tokenize import word_tokenize

   def tokenize_remove_stopwords(df):
       stop_words = set(stopwords.words('english'))
       df['tokens'] = df['text_data'].apply(word_tokenize)
       df['tokens'] = df['tokens'].apply(lambda x: [word for word in x if word.lower() not in stop_words])
       return df

Question: Discuss the trade-offs between a parametric model like Logistic Regression and a non-parametric model like k-Nearest Neighbors (k-NN).

Answer:

  • Logistic Regression: Assumes a linear boundary between classes. It’s computationally efficient during prediction and requires less storage space. However, it might underfit if the relationship isn’t approximately linear.
  • k-NN: Doesn’t assume any functional form for the decision boundary, making it more flexible. However, it requires storing the entire dataset and can be computationally intensive during prediction, especially with large datasets.

Question: Write a Python function to calculate the F1-score given a 2×2 confusion matrix.

Answer:

   def compute_f1_from_confusion_matrix(cm):
       true_positive = cm[1][1]
       false_positive = cm[0][1]
       false_negative = cm[1][0]
       precision = true_positive / (true_positive + false_positive)
       recall = true_positive / (true_positive + false_negative)
       f1_score = 2 * precision * recall / (precision + recall)
       return f1_score

More data science interview resources

For more guides on improving your knowledge of data science hiring, we have outlined helpful resources below:

1,000 Companies use CoderPad to Screen and Interview Developers

Best interview practices for data science roles


The data science interview process can be quite diverse, shaped by variables such as the specific role in question and the applicant’s level of expertise. To maximize the effectiveness of your data science interview questions, adhere to the following guidelines when engaging with potential hires:

  • Develop technical questions that reflect genuine scenarios encountered within your organization – this strategy is not only more engaging for the candidate but also aids in determining how well their skills align with your team’s needs.
  • Encourage an interactive environment by inviting the candidate to ask questions at any point during the discussion.
  • Often times candidates are working with large data sets, so knowledge of big data processing platforms like Hadoop and Spark may be beneficial.

Furthermore, it is vital to uphold established interview norms during data science interviews – adapt the complexity of interview questions to match the candidate’s evolving level of expertise, provide prompt feedback on their status in the recruitment process, and allow sufficient opportunities for candidates to discuss the evaluation or delve deeper into the particulars of working with your team.

]]>
Django https://coderpad.io/interview-questions/django-interview-questions/ Fri, 14 Apr 2023 16:59:16 +0000 https://coderpad.io/?post_type=interview-questions&p=32897 Django is a Python-based back-end framework known for it’s emphasis on promoting code reusability (the Don’t Repeat Yourself principle), clear & extensive developer documentation, and ability to quickly scale.

According to the CoderPad 2024 Developer survey, Django is the 3rd most in-demand back-end framework among technical recruiters and hiring managers.

To evaluate the Django expertise of developers during coding interviews, below you’ll find hands-on coding challenges and interview questions. Additionally, we have outlined a set of suggested practices to ensure that your interview questions accurately measure the candidates’ Django skillset.

Django example question

Create a polling application

Here is a simple but realistic Django project, which allows people to vote on polls.

You have two simple tasks to perform on this project:

  • Add a database migration to populate the database with a poll of your choice.
  • Edit the source code so that people can vote for multiple options instead of just one.

Feel free to take some time to familiarize yourself with the environment before starting the assignment.

Junior Django interview questions

Question:
The following Django view function is giving an error. Identify and fix the issue.

from django.http import HttpResponse

def my_view(request):
    return HttpResponse("Hello, Django!")

Answer:
The code seems fine. However, make sure that you have the necessary Django dependencies installed and that the urlpatterns in your project’s urls.py file is properly configured to route the request to this view function.

Question:
What is the purpose of Django’s ORM (Object-Relational Mapping)?

Answer:
Django’s ORM provides a high-level, Pythonic way to interact with databases. It allows developers to work with database records as Python objects, abstracting away the complexities of SQL queries and database management.

Question:
The following Django model has a mistake. Identify and fix the issue.

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey('Author', on_delete=models.CASCADE)
    publication_date = models.DateField()

Answer:
The code is missing the definition of the Author model. Make sure to import and define the Author model before using it as a foreign key in the Book model.

Question:
Explain the concept of Django’s Migrations.

Answer:
Django’s Migrations are a built-in feature that allows developers to manage database schema changes over time. Migrations help in creating, updating, and deleting database tables, fields, and relationships without manually writing SQL scripts.

Question:
The following Django template code is not rendering the variable correctly. Identify and fix the issue.

<!DOCTYPE html>
<html>
<head>
    <title>My Website</title>
</head>
<body>
    <h1>Welcome, {{ name }}</h1>
</body>
</html>

Answer:
To render a variable in a Django template, you need to pass a context dictionary to the template rendering function, where the variable name is defined. Make sure you are passing the context correctly while rendering the template.

Question:
What is the purpose of Django’s URLconf?

Answer:
Django’s URLconf is a module that defines the mapping between URLs and view functions. It helps in routing incoming HTTP requests to the appropriate view functions or class-based views, allowing developers to create clean and maintainable URL structures.

Question:
Fix the code: The following Django form is not saving data to the database. Identify and fix the issue.

from django import forms
from .models import Person

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        fields = ('name', 'age', 'email')

Answer:
The code is missing the save() method in the PersonForm class. Add the save() method to save the form data to the database. For example:

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        fields = ('name', 'age', 'email')

    def save(self, commit=True):
        person = super().save(commit=False)
        if commit:
            person.save()
        return person

Question:
What is the purpose of Django’s middleware?

Answer:
Django’s middleware is a mechanism that enables developers to process requests and responses globally across the entire Django application. Middleware can perform operations such as authentication, request/response modification, and error handling.

Question:
The following Django view is not retrieving data from the database correctly. Identify and fix the issue.

from

 django.shortcuts import render
from .models import Product

def product_list(request):
    products = Product.objects.filter(category='Electronics')
    return render(request, 'product_list.html', {'products': products})

Answer:
The code is filtering the Product objects incorrectly. When comparing string values, use double underscores (__) to indicate the field lookup. Modify the filter line to the following:

products = Product.objects.filter(category__exact='Electronics')

Question:
Explain the role of Django’s template engine.

Answer:
Django’s template engine is responsible for rendering dynamic web pages by combining templates (HTML files with placeholders) with data from the view functions. It allows developers to separate the presentation logic from the business logic and enables the reuse of templates across different views.

Intermediate Django interview questions

Question:
The following Django view function is throwing an error. Identify and fix the issue.

from django.shortcuts import render
from .models import Post

def post_detail(request, post_id):
    post = Post.objects.get(id=post_id)
    return render(request, 'post_detail.html', {'post': post})

Answer:
The code is assuming that a Post object with the given post_id exists in the database. To handle the case when the Post object does not exist, you can use the get_object_or_404 shortcut from django.shortcuts. Import it and modify the view function as follows:

from django.shortcuts import render, get_object_or_404
from .models import Post

def post_detail(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    return render(request, 'post_detail.html', {'post': post})

Question:
Explain the purpose of Django’s authentication system.

Answer:
Django’s authentication system provides a secure and flexible way to manage user authentication and authorization. It includes features such as user registration, login, logout, password management, and permission-based access control.

Question:
The following Django model has a mistake that is causing a database error. Identify and fix the issue.

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=50, unique=True)
    parent = models.ForeignKey('Category', on_delete=models.CASCADE, null=True, blank=True)

Answer:
The ForeignKey relationship in the Category model is self-referential, indicating a hierarchical structure. However, the on_delete=models.CASCADE is not suitable in this case because it would cause the deletion of parent categories to also delete their child categories. Change the on_delete argument to models.PROTECT to prevent accidental deletion of parent categories.

parent = models.ForeignKey('Category', on_delete=models.PROTECT, null=True, blank=True)

Question:
What are Django signals, and how are they used?

Answer:
Django signals are decoupled notifications that allow certain senders to notify a set of receivers when a particular action occurs. Signals are used to enable decoupled applications, allowing different parts of an application to communicate and respond to events without being tightly coupled to each other.

Question:
The following Django template code is not displaying the looped data correctly. Identify and fix the issue.

<ul>
    {% for category in categories %}
        <li>{{ category.name }}</li>
    {% endfor %}
</ul>

Answer:
The code assumes that the categories variable in the template context is a list. However, if it is a queryset, you need to iterate over it using the .all method. Modify the template code as follows:

<ul>
    {% for category in categories.all %}
        <li>{{ category.name }}</li>
    {% endfor %}
</ul>

Question:
Explain the purpose of Django’s middleware classes.

Answer:
Django’s middleware classes provide hooks for processing requests and responses globally across the entire Django application. They allow developers to modify request and response objects, perform authentication, caching, compression, and other operations at various stages of the request/response lifecycle.

Question:
Fix the code: The following Django form is not saving data correctly. Identify and fix the issue.

from django import forms
from .models import UserProfile

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile


        fields = ('name', 'email')

    def save(self, commit=True):
        user_profile = super().save(commit=False)
        if commit:
            user_profile.save()
        return user_profile

Answer:
The code correctly defines the save() method, but it is missing the commit argument in the super().save() call. Modify the save() method as follows:

def save(self, commit=True):
    user_profile = super().save(commit=commit)
    return user_profile

Question:
What are Django’s class-based views, and what advantages do they offer over function-based views?

Answer:
Django’s class-based views are an alternative way to define views using Python classes instead of functions. They offer advantages such as code reuse, modularity, and the ability to override specific methods to customize behavior at different stages of the view lifecycle. Class-based views also provide built-in mixins for common functionalities, making them highly flexible.

Question:
The following Django view is not handling form validation errors correctly. Identify and fix the issue.

from django.shortcuts import render
from .forms import ContactForm

def contact_view(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            # Process the form data
            return render(request, 'success.html')
    else:
        form = ContactForm()

    return render(request, 'contact.html', {'form': form})

Answer:
The code is missing the case to handle form validation errors. If the form is not valid, it should be re-rendered with the validation errors. Modify the code as follows:

from django.shortcuts import render
from .forms import ContactForm

def contact_view(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            # Process the form data
            return render(request, 'success.html')
    else:
        form = ContactForm()

    return render(request, 'contact.html', {'form': form})

Question:
What is Django’s caching framework, and how can it improve the performance of a web application?

Answer:
Django’s caching framework provides a flexible way to store and retrieve dynamic data, such as database query results or rendered templates, in memory or other caching backends. Caching can significantly improve the performance of a web application by reducing the time required to generate responses and minimizing the load on the underlying resources.

Senior Django interview questions

Question:
The following Django view is performing multiple database queries, which can be optimized. Identify and fix the issue.

from django.shortcuts import render
from .models import Category, Product

def category_products(request, category_id):
    category = Category.objects.get(id=category_id)
    products = Product.objects.filter(category=category)
    return render(request, 'category_products.html', {'category': category, 'products': products})

Answer:
The code can be optimized to perform a single database query using Django’s select_related method. Modify the view function as follows:

from django.shortcuts import render
from .models import Category, Product

def category_products(request, category_id):
    category = Category.objects.select_related('product_set').get(id=category_id)
    products = category.product_set.all()
    return render(request, 'category_products.html', {'category': category, 'products': products})

Question:
What is Django REST framework, and what are its main features?

Answer:
Django REST framework is a powerful toolkit for building Web APIs in Django. Its main features include support for serialization, authentication, permissions, viewsets, routers, and content negotiation. It simplifies the process of building APIs by providing a set of reusable components and enforcing best practices.

Question:
The following Django model has a mistake that is causing a performance issue. Identify and fix the issue.

from django.db import models

class Order(models.Model):
    user = models.ForeignKey('User', on_delete=models.CASCADE)
    products = models.ManyToManyField('Product')
    created_at = models.DateTimeField(auto_now_add=True)

Answer:
The Order model is missing an index on the user field, which could cause performance issues when querying orders by user. Add the db_index=True option to the user field to create an index.

user = models.ForeignKey('User', on_delete=models.CASCADE, db_index=True)

Question:
Explain the concept of Django’s middleware hooks.

Answer:
Django’s middleware hooks allow developers to process requests and responses at various stages of the request/response lifecycle. Middleware classes can intercept and modify requests before they reach the view, as well as modify responses before they are sent back to the client. Middleware hooks provide a flexible way to implement cross-cutting concerns, such as authentication, caching, and request/response manipulation.

Question:
The following Django template code is not rendering the date correctly. Identify and fix the issue.

<p>The current date is: {{ current_date }}</p>

Answer:
To render the date correctly in the template, use Django’s template tags and filters. Modify the template code as follows:

{% load tz %}
<p>The current date is: {{ current_date|timezone:"America/New_York" }}</p>

This example assumes that you want to display the current date in the “America/New_York” timezone. Adjust the timezone filter to your desired timezone.

Question:
What are Django’s database transactions, and how can they ensure data consistency?

Answer:
Django’s database transactions provide a way to group multiple database operations into a single atomic unit. Transactions ensure data consistency by guaranteeing that either all the operations within the transaction are applied, or none of them are. In case of failures or errors, transactions can be rolled back to restore the database to its original state.

Question:
The following Django form is not properly handling file uploads. Identify and fix the issue.

from django import forms
from .models

 import Document

class DocumentForm(forms.ModelForm):
    class Meta:
        model = Document
        fields = ('title', 'file')

Answer:
To properly handle file uploads, the enctype attribute of the HTML form needs to be set to 'multipart/form-data'. Modify the form class as follows:

from django import forms
from .models import Document

class DocumentForm(forms.ModelForm):
    class Meta:
        model = Document
        fields = ('title', 'file')
        widgets = {
            'file': forms.ClearableFileInput(attrs={'multiple': True})
        }

In the template, make sure to add the enctype attribute to the form tag:

<form method="POST" enctype="multipart/form-data">
    <!-- Form fields go here -->
</form>

Question:
Explain the purpose of Django’s context processors and provide an example use case.

Answer:
Django’s context processors are functions that add variables to the context of every template rendered. They provide a convenient way to include common context variables across multiple templates. For example, a context processor can add the current user or the site’s configuration settings to the template context, eliminating the need to manually include these variables in each view.

Question:
The following Django view is performing a potentially expensive calculation repeatedly. Identify and fix the issue.

from django.shortcuts import render

def calculate_stats(request):
    data = get_expensive_data()  # Expensive calculation
    stats = {
        'average': calculate_average(data),
        'max': calculate_max(data),
        'min': calculate_min(data),
    }
    return render(request, 'stats.html', {'stats': stats})

Answer:
To avoid performing the expensive calculation multiple times, you can cache the result using Django’s caching framework. Modify the view function as follows:

from django.shortcuts import render
from django.core.cache import cache

def calculate_stats(request):
    data = cache.get('expensive_data')
    if data is None:
        data = get_expensive_data()  # Expensive calculation
        cache.set('expensive_data', data, 3600)  # Cache for 1 hour

    stats = {
        'average': calculate_average(data),
        'max': calculate_max(data),
        'min': calculate_min(data),
    }
    return render(request, 'stats.html', {'stats': stats})

This example caches the result of the expensive calculation for 1 hour using Django’s default cache backend. Adjust the caching parameters as per your requirements.

Question:
What are Django’s management commands, and how can they be used?

Answer:
Django’s management commands are custom scripts that can be executed from the command line to perform administrative tasks. They provide a convenient way to automate common operations such as database migrations, data imports/exports, and cron-like tasks. Management commands can be created using Django’s command-line utility (manage.py) and can be integrated into deployment scripts or scheduled with tools like cron or Celery.

More Django interview resources

For more guides on improving your knowledge of Django and acing interviews, we have outlined helpful blog posts below:

1,000 Companies use CoderPad to Screen and Interview Developers

Interview best practices for Django roles

For successful Django interviews, taking into account multiple aspects such as the applicant’s experience level and the engineering position is crucial. To guarantee that your Django interview inquiries produce optimal outcomes, we suggest adhering to the following best practices when engaging with candidates:

  • Devise technical queries that correspond with real-world business scenarios within your organization. This approach not only makes the interview more captivating for the applicant but also allows you to more effectively gauge their fit for your team.
  • Encourage the candidate to pose questions throughout the interview and cultivate a cooperative atmosphere.
  • If using Django as part of a full stack, ensure that your developers also have understanding of front-end technologies like HTML/CSS and of database knowledge, especially with the module Django ORM (object-relational mapper).

Moreover, it is essential to follow conventional interview practices when carrying out Django interviews. This encompasses tailoring the question difficulty according to the applicant’s development skill level, offering prompt feedback regarding their application status, and permitting candidates to inquire about the evaluation or collaborating with you and your team.

]]>
Elixir https://coderpad.io/interview-questions/elixir-interview-questions/ Wed, 19 Apr 2023 13:22:52 +0000 https://coderpad.io/?post_type=interview-questions&p=33281 This Erlang-based language is noted for its fault tolerance capabilities, its efficient scalability and concurrency, and its ease of use due to its expressive and concise syntax.

Elixir was created by José Valim, a Brazilian software engineer, in 2011. His goal was to develop a language that combined the strengths of the Erlang/OTP platform with a more modern and expressive syntax, improving developer productivity and making it more accessible.

https://changelog.com/podcast/179

In order to assess the proficiency of developers in Elixir during programming interviews, we offer a collection of practical coding tasks and interview queries below.

Moreover, we’ve devised a serie of recommended approaches to guarantee that your interview questions effectively gauge the candidates’ Elixir abilities.

Elixir example question

Help us design a parking lot app

Hey candidate! Welcome to your interview. Boilerplate is provided. Feel free to change the code as you see fit. To run the code at any time, please hit the run button located in the top left corner.

Goals: Design a parking lot using object-oriented principles

Here are a few methods that you should be able to run:

  • Tell us how many spots are remaining
  • Tell us how many total spots are in the parking lot
  • Tell us when the parking lot is full
  • Tell us when the parking lot is empty
  • Tell us when certain spots are full e.g. when all motorcycle spots are taken
  • Tell us how many spots vans are taking up

Assumptions:

  • The parking lot can hold motorcycles, cars and vans
  • The parking lot has motorcycle spots, car spots and large spots
  • A motorcycle can park in any spot
  • A car can park in a single compact spot, or a regular spot
  • A van can park, but it will take up 3 regular spots
  • These are just a few assumptions. Feel free to ask your interviewer about more assumptions as needed

Junior Elixir interview questions

Question: Fix the code below:

defmodule MathFunctions do
  def multiply(a, b) do
    a + b
  end
end

IO.puts(MathFunctions.multiply(3, 4)) # Expected output: 12

Answer:

defmodule MathFunctions do
  def multiply(a, b) do
    a * b
  end
end

IO.puts(MathFunctions.multiply(3, 4)) # Output: 12

Question:
What is immutability in Elixir, and why is it important?
Answer:
In Elixir, immutability means that once a value is assigned to a variable, it cannot be changed. Instead, new values are created based on existing ones. Immutability is important because it guarantees that data remains consistent and avoids unexpected side effects. It enables safer concurrent programming and helps reason about code.

Question: Fix the code:

defmodule ListFunctions do
  def subtract_one(list) do
    Enum.map(list, &(&1 + 1))
  end
end

IO.inspect(ListFunctions.subtract_one([1, 2, 3])) # Expected output: [0, 1, 2]

Answer:

defmodule ListFunctions do
  def subtract_one(list) do
    Enum.map(list, &(&1 - 1))
  end
end

IO.inspect(ListFunctions.subtract_one([1, 2, 3])) # Output: [0, 1, 2]

Question:

What are OTP behaviors in Elixir, and how do they facilitate building concurrent systems?
Answer:
OTP (Open Telecom Platform) behaviors are higher-level abstractions provided by the OTP framework. They encapsulate common patterns for building concurrent and fault-tolerant systems. OTP behaviors, such as GenServer and Supervisor, provide ready-to-use implementations that handle message passing, state management, and supervision. They simplify the development of concurrent systems and ensure consistency and fault tolerance.

Question: Fix the code below:

defmodule StringFunctions do
  def capitalize(string) do
    string |> String.upcase()
  end
end

IO.puts(StringFunctions.capitalize("elixir")) # Expected output: "Elixir"

Answer:

defmodule StringFunctions do
  def capitalize(string) do
    string |> String.capitalize()
  end
end

IO.puts(StringFunctions.capitalize("elixir")) # Output: "Elixir"

Question:

What are Elixir processes, and how do they enable concurrency?
Answer:
Elixir processes are lightweight, isolated units of execution that run concurrently. They are managed by the Erlang VM and communicate with each other using message passing. Elixir processes allow programs to execute tasks concurrently, improving performance and responsiveness. They enable the development of highly concurrent systems by providing a scalable and fault-tolerant concurrency model.

Question: Fix the code below:

defmodule MathFunctions do
  def subtract(a, b) do
    a + b
  end
end

IO.puts(MathFunctions.subtract(5, 3)) # Expected output: 2

Answer:

defmodule MathFunctions do
  def subtract(a, b) do
    a - b
  end
end

IO.puts(MathFunctions.subtract(5, 3)) # Output: 2

Question:
What is the purpose of GenServer in Elixir, and how does it handle state and message passing?
Answer:
GenServer is a behavior in OTP that provides a client-server model for managing state and handling message passing. It allows developers to build concurrent, stateful processes that can receive and reply to messages. GenServer abstracts away the complexities of managing state and provides a standard interface for handling asynchronous requests and maintaining state consistency.

Question: Fix the code:

defmodule MathFunctions do
  def divide(a, b) do
    a / b
  end
end

IO.puts(MathFunctions.divide(10, 2)) # Expected output: 5

Answer:

defmodule MathFunctions do
  def divide(a, b) do
    div(a, b)
  end
end

IO.puts(MathFunctions.divide(10, 2)) # Output: 5

Question:

What is pattern matching in Elixir, and how is it used in function clauses?
Answer:
Pattern matching in Elixir allows matching against different patterns and executing the corresponding code block. In function clauses, pattern matching is used to define multiple function heads with different patterns of arguments. Elixir matches the input against the defined function clauses and invokes the one that matches. This enables polymorphism and the handling of different cases based on input patterns.

Intermediate Elixir interview questions

Question: How else can the function sum_list below be rewritten?

defmodule MathFunctions do
  def sum_list(list) do
    Enum.reduce(list, 0, fn(x, acc) -> acc + x end)
  end
end

IO.puts(MathFunctions.sum_list([1, 2, 3, 4])) # Expected output: 10

Answer:

defmodule MathFunctions do
  def sum_list(list) do
    Enum.reduce(list, 0, &(&1 + &2))
  end
end

IO.puts(MathFunctions.sum_list([1, 2, 3, 4])) # Output: 10

Question:

Explain the concept of a GenStage in Elixir and how it can be used for backpressure in data processing pipelines.
Answer:
GenStage is a specification and set of behaviors in Elixir that allows building data processing pipelines with built-in backpressure support. It facilitates the flow of data between producers and consumers, ensuring that the rate of data production matches the consumption rate. Producers and consumers communicate through demand-driven protocols, enabling efficient and controlled data processing in systems that require flow control.

Question:

defmodule StringFunctions do
  def split_string(string, separator) do
    String.split(separator, string)
  end
end

IO.inspect(StringFunctions.split_string("Hello, World!", ",")) # Expected output: ["Hello", " World!"]

Answer:

defmodule StringFunctions do
  def split_string(string, separator) do
    String.split(string, separator)
  end
end

IO.inspect(StringFunctions.split_string("Hello, World!", ",")) # Output: ["Hello", " World!"]

Question:
What are the advantages of using ETS (Erlang Term Storage) tables in Elixir? Provide an example use case.
Answer:
ETS tables provide a way to store large amounts of data in-memory, accessible by multiple processes. The advantages of using ETS tables in Elixir include fast read and write operations, concurrent access, and reduced garbage collection overhead. An example use case is caching frequently accessed data, such as configuration settings or database query results, to improve performance and reduce external dependencies.

Question:

defmodule MathFunctions do
  def calculate_average(list) do
    sum = Enum.sum(list)
    length = Enum.count(list)
    sum / length + 1
  end
end

IO.puts(MathFunctions.calculate_average([1, 2, 3, 4])) # Expected output: 2.5

Answer:

defmodule MathFunctions do
  def calculate_average(list) do
    sum = Enum.sum(list)
    length = length(list)
    sum / length
  end
end

IO.puts(MathFunctions.calculate_average([1, 2, 3, 4])) # Output: 2.5

Question:
Explain the concept of fault tolerance in Elixir and how it is achieved through supervision trees.
Answer:
Fault tolerance in Elixir refers to the ability of a system to recover from failures and continue operating. It is achieved through supervision trees, where processes are organized hierarchically under supervisors. Supervisors monitor their child processes and handle failures by restarting or terminating them. By using supervision trees, Elixir applications can automatically recover from errors and maintain system stability.

Question:

defmodule Counter do
  defstruct count: 0
  def increment(counter) do
    %{counter | count: counter.count + 1}
  end
end

counter = %Counter{}
counter = Counter.increment(counter)
IO.inspect(counter.count) # Expected output: 1

Answer:

defmodule Counter do
  defstruct count: 0
  def increment(counter) do
    %{counter | count: counter.count + 1}
  end
end

counter = %Counter{}
counter = Counter.increment(counter)
IO.inspect(counter.count) # Output: 1

Question:
What is the purpose of OTP applications in Elixir, and how are they structured?
Answer:
OTP (Open Telecom Platform) applications in Elixir are a way to package and manage reusable components of an Elixir system. They provide a structured way to organize code, configuration, and dependencies. OTP applications consist of a mix.exs file specifying dependencies and metadata, lib directory containing the application’s modules, and other optional directories for configuration, documentation, and tests. OTP applications can be easily started, stopped, and managed using tools like mix and releases.

Question:

defmodule StringFunctions do
  defp is_palindrome?(string) do
    string == String.reverse(string)
  end
end

IO.puts(StringFunctions.is_palindrome?("racecar")) # Expected output: true

Answer:

defmodule StringFunctions do
  def is_palindrome?(string) do
    string == String.reverse(string)
  end
end

IO.puts(StringFunctions.is_palindrome?("racecar")) # Output: true

Question:

What is the role of the BEAM (Erlang virtual machine) in Elixir, and how does it contribute to Elixir’s scalability and fault tolerance?
Answer:
The BEAM is the virtual machine on which Elixir runs, inheriting its characteristics from Erlang. It provides features like lightweight processes, preemptive scheduling, and message passing. These features enable Elixir to handle massive concurrency, distribute work across multiple cores and machines, and build fault-tolerant systems. The BEAM’s built-in mechanisms for process isolation, error handling, and supervision make it well-suited for building scalable and resilient applications.

Senior Elixir interview questions


Question:
The code snippet below attempts to calculate the sum of a list of numbers. However, when invoking MathFunctions.sum([1, 2, 3, 4, 5]), it doesn’t produce the expected output. Fix the code to correctly calculate the sum and return the expected output.

defmodule MathFunctions do
  def sum(list) do
    Enum.reduce(list, 0, &+/2)
  end
end

IO.puts(MathFunctions.sum([1, 2, 3, 4, 5])) # Expected output: 15

Answer:

defmodule MathFunctions do
  def sum(list) do
    Enum.sum(list)
  end
end

IO.puts(MathFunctions.sum([1, 2, 3, 4, 5])) # Output: 15

Question:
Explain the concept of concurrency in Elixir. How does Elixir handle concurrency, and what are the benefits of concurrent programming in Elixir?

Answer:
Concurrency in Elixir refers to the ability to execute multiple computations simultaneously. Elixir handles concurrency through lightweight processes, also known as “actors.” These processes are isolated and communicate with each other through message passing.

Elixir uses the Actor Model, where each process has its own memory and executes independently. Processes communicate by sending and receiving messages, allowing for loose coupling and fault-tolerant systems.

The benefits of concurrent programming in Elixir include:

  • Scalability: Elixir’s concurrency model allows developers to easily distribute work across multiple processes and utilize multiple CPU cores. This enables building scalable systems that can handle high loads and efficiently utilize available resources.
  • Fault Tolerance: Elixir’s supervision mechanisms and isolated processes provide built-in fault tolerance. If a process fails, it can be restarted by a supervisor without affecting other parts of the system. This makes it easier to build robust and fault-tolerant applications.
  • Responsiveness: Concurrency enables handling multiple requests or events simultaneously. By leveraging lightweight processes, Elixir can handle concurrent tasks efficiently, resulting in responsive systems that can handle high throughput and low latency requirements.

Question:

The code snippet below attempts to find the maximum element in a list of numbers. However, when invoking MathFunctions.max([3, 7, 2, 9, 5]), it doesn’t produce the expected output. Fix the code to correctly find the maximum number and return the expected output.

defmodule MathFunctions do
  def max(list) do
    Enum.reduce(list, 0, fn(x, acc) -> if x < acc, do: x, else: acc end)
  end
end

IO.puts(MathFunctions.max([3, 7, 2, 9, 5])) # Expected output: 9

Answer:

defmodule MathFunctions do
  def max(list) do
    Enum.max(list)
  end
end

IO.puts(MathFunctions.max([3, 7, 2, 9, 5])) # Output: 9

Question:
What is metaprogramming in Elixir? Explain the concept and the role of macros in Elixir.

Answer:
Metaprogramming in Elixir refers to the ability to write code that generates or manipulates other code at compile-time. Elixir provides macros as a powerful metaprogramming tool.

Macros in Elixir are functions that transform and generate code. They operate on the abstract syntax tree (AST) of the code and are expanded during compilation. Macros are defined using the defmacro construct and can be invoked using the macro keyword.

Macros allow developers to define new constructs, domain-specific languages (DSLs), and abstractions specific to their application’s needs. They provide a way to extend the language itself without modifying the compiler.

Question:

The code snippet below attempts to reverse a given string. However, when invoking StringFunctions.reverse("Hello, World!"), it doesn’t produce the expected output. Fix the code to correctly reverse the string and return the expected output.

defmodule StringFunctions do
  def reverse(string) do
    Enum.reduce(string, "", fn(x, acc) -> acc <> x end)
  end
end

IO.puts(StringFunctions.reverse("Hello, World!")) # Expected output: "!dlroW ,olleH"

Answer:

defmodule StringFunctions do
  def reverse(string) do
    String.reverse(string)
  end
end

IO.puts(StringFunctions.reverse("Hello, World!")) # Output: "!dlroW ,olleH"

Question:
What is OTP (Open Telecom Platform) in Elixir? Explain its purpose and the key components it provides for building fault-tolerant systems.

Answer:
OTP (Open Telecom Platform) in Elixir is a set of libraries, tools, and design principles that facilitate the building of robust, fault-tolerant, and scalable systems. It is a part of the Erlang ecosystem and extends to Elixir.

The purpose of OTP is to provide abstractions and standardized patterns for building concurrent and distributed applications. OTP promotes the “Let it crash” philosophy, where processes are isolated and allowed to fail while supervisors monitor and handle the failures.

Key components of OTP include:

  • Supervisors: Supervisors are responsible for starting, stopping, and restarting processes in a hierarchical manner. They define restart strategies and supervise the overall system’s health.
  • GenServers: GenServers are generic servers that encapsulate state, handle requests, and provide a callback interface for message handling. They are used for building stateful components.
  • Applications: OTP applications provide a way to organize and manage the codebase. Applications define the structure, dependencies, and configuration of a system.
  • Behaviors: OTP behaviors are predefined sets of callbacks that help developers implement common patterns. Examples include GenServer, Supervisor, and Application behaviors.

By leveraging OTP, developers can build fault-tolerant systems with built-in error handling, scalability, and distribution capabilities.

Question:
The code snippet below attempts to filter out even numbers from a given list. However, when invoking ListFunctions.filter_even([1, 2, 3, 4, 5]), it doesn’t produce the expected output. Fix the code to correctly filter the even numbers and return the expected output.

defmodule ListFunctions do
  def filter_even(list) do
    Enum.filter(list, fn(x) -> rem(x, 2) == 0 end)
  end
end

IO.inspect(ListFunctions.filter_even([1, 2, 3, 4, 5])) # Expected output: [2, 4]

Answer:

defmodule ListFunctions do
  def filter_even(list) do
    Enum.filter(list, &rem(&1, 2) == 0)
  end
end

IO.inspect(ListFunctions.filter_even([1, 2, 3, 4, 5])) # Output: [2, 4]

Question:
What is OTP (Open Telecom Platform) in Elixir? Explain its purpose and the key components it provides for building fault-tolerant systems.

Answer:
OTP (Open Telecom Platform) in Elixir is a set of libraries, tools, and design principles that facilitate the building of robust, fault-tolerant, and scalable systems. It is a part of the Erlang ecosystem and extends to Elixir.

The purpose of OTP is to provide abstractions and standardized patterns for building concurrent and distributed applications. OTP promotes the “Let it crash” philosophy, where processes are isolated and allowed to fail while supervisors monitor and handle the failures.

Key components of OTP include:

  • Supervisors: Supervisors are responsible for starting, stopping, and restarting processes in a hierarchical manner. They define restart strategies and supervise the overall system’s health.
  • GenServers: GenServers are generic servers that encapsulate state, handle requests, and provide a callback interface for message handling. They are used for building stateful components.
  • Applications: OTP applications provide a way to organize and manage the codebase. Applications define the structure, dependencies, and configuration of a system.
  • Behaviors: OTP behaviors are predefined sets of callbacks that help developers implement common patterns. Examples include GenServer, Supervisor, and Application behaviors.

By leveraging OTP, developers can build fault-tolerant systems with built-in error handling, scalability, and distribution capabilities.

Question:

The code snippet below attempts to calculate the average of a list of numbers. However, when invoking MathFunctions.average([1, 2, 3, 4, 5]), it doesn’t produce the expected output. Fix the code to correctly calculate the average and return the expected output.

defmodule MathFunctions do
  def average(list) do
    sum = Enum.reduce(list, 0, &+/2)
    sum / length(list)
  end
end

IO.puts(MathFunctions.average([1, 2, 3, 4, 5])) # Expected output: 3.0

Answer:

defmodule MathFunctions do
  def average(list) do
    sum = Enum.sum(list)
    length = length(list)
    sum / length
  end
end

IO.puts(MathFunctions.average([1, 2, 3, 4, 5])) # Output: 3.0

Question:
What is metaprogramming in Elixir? Explain the concept and the role of macros in Elixir.

Answer:
Metaprogramming in Elixir refers to the ability to write code that generates or manipulates other code at compile-time. Elixir provides Macros in Elixir are functions that transform and generate code at compile-time. They operate on the abstract syntax tree (AST) of the code and are expanded during compilation. Macros are defined using the defmacro construct and can be invoked using the macro keyword.

Macros allow developers to define new constructs, domain-specific languages (DSLs), and abstractions specific to their application’s needs. They provide a way to extend the language itself without modifying the compiler. Macros enable code generation, code reuse, and domain-specific optimizations.

By using macros, developers can write expressive, concise, and flexible code that adapts to different scenarios and simplifies complex tasks.

1,000 Companies use CoderPad to Screen and Interview Developers

Interview best practices for Elixir roles

To conduct effective Elixir interviews, it’s essential to take into account various factors, such as the candidates’ experience levels and the specific engineering role they’re pursuing. To guarantee that your Elixir interview questions generate optimal outcomes, we suggest following these best practices when engaging with candidates:

  • Create technical questions that reflect real-world situations within your organization. This strategy will not only captivate the candidate but also enable you to more accurately assess their suitability for your team.
  • Foster a collaborative environment by inviting candidates to ask questions throughout the interview.
  • Your candidates should be familiar with concurrency and distributed systems.

Additionally, it’s vital to adhere to conventional interview practices when carrying out Elixir interviews. This involves tailoring question difficulty according to the candidate’s ability, offering prompt feedback on their application status, and permitting candidates to inquire about the evaluation or collaborate with you and your team.

]]>