• Back

My Mixed feelings about coding interview

intro

These days, coding interviews are like security checks before getting into big companies, and they are getting more and more competitive. It is not uncommon to hear someone fail coding rounds multiple times before he/she could eventually land in a halfway decent job. Leetcode used to only have 300 questions, but now it's a whopping 2400. I actually got dinged twice as well in the beginning of 2021, which is why I am here rethinking what I should do about this trend no matter it's for good or bad.

I heard a lot of people (usually mid-senior level), of course, these are usually the losers of this coding game, argue coding interviews are not reflective of the person's actual capability. However, in the market driven economy, coding interview is a proven efficient way to screen people. As I was sitting across the table on the interview table back when I was working for Bosch, I understand the pain of not being able to select between a bunch of qualified candidates. A coding round would serve like an efficient weeder to weed out the "less proficient" candidates. Sometimes, as an interviewer, you cannot help but to feel a weird sense of achievement by crushing someone else's ego.

The real question is how should we navigate given that the market is already like this? Coding interview skills are a little different from actual coding skills, since it is all about algorithms. In my years of experience of doing ML research, I never actually needed to use heap or any fancy data structures to process my data. Whereas at a coding prep site, there's almost 1 in 5 chance that you can stumble upon on a heap question. Should I complain? Should I just keep doing research and publish papers and stay in academia? or should I get on the train and do leetcode as well?

I guess I am more of a realistic person and my answer is to tackle the leetcode questions with a clear structure of what I am doing, and try to rebuild a stronger algorithmic foundation throughout this process. ML or stats or math skills are like the 'devil fruit' power, but coding skills, at the end of the day, are still the basic combat power to rely on in this rapidly-evolving tech world.

I am taking my baby steps.

P.S. updated 8-22-2021, 2-18-2022, 4-28-2022, 10-22-2022, 4-22-2023, 1-17-2024:

After doing LeetCode for 2+ year or so, I have several important epiphonies:

  • Interview coding is different from research coding; do it or not do, stop whining since there's not much use complaining;
  • Do questions by categories are way more efficient than do it randomly! Remember common patterns!
  • Need to take notes and Review! we human beings eventually succumb to the Ebbinghaus forgetting curve
  • Discipline, Discipline and Discipline! Laziness it the biggest enemy.
  • Write succinct code! Short code but readable. Try to cut to the chase ASAP intead of trying brute force first!
  • Try to explain every major implementation step or motivation. Carve out the data structure, sudo code, and try to list out several edge cases.
  • Try to talk like a likeable person during interview: positive, smart, good technical habits, and willingness to listen and debug.
  • Eventually, muscle memories work faster than the brain, there are patterns in each categories, and would need to memorize.
  • Thought process is more important than actual coding, need to let people see why you are doing what.
  • In the GPT-4 era,, I don't see much value of a person only memorize the answer, since GPT-4 can surpass my test capabilities very easily. I think it is about demonstrating the level of understanding of the problem and the thought process that is valuable. However, I am still very amazed/scared of the capability of GPT-4, when I take notes I will post if GPT could solve the question and tally the accuracy after a year or so.
  • I managed to keep doing Leetcode's daily challenge after I found where I want to work at. It simply became my habit, which does not take much mental power to execute every day. In this way, you would always keep at least 70-80% percent of your peak coding performance, and still could think independently without much help from GPT tools.
  • Array:

    Array Questions are prevalent but could be combined with other categories Array questions I practiced

  • Two Pointers are actually large enough of a topic that worth its own page.
  • Hashmap(2 sums)
  • Graph problem hidden in words
  • Stack:

    Stack questions on leetcode is not very intuitive. The questions, if not thought through carefully, can easily fool you into O(n^2) solutions. The correct answers are most of times O(n). Stack questions I practiced

  • Remove K digits Trick: find the first node of descending orer
  • One trick seems to be in the while loop within the for loop that goes through the input.
  • The other trick seems to be using 2-dimensional stack instead of one
  • Next time, if I see anything that requires to keep track of the previous elements, I should think about using the LIFO strategy to host that many elements in the stack and count them through popping them out of the stack.

    Heap:

    Heap questions are intrinsically more challenging. Leetcode lists almost all the heap questions above medium difficulty. But once you get used to them, everything would look very similar to you. Especially, once you are familiar with the idea of keeping priority queue in 2D heap and how to use 2 heaps to tackle a moving stream of data, all other heap questions would be similar derivative or variant questions. Heap questions I practiced

  • The core idea about heaps is to leverage its natural sorting capability. Top K xxx for xxx...
  • This is a great resource for Heap
  • How to heapify, why heap is better than sorted list, merge many already-sorted input streams into a single sorted output stream.

    Tree:

    Tree questions are mostly revolving around BST(Binary Search Trees), and to grasp BST well, I really think the best way is to go back and read CLRS thoroughly and understand every bit they say about BSTs. Tree questions I practiced

  • Search O(height)
  • Find min, max, successor, predecessor O(height)
  • Insert O(height)
  • Delete: This one is tricky since it can involve 2 different cases. still O(height) thoTransplant:replaces one subtree as a child of its parent with another tree.
  • Rotation: This operation is needed for red-black tree or more advanced tree structures.
  • Tree questions involve BFS, DFS, Dijkstra's algorithm, and lots lots of recursions. This topic is one of the most important topic and definite worth spending more time on it. I took 2 weeks going through many tree-based questions, and I still feel I am not mastering tree 100%. There are couple of important notes:
  • Remember sub-category of tries, treaps.
  • Be sure to get the recursion base case/ terminate case first. e.g. (#543 diameter of the tree, #199 rightside)
  • Get comfortable to use Stack for DFS and queue for BFS, they run faster and more intuitive to understand
  • Serialize/deserialize , construct tree from preorder+inorder list, postorder+inorder list
  • Get comfortable with recursion.
  • Matrix related:

    Matrix questions are somehow related and they share lots of similarities. Matrix questions I practiced

    Graph:

    Graph questions are involved, but I feel once I know the pattern, it's a lot more within control

  • Disjoint Set, Union Find: Union function and find function, quick union, quick find, or union with rank
                           
                           def isBipartite(self, graph: List[List[int]]) -> bool:
                                length = len(graph)
                                parent = [i for i in range(length)]
    
                                def find(x):
                                    if x!=parent[x]:
                                        parent[x]=find(parent[x]) #rank compression
                                    return parent[x]
    
                                def union(x,y): # union by rank can balance the tree
                                #whichever is taller tree will dominate
                                    px,py=find(x),find(y)
                                    if px!=py:
                                        parent[px]=py
    
                                for i in range(length):
                                    pari=find(i)
                                    for j in graph[i]:
                                        #check connected or not
                                        if find(j)==pari:
                                            return False
                                        union(graph[i][0],j)
                                return True
                           
                        
  • BFS Template(queue, priority queue)
  •                        
                           def cloneGraph(self, node: 'Node') -> 'Node':
                                if not node:
                                    return node
                                queue = [node]
                                seen = {}
                                seen[node] = Node(node.val, [])
                                while queue:
                                    curr_node = queue.pop(0)
                                    for neighbor in curr_node.neighbors:
                                        if neighbor not in seen:
                                            seen[neighbor] = Node(neighbor.val, [])
                                            queue.append(neighbor)
                                        seen[curr_node].neighbors.append(seen[neighbor])
                                return seen[node]
                           
                        
  • DFS Template
  •                        
                           class Solution:
                            def isValidBST(self, root: Optional[TreeNode]) -> bool:
    
                                if root is None:
                                    return True
    
                                dfs_stack = [root]
                                self._add_all_left_nodes(root, dfs_stack)
    
                                prev_val = dfs_stack[-1].val - 1 # could use math.min as well
    
                                while len(dfs_stack) > 0:
                                    node = dfs_stack.pop()
                                    if prev_val < node.val:
                                        prev_val = node.val
                                    else:
                                        return False
                                    if node.right is not None:
                                        dfs_stack.append(node.right)
                                        self._add_all_left_nodes(node.right, dfs_stack)
    
                                return True
    
    
                            def _add_all_left_nodes(self, node, s):
                                while node.left is not None:
                                    s.append(node.left)
                                    node = node.left
                           
                    
  • Dijkstra's, A-Star
  • A good start is to build adjacency list
  • Lots of word, string, char type of questions are implicit Graph questions, like word ladder and etc.
  • Dynamic Programming:

    Base Case; Transition function; How to think about breaking a problem down to subproblems. After doing quite a few of them, I found that starting with recursion is usually the easiest, then adding memoization is not difficult, and finally coming up with tabular dp will be natural. The core is to pin down the state-transition function as early as possible. DP questions I practiced

    Backtrack:

    Backtracking is a tricky categrory, but could be solved using a template, 1.Subsets 2. Subsets II 3.Permutations 4. Permutations II 5. Combinations 6. Combination Sum II 7. Combination Sum III 8. Palindrome Partition Backtrack questions I practiced

  • The core idea about backtracking is it incrementally builds candidates to the solutions, and abandons a candidate ("backtrack") as soon as it determines that this candidate cannot lead to a final solution.
                               
                               def backtrack(candidate):
                                    if find_solution(candidate):
                                        output(candidate)
                                        return
    
                                    # iterate all possible candidates.
                                    for next_candidate in list_of_candidates:
                                        if is_valid(next_candidate):
                                            # try this partial candidate solution
                                            place(next_candidate)
                                            # given the candidate, explore further.
                                            backtrack(next_candidate)
                                            # backtrack
                                            remove(next_candidate)
                               
                        
  • This is a great resource for Backtracing
  • Still this type of questions are very challenging even after practise, since they would usually involve O(2^n) complexity.


    Comments

    Want to leave a comment? Visit this post's issue page on GitHub (you'll need a GitHub account).