Designing a tech interview for software engineers is not easy. We have to find out as much as possible about the candidate in very limited time. I wouldn’t focus only on a single subject during the interview. I described 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 a 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.

#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 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>) {}

  foo: {
    rab: 3
  // other fields are not required

The answer could be:

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

I'm a software engineer with 7 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.