20+ helpful Python syntax patterns for coding interviews
By: Lakshay Akula
Tags: Programming, Python, Software Development, Dynamic Programming, Coding Interviews

When coding in real life, I sometimes forget syntax and need to resort to Google. Sadly, this luxury isn’t available during coding interviews. To address this, I’ve been reviewing common syntax patterns in Python for coding interviews. Syntax isn’t as important as understanding core algorithms and data structure concepts in the first place, but for me, reviewing syntax instills confidence in my code and saves me invaluable time. I hope it does the same for you.

Part 1: Lists

1. Sorting

sorted(numbers) will return the sorted numbers in ascending order and leave the original numbers unchanged. You can also use numbers.sort() which sorts numbers in-place and mutates numbers in-place

will return the sorted numbers in ascending order and leave the original numbers unchanged. You can also use which sorts numbers in-place and mutates in-place sorted has two optional arguments, key and reverse . key lets you modify the values for comparison. So sorted(..., key=str.lower) will sort case insensitive. reverse lets you sort in descending order, so sorted(..., reverse=True) is descending.

has two optional arguments, and . lets you modify the values for comparison. So will sort case insensitive. lets you sort in descending order, so is descending. sorted uses Timsort which has O(nlogn) average- and worst-case time complexity, uses O(n) space (similar to merge sort), and has a best-case O(n) time complexity (similar to insertion sort).

2. List slicing syntax

General syntax is iterable[start:stop:step]

list[i:j] returns from index i , up to but not including j

returns from index , up to list[i:] returns from index i , up to the end

returns from index , up to the end list[:j] returns from the start, up to index j

returns from the start, up to index list[::2] returns every other element from the list (indices 0, 2, .. )

returns every other element from the list (indices ) list[::-1] reverses the list—although list.reversed() is faster

Real-life demonstration of list slicing

3. List comprehension (i.e., Pythonic map and filter)

Generic syntax is expression for member in iterable [if conditon]

E.g., [element * 2 for element in list if element % 2 == 0] will return a new list with every even element doubled from the original list

will return a new list with every even element doubled from the original list Use this instead of map and filter for better readability

4. Using range

General syntax is range(start, stop, step)

range(n) for an iterable from 0 to n — 1

for an iterable from to range(i, j) for an iterable from i up to but not including j

for an iterable from up to but not including range(i, j, n) for the same as above, but every n-th element. Note that you need to specify start and stop if you want to use step — range(10, step=2) results in an error so do range(0, 10, 2) instead—read this for more info behind why range doesn’t support keyword arguments

5. Creating lists of size N

You can use range and convert to a list (e.g., list(range(N)) or [*range(N)] if you’re feeling fancy)

or if you’re feeling fancy) For all of the same element use [element] * N

E.g., [0] * 10 is a list of 10 zeros, [None] * 5 is a list of 5 Nones

is a list of 10 zeros, is a list of 5 For 2 or more dimensions, don’t do [[0] * 5] * 10 . The 10 rows will just be references to the one row with 5 values, so editing one row will change the other 9. Instead do, [[0 for i in range(5)] for j in range(10)] .

Part 2: Iteration

6. Use enumerate

# Use enumerate to get index and element

# from an iterable

for index, element in enumerate(list):

print(element + ' is at index ' + str(index))

A do while statement like this other languages—

do {

# Write code here

} while (condition)

is this in Python (not my favorite syntax, but needs to be done sometimes)—

while True:

# Write code here

if condition:

break

Part 3. Higher-order functions

8. Use list comprehension instead of `map` or `filter`

list = list(range(10)) # [0, 1, ..., 9] # Not very Pythonic 👎

evens = filter(list, lambda x : x % 2 == 0) # [0, 2, ..., 8]

evens_doubled = map(events, lambda x : x*2) # [0, 4, ..., 16] # Using list comprehension is Pythonic 👍

evens_doubled = [x*2 for x in list if x % 2 == 0]

9. Use reduce with caution

Use reduce with caution— sum , math.prod , all , and any are more readable and it is worth writing out a more readable for loop for anything more complex than that. Even Guido (the creator of Python) himself thought this, which is why reduce has been demoted from the built-in library to the functools library.

Pour one out for built-in reduce

Nonetheless, it’s good to know. The syntax is functools.reduce(function, iterable[, initializer]) . Some examples below

from functools import reduce

nums = [1, 2, 3, 4, 5]

bools = [True, False, True, False, True] # sum

reduce(lambda a, b: a + b, nums, 0) # 15 # math.prod

reduce(lambda a, b: a * b, nums, 1) # 120 # min

reduce(lambda a, b: a if a < b else b, nums) # 1 # max

reduce(lambda a, b: a if a > b else b, nums) # 5 # any

reduce(lambda a, b: a or b, bools) # true # all

reduce(lambda a, b: a and b, bools) # false

10. Using zip, zip_longest, and zip_shortest

zip lets you iterate through lists simultaneously

list_a = [1, 2, 3, 4]

list_b = [10, 20, 30, 40] list_sum = [a + b for a, b in zip(list_a, list_b)]

# OR

list_sum = []

for a, b in zip(list_a, list_b):

list_sum.append(a + b) print(list_sum) # [11, 22, 33, 44]

For lists of unequal length, zip truncates the output to the shortest list. zip_longest in the itertools library lets you pad the smaller list with a fill_value so you can zip as if they were of equal length.

from itertools import zip_longest, zip_shortest short = [1, 2]

long = [10, 20, 30, 40] zip_short = [a + b for a, b in zip(short, long)]

print(zip_short) # [11, 22]

zip_long = [a + b for a, b in zip_longest(short, long, fillvalue=0)]

print(zip_long) # [11, 22, 30, 40]

Part 4. Data Structures

11. Python dictionary methods

To check if a key is in a dictionary, use key in my_dict

my_dict[key] accesses an item in the dictionary and returns a KeyError if the key isn’t in the dictionary. To avoid an error use my_dict.get(key, default_value=None) which returns default_value instead of an error if the key isn’t in the dictionary

accesses an item in the dictionary and returns a if the key isn’t in the dictionary. To avoid an error use which returns instead of an error if the key isn’t in the dictionary To remove a key from a dictionary, use del my_dict[key] if you know the key is in my_dict . Otherwise, use my_dict.pop(key, None) which will return None if key is not in my_dict

if you know the is in . Otherwise, use which will return if is not in my_dict.setdefault(key, default_value) returns the current value for key if it is in my_dict , if not, it sets it to default_value and returns that

returns the current value for if it is in , if not, it sets it to and returns that setdefault is especially helpful for counting elements, where you can do something like counts[element] = counts.setdefault(element, 0) + 1

is for counting elements, where you can do something like my_dict.keys() , my_dict.values() , and my_dict.items() will return a list of the keys, values, and tuples of (key, value) from the dictionary respectively (these are helpful for iteration)

, , and will return a list of the keys, values, and tuples of from the dictionary respectively (these are helpful for iteration) You can use dictionary comprehension, much like list comprehension to create new dictionaries

my_basket = {'apple': 2, 'banana': 3, 'starfruit': 1}

double_my_basket = {k:v*2 for (k, v) in my_basket.items()}

print(double_my_basket) # {'apple': 4, 'banana': 6, 'starfruit': 2}

For sets and dictionaries you can use the merge and update operators | and |= respectively to add keys and values (only available for dictionaries as of Python 3.9)

a = {1, 2, 3} # New set

a += {4} # ❌ Returns a `TypeError`

a |= {4} # ✅ {1, 2, 3, 4}

12. Using OrderedDict

OrderedDict isn’t used very frequently, but it can make some problems such as implementing an LRU cache almost trivial. An OrderedDict is effectively a dictionary combined with a doubly-linked list for ordering

isn’t used very frequently, but it can make some problems such as implementing an LRU cache almost trivial. An is effectively a dictionary combined with a doubly-linked list for ordering OrderedDict has a .popitem() method which lets you remove items in LIFO (last-in, first-out) order ( popitem(last=False) will remove items in FIFO order).

has a method which lets you remove items in LIFO (last-in, first-out) order ( will remove items in FIFO order). Also .move_to_end(item_key) let’s you move an item to the end of the dictionary (so it can be popped next). .move_to_end(item_key, last=False) lets you move an item to the beginning.

13. Using collections.Counter

Frequently, interview questions involve adding counts to a hash map. For this, you can just use collections.Counter like below. A Counter is a subclass of dict and you can interact with it like it is one.

things = ['a', 'a', 'b', 'c', 'b', 'b']

counts = collections.Counter(things)

print(counts) # Counter({'b': 3, 'a': 2, 'c': 1})

14. Using heapq

Don’t use queue.PriorityQueue , heapq is more flexible

, is more flexible Read through the official heapq documentation—some callouts below

heapify is in-place, so heapify(list) will turn list into a heap

is in-place, so will turn into a heap heapq only supports min-heaps, for a max-heap, multiply all values by -1 before heapifying and multiply by -1 after popping (annoying, I know)

only supports min-heaps, for a max-heap, multiply all values by before heapifying and multiply by after popping (annoying, I know) If you want to create a heap for objects, you should add them as a tuple with (priority, counter, object) . counter is a unique number to break priority ties (otherwise you’ll get an error). I start counter at 0 and increment it whenever I push to the heap.

15. Implementing a Tree

There aren’t any Tree data structures that are helpful for interviews in Python’s standard library, so you’ll need to implement your own

For a binary tree, you can create a Node class with left and right Node attributes. Make sure to keep track of the “head” Node in your code.

For a non-binary tree, you can use an array or a dictionary for the children. If you don’t expect children with duplicate values and want O(1) lookup, a dictionary might be better.

# Binary Tree Node

class Node:

def __init__(value):

self.value = value

self.left = None

self.right = None # Non-Binary Tree Node

class Node:

def __init__(value):

self.value = value

self.children = [] # or self.children = {} for dict

16. Implementing a Trie

There is no Trie in Python’s standard libraries, but you can implement one easily. Here’s one with just insert and search functionality and you can build additional functionality on top of this.

class TrieNode:

def __init__(self):

self.is_complete_word = False

self.children = {} class Trie:

def __init__(self):

# Blank Trie

self.root = TrieNode()



def insert(self, word: str) -> None:

"""

Insert a word:

— Iterate through all characters in the word

- If we we encounter a char we don't have a

node for, create a new node

- Mark the last node as a complete word

"""

curr = self.root

for char in word:

curr = curr.children.setdefault(char, TrieNode())

curr.is_complete_word = True



def search(self, word: str) -> bool:

"""

Search for a word:

— Iterate through all characters in the word

- If we we encounter a char we don't have a

node for, return False

- At the last node, return whether the word

is a complete word in our Trie

"""

curr = self.root

for char in word:

if char not in curr.children:

return False

curr = curr.children[char]

return curr.is_complete_word

Part 4: Recursion & Dynamic Programming

17. Memoize using a decorator

Just add the @cache decorator

# No memoization O(2**N) time complexity

def fib(n):

return fib(n - 1) + fib(n - 2) if n > 1 else n # Memoized, now O(N) time complexity

from functools import cache

@cache

def fib(n):

return fib(n - 1) + fib(n - 2) if n > 1 else n # Similar to doing this

memo = {}

def fib(n):

return memo.setdefault(n, fib(n - 1) + fib(n - 2) if n > 1 else n) # You can limit the memo size to N

# using lru_cache (here N = 62)

from functools import lru_cache

@lru_cache(64)

def fib(n):

return fib(n - 1) + fib(n - 2) if n > 1 else n

Who knew memoizing was so easy!

18. Making recursive programs iterative

Here’s an example of making an iterative Fibonacci function using tabulation ( O(n) space complexity).

def fib(n):

fibs = [None] * n

fibs[0], fibs[1] = 0, 1

for i in range(2, n):

fib[i] = fib[i - 1] + fib[i - 2]

return fib[i]

And here’s an O(1) space complexity version.

def fib(n):

if n <= 1:

return n

first, second = 0, 1

for i in range(2, n + 1):

first, second = second, first + second

return second

Another way to make a program iterative is to create your own stack. This answer from StackOverflow does a great job explaining how.

Part 4: Misc

19. Ternary if operator

condition ? a : b in other languages is a if condition else b in Python

20. Manipulate binary strings

Get rid of the annoying 0b prefix after using bin via slicing—for example, bin(7) = "0b111 so bin(7)[2:] = "111"

prefix after using via slicing—for example, so To add leading zeros up to N digits, add 2**N and then slice—for example, to pad 111 with zeros until it’s 5 digits long, do bin(2**5 + 7)[3:] = “00111” (note that 2**N > num for this to work)

21. Mutable vs. immutable objects

A mutable object can be changed and an immutable object can’t. Here’s a list of built-in types and whether they are immutable. Custom classes are usually mutable.

This just means you need to be careful with creating variables from existing variables for mutable objects. You could unintentionally change the existing object!

ones = [1] * 3

twos = ones

for i, _ in enumerate(twos):

twos[i] = 2

print(twos) # [2, 2, 2]

print(ones) # Also [2, 2, 2]!

To avoid this, you need to create a copy from the variable.

ones = [1] * 3

twos = list(ones) # Creates a copy instead

for i, _ in enumerate(twos):

twos[i] = 2

print(twos) # [2, 2, 2]

print(ones) # [1, 1, 1] # or using list comprehension

twos = [2 for i in ones]

print(twos) # [2, 2, 2]

print(ones) # [1, 1, 1]

Please comment if you have any questions or additional suggestions, and best of luck to those of you interviewing!