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]>;
};