I was at a Free Code Camp meetup in San Francisco a few days ago (for those not familiar, Free Code Camp is a group of people who get together to learn JavaScript and web development), and someone getting ready for frontend dev job interviews asked for JavaScript questions to practice. After a bit of Googling, I couldn’t find any lists I could point her to and say “if you can do these, you can land a job”. Some came pretty close, some were silly, but all were either incomplete or asked questions that people don’t actually ask in real life.
So, based on my experiences on both sides of the interview table, here goes. These are a sampling of questions I’ve asked and been asked when hiring frontend engineers. Keep in mind that some places (like Google) focus more on designing efficient algorithms, so if you want to work there you should practice past CodeJam problems in addition to the stuff below. If you have a question that belongs in one of these lists (or I’ve made a mistake somewhere), shoot me an email.
I’ll be adding/updating answers to these questions here (feel free to make PRs with additions/improvements!).
I’ve grouped questions into a few sections: Concepts, Coding, Debugging, and System Design:
Be able to clearly explain these in words (no coding):
this
work?Implement the following functions (tests follow each question):
isPrime
- Returns true
or false
, indicating whether the given number is prime.
isPrime(0) // false
isPrime(1) // false
isPrime(17) // true
isPrime(10000000000000) // false
factorial
- Returns a number that is the factorial of the given number.
factorial(0) // 1
factorial(1) // 1
factorial(6) // 720
fib
- Returns the nth Fibonacci number.
fib(0) // 0
fib(1) // 1
fib(10) // 55
fib(20) // 6765
isSorted
- Returns true
or false
, indicating whether the given array of numbers is sorted.
isSorted([]) // true
isSorted([-Infinity, -5, 0, 3, 9]) // true
isSorted([3, 9, -3, 10]) // false
filter
- Implement the filter
function.
filter([1, 2, 3, 4], n => n < 3) // [1, 2]
reduce
- Implement the reduce
function.
reduce([1, 2, 3, 4], (a, b) => a + b, 0) // 10
reverse
- Reverses the given string (yes, using the built in reverse
function is cheating).
reverse('') // ''
reverse('abcdef') // 'fedcba'
indexOf
- Implement the indexOf function for arrays.
indexOf([1, 2, 3], 1) // 0
indexOf([1, 2, 3], 4) // -1
isPalindrome
- Return true
or false
indicating whether the given string is a plaindrone (case and space insensitive).
isPalindrome('') // true
isPalindrome('abcdcba') // true
isPalindrome('abcd') // false
isPalindrome('A man a plan a canal Panama') // true
missing
- Takes an unsorted array of unique numbers (ie. no repeats) from 1 through some number n, and returns the missing number in the sequence (there are either no missing numbers, or exactly one missing number). Can you do it in O(N) time? Hint: There’s a clever formula you can use.
missing([]) // undefined
missing([1, 4, 3]) // 2
missing([2, 3, 4]) // 1
missing([5, 1, 4, 2]) // 3
missing([1, 2, 3, 4]) // undefined
isBalanced
- Takes a string and returns true
or false
indicating whether its curly braces are balanced.
isBalanced('}{') // false
isBalanced('{{}') // false
isBalanced('{}{}') // true
isBalanced('foo { bar { baz } boo }') // true
isBalanced('foo { bar { baz }') // false
isBalanced('foo { bar } }') // false
fib2
- Like the fib
function you implemented above, able to handle numbers up to 50
(hint: look up memoization).
fib2(0) // 0
fib2(1) // 1
fib2(10) // 55
fib2(50) // 12586269025
isBalanced2
- Like the isBalanced
function you implemented above, but supports 3 types of braces: curly {}
, square []
, and round ()
. A string with interleaving braces should return false.
isBalanced2('(foo { bar (baz) [boo] })') // true
isBalanced2('foo { bar { baz }') // false
isBalanced2('foo { (bar [baz] } )') // false
uniq
- Takes an array of numbers, and returns the unique numbers. Can you do it in O(N) time?
uniq([]) // []
uniq([1, 4, 2, 2, 3, 4, 8]) // [1, 4, 2, 3, 8]
intersection
- Find the intersection of two arrays. Can you do it in O(M+N) time (where M and N are the lengths of the two arrays)?
intersection([1, 5, 4, 2], [8, 91, 4, 1, 3]) // [4, 1]
intersection([1, 5, 4, 2], [7, 12]) // []
sort
- Implement the sort function to sort an array of numbers in O(N×log(N)) time.
sort([]) // []
sort([-4, 1, Infinity, 3, 3, 0]) // [-4, 0, 1, 3, 3, Infinity]
includes
- Return true
or false
indicating whether the given number appears in the given sorted array. Can you do it in O(log(N)) time?
includes([1, 3, 8, 10], 8) // true
includes([1, 3, 8, 8, 15], 15) // true
includes([1, 3, 8, 10, 15], 9) // false
assignDeep
- Like Object.assign
, but merges objects deeply. For the sake of simplicity, you can assume that objects can contain only numbers and other objects (and not arrays, functions, etc.).
assignDeep({ a: 1 }, {}) // { a: 1 }
assignDeep({ a: 1 }, { a: 2 }) // { a: 2 }
assignDeep({ a: 1 }, { a: { b: 2 } }) // { a: { b: 2 } }
assignDeep({ a: { b: { c: 1 }}}, { a: { b: { d: 2 }}, e: 3 })
// { a: { b: { c: 1, d: 2 }}, e: 3 }
reduceAsync
- Like the reduce
you implemented in the Easy section, but each item must be resolved before continuing onto the next.
let a = () => Promise.resolve('a')
let b = () => Promise.resolve('b')
let c = () => new Promise(resolve => setTimeout(() => resolve('c'), 100))
await reduceAsync([a, b, c], (acc, value) => [...acc, value], [])
// ['a', 'b', 'c']
await reduceAsync([a, c, b], (acc, value) => [...acc, value], ['d'])
// ['d', 'a', 'c', 'b']
Implement seq
in terms of reduceAsync
. seq
takes an array of functions that return promises, and resolves them one after the other.
let a = () => Promise.resolve('a')
let b = () => Promise.resolve('b')
let c = () => Promise.resolve('c')
await seq([a, b, c]) // ['a', 'b', 'c']
await seq([a, c, b]) // ['a', 'c', 'b']
Note: For the data structures you’ll implement below, the idea isn’t to memorize them, but just to be able to look at the given API, Google how they work, and implement them, and to have a high level idea of what they are used for and what their tradeoffs are compared to other data structures.
permute
- Return an array of strings, containing every permutation of the given string.
permute('') // []
permute('abc') // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
debounce
- Implement the debounce function.
let a = () => console.log('foo')
let b = debounce(a, 100)
b()
b()
b() // only this call should invoke a()
Implement a LinkedList
class without using JavaScript’s built-in arrays ([]
). Your LinkedList should support just 2 methods: add
and has
:
class LinkedList {...}
let list = new LinkedList(1, 2, 3)
list.add(4) // undefined
list.add(5) // undefined
list.has(1) // true
list.has(4) // true
list.has(6) // false
Implement a HashMap
class without using JavaScript’s built-in objects ({}
) or Map
s. You are provided a hash()
function that takes a string and returns a number (the numbers are mostly unique, but sometimes two different strings will return the same number):
function hash (string) {
return string
.split('')
.reduce((a, b) => ((a << 5) + a) + b.charCodeAt(0), 5381)
}
Your HashMap should support just 2 methods, get
, set
:
let map = new HashMap
map.set('abc', 123) // undefined
map.set('foo', 'bar') // undefined
map.set('foo', 'baz') // undefined
map.get('abc') // 123
map.get('foo') // 'baz'
map.get('def') // undefined
Implement a BinarySearchTree
class. It should support 4 methods: add
, has
, remove
, and size
:
let tree = new BinarySearchTree
tree.add(1, 2, 3, 4)
tree.add(5)
tree.has(2) // true
tree.has(5) // true
tree.remove(3) // undefined
tree.size() // 4
Implement a BinaryTree
class with breadth first search and inorder, preorder, and postorder depth first search functions.
let tree = new BinaryTree
let fn = value => console.log(value)
tree.add(1, 2, 3, 4)
tree.bfs(fn) // undefined
tree.inorder(fn) // undefined
tree.preorder(fn) // undefined
tree.postorder(fn) // undefined
For each of the following questions, start by understanding and explaining why the given piece of code doesn’t work. Then propose a couple of fixes, and rewrite the code to implement one of the fixes you proposed so the program works correctly:
I want this code to log out "hey amy"
, but it logs out "hey arnold"
- why?
function greet(person) {
if (person == { name: 'amy' }) {
return 'hey amy'
} else {
return 'hey arnold'
}
}
greet({ name: 'amy' })
I want this code to log out the numbers 0
, 1
, 2
, 3
in that order, but it doesn’t do what I expect (this is a bug you run into once in a while, and some people love to ask about it in interviews).
for (var i = 0; i < 4; i++) {
setTimeout(() => console.log(i), 0)
}
I want this code to log out "doggo"
, but it logs out undefined
!
let dog = {
name: 'doggo',
sayName() {
console.log(this.name)
}
}
let sayName = dog.sayName
sayName()
I want my dog to bark()
, but instead I get an error. Why?
function Dog(name) {
this.name = name
}
Dog.bark = function() {
console.log(this.name + ' says woof')
}
let fido = new Dog('fido')
fido.bark()
Why does this code return the results that it does?
function isBig(thing) {
if (thing == 0 || thing == 1 || thing == 2) {
return false
}
return true
}
isBig(1) // false
isBig([2]) // false
isBig([3]) // true
If you’re not sure what “system design” means, start here.
Talk me through a full stack implemention of an autocomplete widget. A user can type text into it, and get back results from a server.
Talk me through a full stack Twitter implementation (shamelessly stolen from my friend Michael Vu).
If you got this far and want more, below are some high quality resources that I’ve found helpful.
For more things to implement:
For some good reading: