Software Interview Preparation Guide
This document contains an overview of the minimum skillset required for the general software engineering candidate.
Many of the important points here are derived from the Cracking the Coding Interview, 6th Edition print.
We assume the use of the python3
programming language.
Non-Technical Competency
STAR (SOAR)
Use the STAR
method for responding to non technical competency questions.
- Situation
- Task (Objective)
- Action
- Response/Result
Amazon Leadership Principles
Although these principles are tailored to Amazon, they form a solid foundation for competency interviews for all companies.
Customer Obsession
Leaders start with the customer and work backwards. They work vigorously to earn and keep customer trust. Although leaders pay attention to competitors, they obsess over customers.
Ownership
Leaders are owners. They think long term and don’t sacrifice long-term value for short-term results. They act on behalf of the entire company, beyond just their own team. They never say “that’s not my job.”
Invent and Simplify
Leaders expect and require innovation and invention from their teams and always find ways to simplify. They are externally aware, look for new ideas from everywhere, and are not limited by “not invented here.” As we do new things, we accept that we may be misunderstood for long periods of time.
Are Right, A Lot
Leaders are right a lot. They have strong business judgment and good instincts. They seek diverse perspectives and work to disconfirm their beliefs.
Learn and Be Curious
Leaders are never done learning and always seek to improve themselves. They are curious about new possibilities and act to explore them.
Hire and Develop the Best
Leaders raise the performance bar with every hire and promotion. They recognize exceptional talent, and willingly move them throughout the organization. Leaders develop leaders and take seriously their role in coaching others. We work on behalf of our people to invent mechanisms for development like Career Choice.
Insist on the Highest Standards
Leaders have relentlessly high standards—many people may think these standards are unreasonably high. Leaders are continually raising the bar and driving their teams to deliver high-quality products, services, and processes. Leaders ensure that defects do not get sent down the line and that problems are fixed so they stay fixed.
Think Big
Thinking small is a self-fulfilling prophecy. Leaders create and communicate a bold direction that inspires results. They think differently and look around corners for ways to serve customers.
Bias for Action
Speed matters in business. Many decisions and actions are reversible and do not need extensive study. We value calculated risk taking.
Frugality
Accomplish more with less. Constraints breed resourcefulness, self-sufficiency and invention. There are no extra points for growing headcount, budget size, or fixed expense.
Earn Trust
Leaders listen attentively, speak candidly, and treat others respectfully. They are vocally self-critical, even when doing so is awkward or embarrassing. Leaders do not believe their or their team’s body odor smells of perfume. They benchmark themselves and their teams against the best.
Dive Deep
Leaders operate at all levels, stay connected to the details, audit frequently, and are skeptical when metrics and anecdote differ. No task is beneath them.
Have Backbone; Disagree and Commit
Leaders are obligated to respectfully challenge decisions when they disagree, even when doing so is uncomfortable or exhausting. Leaders have conviction and are tenacious. They do not compromise for the sake of social cohesion. Once a decision is determined, they commit wholly.
Deliver Results
Leaders focus on the key inputs for their business and deliver them with the right quality and in a timely fashion. Despite setbacks, they rise to the occasion and never settle.
Big O
Big O is a notation for measuring the efficiency of algorithms.
It measures the upper bound of an algorithm’s run time or space utilization relative to the input size.
Adhere to the following rules of thumb:
- Drop the constants. (e.g.
) - Drop the non-dominant terms. (e.g.
) - Runtime of typical recursive algorithms with multiple branches:
Reference Table
The notations are ranked from most efficient to least efficient.
Notation | Name | Notes |
---|---|---|
O(1) | constant | Reference on a look-up table |
O(\log{n}) | logarithmic | Binary search on sorted array |
O(n) | linear | Search on an unsorted array |
O(n\log{n}) | loglinear | Merge sort runtime complexity |
O(n^2) | quadratic | Bubble/Selection/Insertion sort |
O(n^c) | polynomial | Tree-adjoining grammar parsing |
O(c^n) | exponential | Exact Travelling Salesman (Dynamic Programming) |
O(n!) | factorial | Brute Force Search Travelling Salesman |
Examples
ins_sort
|
We iterate through the array to 1.
find the elements to insert, then another time to 2.
find the position to insert.
Once the position is found, we 3.
perform an array slice assignment.
All inserts occur in place.
This ins_sort
has $ python3 -m timeit -n 100 '([0] * 2000000)[1:2] = range(1)'
# 100 loops, best of 5: 9.13 msec per loop
$ python3 -m timeit -n 100 '([0] * 2000000)[1:20000] = range(19999, -1, -1)'
# 100 loops, best of 5: 11 msec per loop
$ python3 -m timeit -n 100 '([0] * 2000000)[1:200000] = range(199999, -1, -1)'
# 100 loops, best of 5: 53.7 msec per loop
$ python3 -m timeit -n 100 '([0] * 2000000)[1:2000000] = range(1999999, -1, -1)'
# 100 loops, best of 5: 191 msec per loop
range_sum
|
Each recursion adds an additional level to the stack.
This range_sum
implementation has range_sum(5)
-> range_sum(4)
-> range_sum(3)
-> range_sum(2)
-> range_sum(1)
two_exp
|
Each recursion splits into two separate branches.
This two_exp
implementation has two_exp(3)
/ \
two_exp(2) two_exp(2)
/| |\
two_exp(1) two_exp(1) two_exp(1) two_exp(1)
memo_two_exp
|
Each recursion splits into two separate branches, but one of the branches is guaranteed to hit the cache.
This memo_two_exp
implementation has two_exp(3)
/ \
two_exp(2) # two_exp(2) cached
/|
two_exp(1) # two_exp(1) cached
Data Structures
Linked Lists
The standard library implementation, collections.deque
, is a doubly linked list supporting efficient O(1)
appends and pops for either side of the deque.from collections import deque
dll = deque([1, 2, 3]) # [1, 2, 3]
dll.append(200) # [1, 2, 3, 200]
dll.appendleft(-789) # [-789, 1, 2, 3, 200]
dll.pop() # [-789, 1, 2, 3]
dll.popleft() # [1, 2, 3]
dll.extendleft(["A", "B"]) # ["B", "A", 1, 2, 3]
dll.extend(["Y", "Z"]) # ["B", "A", 1, 2, 3, "Y", "Z"]
Implementation
|
Stacks and Queues
The stack (last-in, first-out) and queue (first-in, first-out) data structures are trivially implemented using the list
builtin or the linked list data structure.
The appends and pops have O(1)
time complexity.
Implementation
|
Graphs, Trees, Heaps, Tries
Python does not have a standard library graph implementation. A simple graph can be constructed using a dictionary and lists and serves as the base data structure.
A tree
is a connected graph without cycles. A node is called a leaf node if it has no children.
A binary tree
is a tree where each node has up to two neighbors/children.
A binary search tree
is a binary tree where each node satisfies the condition: all_left_descendents <= n < all_right_descendents
.
A complete binary tree
is where each level of the tree is fully filled from left to right. The last level may not be full filled, but the nodes must still satisfy the left to right property.
A full binary tree
is where all nodes have either zero or two children.
A perfect binary tree
is where all interior nodes have two children and all leaf nodes are on the same level.
A min-heap
is a complete binary tree where each node is smaller than its children.
A max-heap
is a complete binary tree where each node is greater than its children.
A trie
, or a prefix tree, is an n-ary tree where all leaf nodes are semantically terminating nodes.
One common use case is to store of English language prefix lookups.
Implementation
|
Hash Tables
A hash table (a.k.a. hashmap) is provided by Python’s dict
built-in.hm = {} # dict()
hm["foo"] = "bar" # {"foo": "bar"}
Implementation
|
Algorithms
BFS/DFS
Breadth First Search (BFS) and Depth First Search (DFS) are two different ways to traverse a graph.
Step | Breadth First Search | Depth-First-Search |
---|---|---|
0 | Node 0 | Node 0 |
1 | Node 1 | -Node 1 |
2 | Node 4 | --Node 3 |
3 | Node 5 | ---Node 2 |
4 | Node 3 | ---Node 4 |
5 | Node 2 | -Node 5 |
Implementation
Our BFS/DFS algorithm relies on the Graph implementation above.from collections import deque
class IteratorBFS(object):
def __init__(self, graph, start):
self.graph = graph
self.to_visit = deque([start, ])
self.visited = set()
def __iter__(self):
while self.to_visit:
cur = self.to_visit.pop() # First In, First out
if cur in self.visited:
continue
for neighbor in self.graph.neighbors(cur):
if neighbor not in self.visited:
self.to_visit.appendleft(neighbor)
self.visited.add(cur)
yield cur
class IteratorDFS(object):
def __init__(self, graph, start):
self.graph = graph
self.to_visit = deque([start, ])
self.visited = set()
def __iter__(self):
while self.to_visit:
cur = self.to_visit.popleft() # First In, Last out
if cur in self.visited:
continue
# reversed to preserve order of edges, but not 100% necessary
for neighbor in reversed(self.graph.neighbors(cur)):
if neighbor not in self.visited:
self.to_visit.appendleft(neighbor)
self.visited.add(cur)
yield curg = Graph(
edges=[
("0", "1"),
("0", "4"),
("0", "5"),
("1", "3"),
("1", "4"),
("2", "1"),
("3", "2"),
("3", "4"),
]
)
iterator = IteratorBFS(g, "0")
# next(iterator) # Step by step search. "0"
assert list(iterator) == ["0", "1", "4", "5", "3", "2"]
iterator = IteratorDFS(g, "0")
assert list(iterator) == ["0", "1", "3", "2", "4", "5"]
Merge Sort
The merge sort algorithm.
The runtime of this implementation is
Implementation
|
Quicksort
The quicksort algorithm.
The runtime of this implementation is
Implementation
|
Dynamic Programming
Dynamic programming is an approach to more effectively compute recursive problems.
The key takeaway is that these problems are built off of solutions to smaller sub problems.
knapsack
Given a list of weighted values and a capacity, return the maximum value that can be carried without exceeding the available capacity.
For example, with [(2, 3), (4, 11), (3, 12)]
and a capacity of 5
, you can carry the items of weight 2
, and 3
for a total value of (3 + 12) = 15
.
Each item can be taken at most one time.
Recursion Only
|
Memoized Recursion (Top-Down)
|
Tabular (Bottom-Up)
|