Designing a tech interview for software engineers is not easy. We have to find out as much as possible about the candidate in a very limited time. I wouldn’t focus only on a single subject during the interview. I described in a separate article how I design a tech interview. This list rather should serve as an inspiration to take one, max two questions into the whole interview.

#1 Union vs intersection

What is the difference between these two type definitions?

type Type1 = Dog | Cat 
type Type2 = Dog & Cat
type Type1 = Dog[] | Cat[]
type Type2 = (Dog | Cat)[]

The differences are very subtle but quite fundamental. It tells you if the candidate understands unions and intersections.

#2 Type values as keys

Define a type Product so that a name field can only accept available languages, but doesn’t require them

type LanguageOptions = 'de' | 'en' | 'es' | 'pl';
const product: Product = {
  name: {
    de: 'Das Auto',
    en: 'The car',
  },
  description: {
    de: 'Das beste Auto der Welt',
    en: 'The best car in the world',
  },
};

The example answer could be:

type LanguageOptions = 'de' | 'en' | 'es' | 'pl';
type LocalizedValue = Partial<Record<LanguageOptions, string>>;
type Product = {
  name: LocalizedValue;
  description: LocalizedValue;
};

It checks if the candidate knows how to use dynamic values as keys. Also, it may tell you if they know TypeScript utility types. I can also explicitly ask if they know other utility types. I have an assumption, that if you have been working with TypeScript long enough, you must have come across utility types at some point.

#3 ReturnType and typeof

How to define a type that covers all exported functions of this factory function

export type UserService = ...
export function createUserService() {
  return {
    async getUser(userId: string) {},
    async updateUser(userId: string, userData: UserData) {},
  };
}

// so that it could be used like:
function createUseCase({userService}: {userService: UserService}) {
  return async function(userId: string) {
    const user = await userService.getUser(userId);
  }
}

The answer is:

export type UserService = ReturnType<typeof createUserService>;

It tells you if the candidate is aware of the ReturnType utility type as well as knows how to use typeof properly.

#4 Type narrowing

What are the ways to implement the logic of processContent so that it is type-safe? Would you implement it differently?

interface MarkdownComponent extends Component {
  type: "markdown";
  value: string;
}

interface ImageComponent extends Component {
  type: "image";
  value: {
    url: string;
    size: {
      height: number;
      width: number;
    };
  };
}

type Component = {
  type: string;
  value: unknown;
};

function processContent(components: Component[]) {
  // ...
}

function processMarkdown(component: MarkdownComponent) {}
function processImage(component: ImageComponent) {}

#5 Deep partial

Given, there is a Partial<T> type that makes fields optional – how would you implement a DeepPartial<T> type that does this recursively so that you can make any key optional

type SomeType = {
  foo: {
    bar: {
      baz: string;
    }
    rab: number;
  }
  oof: boolean;
}

function someFunc(value: DeepPartial<SomeType>) {}

someFunc({
  foo: {
    rab: 3
  }
  // other fields are not required
})

Alternatively, we could provide an answer and ask a candidate to walk us through the solution. I don’t remember how to do it myself, I usually google the type to use. But it still makes sense to understand it.

export type DeepPartial<T> = {
  [P in keyof T]?: DeepPartial<T[P]>;
};
Author

I'm a software engineer with 9 years of experience. I highly value team work and focus a lot on knowledge sharing aspects within teams. I also support companies with technical interview process. On top of that I read psychological books in my spare time and find other people fascinating.