Python Arrays & Lists (1D & 2D)
Python Arrays & Lists Interview Questions
What is a list in Python?
A list is a built-in data structure in Python that represents an ordered, mutable collection of items. Lists can contain elements of different data types and are created using square brackets [].
How to create a 1D list in Python?
# Different ways to create 1D lists
# 1. Using square brackets
list1 = [1, 2, 3, 4, 5]
list2 = ['apple', 'banana', 'cherry']
mixed = [1, 'hello', 3.14, True]
# 2. Using list() constructor
list3 = list() # Empty list
list4 = list(range(5)) # [0, 1, 2, 3, 4]
list5 = list('hello') # ['h', 'e', 'l', 'l', 'o']
# 3. Using list comprehension
squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
# 4. From other iterables
tuple_to_list = list((1, 2, 3)) # [1, 2, 3]
string_to_list = list("abc") # ['a', 'b', 'c']
What is the difference between arrays and lists in Python?
| Aspect | Python List | Array (array module/numpy) |
|---|---|---|
| Type | Built-in data type | From array module or numpy |
| Data types | Can store different types | Stores same type elements |
| Memory | More memory overhead | Memory efficient |
| Operations | Basic operations | Mathematical operations |
| Performance | Slower for numerical ops | Faster for numerical ops |
| Use case | General purpose | Numerical computations |
How to create a 2D list (matrix) in Python?
# Different ways to create 2D lists (matrices)
# 1. Direct initialization
matrix1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 2. Using nested list comprehension
rows, cols = 3, 4
matrix2 = [[0 for _ in range(cols)] for _ in range(rows)]
# [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
# 3. Using multiplication (CAUTION: Creates references!)
matrix3 = [[0] * 3 for _ in range(3)] # Correct
# matrix4 = [[0] * 3] * 3 # WRONG! Creates references
# 4. From existing lists
row1 = [1, 2, 3]
row2 = [4, 5, 6]
matrix4 = [row1, row2]
# 5. Using numpy (for numerical work)
import numpy as np
matrix_np = np.zeros((3, 3)) # 3x3 matrix of zeros
What are the basic list operations in Python?
my_list = [1, 2, 3, 4, 5]
# 1. Access elements
print(my_list[0]) # 1 (first element)
print(my_list[-1]) # 5 (last element)
# 2. Slicing
print(my_list[1:3]) # [2, 3] (index 1 to 2)
print(my_list[:3]) # [1, 2, 3] (first 3)
print(my_list[2:]) # [3, 4, 5] (from index 2)
# 3. Length
print(len(my_list)) # 5
# 4. Check existence
print(3 in my_list) # True
print(10 in my_list) # False
# 5. Concatenation
new_list = my_list + [6, 7, 8] # [1, 2, 3, 4, 5, 6, 7, 8]
# 6. Repetition
repeated = my_list * 2 # [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
# 7. Iteration
for item in my_list:
print(item)
What are list methods in Python?
my_list = [1, 2, 3]
# 1. append() - add to end
my_list.append(4) # [1, 2, 3, 4]
# 2. extend() - add multiple items
my_list.extend([5, 6]) # [1, 2, 3, 4, 5, 6]
# 3. insert() - insert at position
my_list.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6]
# 4. remove() - remove first occurrence
my_list.remove(3) # [0, 1, 2, 4, 5, 6]
# 5. pop() - remove and return item at index
item = my_list.pop(1) # item=1, list=[0, 2, 4, 5, 6]
# 6. clear() - remove all items
my_list.clear() # []
# 7. index() - find index of item
my_list = [10, 20, 30, 20]
idx = my_list.index(20) # 1 (first occurrence)
# 8. count() - count occurrences
cnt = my_list.count(20) # 2
# 9. sort() - sort in place
my_list.sort() # [10, 20, 20, 30]
my_list.sort(reverse=True) # [30, 20, 20, 10]
# 10. reverse() - reverse in place
my_list.reverse() # [10, 20, 20, 30] (original order)
# 11. copy() - create shallow copy
copy_list = my_list.copy()
How to access elements in a 2D list?
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 1. Access single element
print(matrix[0][0]) # 1 (row 0, column 0)
print(matrix[1][2]) # 6 (row 1, column 2)
print(matrix[-1][-1]) # 9 (last row, last column)
# 2. Access entire row
row1 = matrix[0] # [1, 2, 3]
row2 = matrix[1] # [4, 5, 6]
# 3. Access column (requires iteration or list comprehension)
col1 = [row[0] for row in matrix] # [1, 4, 7]
col2 = [row[1] for row in matrix] # [2, 5, 8]
# 4. Using loops
for i in range(len(matrix)): # Row indices
for j in range(len(matrix[i])): # Column indices
print(f"matrix[{i}][{j}] = {matrix[i][j]}")
# 5. Using enumerate
for i, row in enumerate(matrix):
for j, value in enumerate(row):
print(f"Position ({i},{j}): {value}")
# 6. Slicing 2D lists
first_two_rows = matrix[:2] # [[1,2,3], [4,5,6]]
first_two_cols = [row[:2] for row in matrix] # [[1,2], [4,5], [7,8]]
What is list comprehension and how is it used?
# List comprehension: concise way to create lists
# Syntax: [expression for item in iterable if condition]
# Basic examples
squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
# With condition
evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
# Transforming elements
words = ['hello', 'world', 'python']
upper_words = [word.upper() for word in words] # ['HELLO', 'WORLD', 'PYTHON']
# Nested list comprehension (for 2D lists)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row] # [1,2,3,4,5,6,7,8,9]
# Transpose matrix
transpose = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# With if-else
numbers = [1, 2, 3, 4, 5]
classified = ['even' if x % 2 == 0 else 'odd' for x in numbers]
# ['odd', 'even', 'odd', 'even', 'odd']
# Creating 2D list
rows, cols = 3, 4
matrix2 = [[i*j for j in range(cols)] for i in range(rows)]
# [[0, 0, 0, 0], [0, 1, 2, 3], [0, 2, 4, 6]]
What is the difference between shallow copy and deep copy?
import copy
# Original list with nested list
original = [[1, 2, 3], [4, 5, 6]]
# 1. Assignment (creates reference)
reference = original
reference[0][0] = 100
print(original[0][0]) # 100 (original changed!)
# 2. Shallow copy (copy() or slicing)
shallow = original.copy() # or original[:]
shallow[0] = [7, 8, 9] # This doesn't affect original
print(original[0]) # [100, 2, 3] (unchanged)
shallow[1][0] = 400 # This affects original!
print(original[1][0]) # 400 (original changed!)
# 3. Deep copy (creates completely new objects)
deep = copy.deepcopy(original)
deep[1][0] = 9999
print(original[1][0]) # 400 (unchanged)
print(deep[1][0]) # 9999
# Summary:
# - Assignment: Both variables point to same list
# - Shallow copy: New list, but nested objects are shared
# - Deep copy: Completely independent copy
How to sort lists in Python?
# 1. sort() method (in-place)
numbers = [3, 1, 4, 1, 5, 9, 2]
numbers.sort()
print(numbers) # [1, 1, 2, 3, 4, 5, 9]
# 2. sorted() function (returns new list)
numbers = [3, 1, 4, 1, 5, 9, 2]
sorted_numbers = sorted(numbers)
print(sorted_numbers) # [1, 1, 2, 3, 4, 5, 9]
print(numbers) # [3, 1, 4, 1, 5, 9, 2] (original unchanged)
# 3. Reverse sort
numbers.sort(reverse=True) # [9, 5, 4, 3, 2, 1, 1]
# 4. Sorting with custom key
words = ['banana', 'apple', 'cherry', 'date']
words.sort(key=len) # Sort by length
print(words) # ['date', 'apple', 'banana', 'cherry']
words.sort(key=str.lower) # Case-insensitive sort
# 5. Sorting list of tuples
students = [('Alice', 25), ('Bob', 20), ('Charlie', 23)]
students.sort(key=lambda x: x[1]) # Sort by age
print(students) # [('Bob', 20), ('Charlie', 23), ('Alice', 25)]
# 6. Sorting 2D lists
matrix = [[3, 2, 1], [6, 5, 4], [9, 8, 7]]
matrix.sort(key=lambda x: x[0]) # Sort by first element of each row
print(matrix) # [[3, 2, 1], [6, 5, 4], [9, 8, 7]]
# 7. Natural sort (for strings with numbers)
import re
def natural_sort_key(s):
return [int(text) if text.isdigit() else text.lower()
for text in re.split(r'(\d+)', s)]
What are common list operations for 2D lists?
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 1. Transpose matrix
transpose = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# 2. Flatten matrix
flattened = [element for row in matrix for element in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 3. Get diagonal elements
diagonal = [matrix[i][i] for i in range(3)] # [1, 5, 9]
anti_diagonal = [matrix[i][2-i] for i in range(3)] # [3, 5, 7]
# 4. Sum rows and columns
row_sums = [sum(row) for row in matrix] # [6, 15, 24]
col_sums = [sum(matrix[i][j] for i in range(3)) for j in range(3)] # [12, 15, 18]
# 5. Find maximum/minimum in matrix
max_val = max(max(row) for row in matrix) # 9
min_val = min(min(row) for row in matrix) # 1
# 6. Rotate matrix 90 degrees clockwise
def rotate_90(matrix):
return [[row[i] for row in reversed(matrix)] for i in range(len(matrix[0]))]
# 7. Check if matrix is square
is_square = len(matrix) == len(matrix[0]) # True
# 8. Create identity matrix
n = 3
identity = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
# [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
How to use the array module in Python?
import array
# Creating arrays (more memory efficient than lists for numeric data)
# Type codes: 'b' (signed char), 'B' (unsigned char), 'i' (signed int),
# 'I' (unsigned int), 'f' (float), 'd' (double)
# Create array of integers
arr = array.array('i', [1, 2, 3, 4, 5])
print(arr) # array('i', [1, 2, 3, 4, 5])
# Basic operations
print(arr[0]) # 1
print(len(arr)) # 5
arr.append(6) # Add element
arr.extend([7, 8]) # Add multiple
# Slicing
print(arr[2:5]) # array('i', [3, 4, 5])
# Convert to list
my_list = arr.tolist() # [1, 2, 3, 4, 5, 6, 7, 8]
# Mathematical operations (limited compared to numpy)
arr2 = array.array('i', [10, 20, 30, 40, 50])
# Memory efficiency comparison
import sys
list_data = [1, 2, 3, 4, 5]
arr_data = array.array('i', [1, 2, 3, 4, 5])
print(f"List memory: {sys.getsizeof(list_data)} bytes")
print(f"Array memory: {sys.getsizeof(arr_data)} bytes")
# Limitations of array module:
# - Only homogeneous data types
# - Limited mathematical operations
# - No built-in matrix operations
# For numerical work, numpy is better
How to use numpy arrays for numerical computations?
import numpy as np
# Creating numpy arrays
arr1d = np.array([1, 2, 3, 4, 5]) # 1D array
arr2d = np.array([[1, 2, 3], [4, 5, 6]]) # 2D array
# Special arrays
zeros = np.zeros((3, 3)) # 3x3 matrix of zeros
ones = np.ones((2, 4)) # 2x4 matrix of ones
identity = np.eye(3) # 3x3 identity matrix
range_arr = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
# Array operations (element-wise)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(a + b) # [5, 7, 9]
print(a * b) # [4, 10, 18] (element-wise multiplication)
print(a @ b) # 32 (dot product)
# Matrix operations
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(A @ B) # Matrix multiplication
print(A.T) # Transpose
print(np.linalg.inv(A)) # Inverse
# Statistical operations
data = np.array([1, 2, 3, 4, 5])
print(np.mean(data)) # 3.0
print(np.std(data)) # Standard deviation
print(np.sum(data)) # 15
print(np.min(data), np.max(data)) # 1, 5
# Slicing and indexing
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(matrix[0, 0]) # 1
print(matrix[:, 1]) # [2, 5, 8] (second column)
print(matrix[1:, :2]) # [[4, 5], [7, 8]]
What are common mistakes when working with lists?
# 1. Modifying list while iterating
numbers = [1, 2, 3, 4, 5]
# for num in numbers:
# if num % 2 == 0:
# numbers.remove(num) # Unexpected behavior!
# Solution: Create a copy or use list comprehension
numbers = [num for num in numbers if num % 2 != 0]
# 2. Using multiplication for nested lists
matrix_wrong = [[0] * 3] * 3 # Creates references!
matrix_wrong[0][0] = 1
print(matrix_wrong) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
# Solution: Use list comprehension
matrix_correct = [[0] * 3 for _ in range(3)]
# 3. Confusing append() and extend()
lst = [1, 2, 3]
lst.append([4, 5]) # [1, 2, 3, [4, 5]] (nested list)
lst.extend([6, 7]) # [1, 2, 3, [4, 5], 6, 7]
# 4. Forgetting that sort() modifies in-place
numbers = [3, 1, 4, 1, 5]
sorted_numbers = numbers.sort() # WRONG! sort() returns None
# Correct: sorted_numbers = sorted(numbers)
# 5. Index out of range
lst = [1, 2, 3]
# print(lst[5]) # IndexError
print(lst[-1]) # 3 (safe way to get last element)
# 6. Shallow copy issues
original = [[1, 2], [3, 4]]
copy = original.copy()
copy[0][0] = 99
print(original[0][0]) # 99 (unexpected!)
# 7. Comparing lists with == and is
list1 = [1, 2, 3]
list2 = [1, 2, 3]
print(list1 == list2) # True (values equal)
print(list1 is list2) # False (different objects)
How to find duplicates in a list?
numbers = [1, 2, 3, 2, 4, 5, 3, 6, 7, 7]
# 1. Using set to find all duplicates
seen = set()
duplicates = set()
for num in numbers:
if num in seen:
duplicates.add(num)
else:
seen.add(num)
print(f"Duplicates: {list(duplicates)}") # [2, 3, 7]
# 2. Using list comprehension and count()
duplicates = list(set([x for x in numbers if numbers.count(x) > 1]))
print(f"Duplicates: {duplicates}") # [2, 3, 7]
# 3. Using collections.Counter
from collections import Counter
counter = Counter(numbers)
duplicates = [item for item, count in counter.items() if count > 1]
print(f"Duplicates: {duplicates}") # [2, 3, 7]
# 4. Find first duplicate
def first_duplicate(lst):
seen = set()
for item in lst:
if item in seen:
return item
seen.add(item)
return None
print(f"First duplicate: {first_duplicate(numbers)}") # 2
# 5. Remove duplicates (preserve order)
def remove_duplicates(lst):
seen = set()
result = []
for item in lst:
if item not in seen:
seen.add(item)
result.append(item)
return result
print(f"Without duplicates: {remove_duplicates(numbers)}")
# [1, 2, 3, 4, 5, 6, 7]
How to merge and split lists?
# Merging lists
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
# 1. Using + operator
merged = list1 + list2 + list3 # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 2. Using extend()
merged = []
merged.extend(list1)
merged.extend(list2) # [1, 2, 3, 4, 5, 6]
# 3. Using unpacking (Python 3.5+)
merged = [*list1, *list2, *list3] # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 4. Using itertools.chain
from itertools import chain
merged = list(chain(list1, list2, list3))
# Splitting lists
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 1. Using slicing
first_half = numbers[:len(numbers)//2] # [1, 2, 3, 4]
second_half = numbers[len(numbers)//2:] # [5, 6, 7, 8, 9]
# 2. Split into chunks
def split_into_chunks(lst, chunk_size):
return [lst[i:i+chunk_size] for i in range(0, len(lst), chunk_size)]
chunks = split_into_chunks(numbers, 3)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 3. Split based on condition
evens = [x for x in numbers if x % 2 == 0] # [2, 4, 6, 8]
odds = [x for x in numbers if x % 2 != 0] # [1, 3, 5, 7, 9]
# 4. Using numpy array_split
import numpy as np
arr = np.array(numbers)
chunks = np.array_split(arr, 3) # 3 approximately equal parts
How to perform matrix operations on 2D lists?
# Matrix operations using pure Python (without numpy)
def matrix_add(A, B):
"""Add two matrices element-wise"""
return [[A[i][j] + B[i][j] for j in range(len(A[0]))]
for i in range(len(A))]
def matrix_multiply(A, B):
"""Multiply two matrices"""
result = [[0 for _ in range(len(B[0]))] for _ in range(len(A))]
for i in range(len(A)):
for j in range(len(B[0])):
for k in range(len(B)):
result[i][j] += A[i][k] * B[k][j]
return result
def matrix_transpose(matrix):
"""Transpose a matrix"""
return [[matrix[j][i] for j in range(len(matrix))]
for i in range(len(matrix[0]))]
def matrix_scalar_multiply(matrix, scalar):
"""Multiply matrix by scalar"""
return [[element * scalar for element in row] for row in matrix]
# Example usage
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
print("A + B:", matrix_add(A, B)) # [[6, 8], [10, 12]]
print("A * B:", matrix_multiply(A, B)) # [[19, 22], [43, 50]]
print("Transpose of A:", matrix_transpose(A)) # [[1, 3], [2, 4]]
print("2 * A:", matrix_scalar_multiply(A, 2)) # [[2, 4], [6, 8]]
# For complex operations, use numpy:
import numpy as np
A_np = np.array(A)
B_np = np.array(B)
print("A + B (numpy):", A_np + B_np)
print("A * B (numpy):", A_np @ B_np) # Matrix multiplication
print("Inverse of A:", np.linalg.inv(A_np))
print("Determinant of A:", np.linalg.det(A_np))
What are list slicing techniques and tricks?
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Basic slicing: list[start:stop:step]
print(numbers[2:5]) # [2, 3, 4] (index 2 to 4)
print(numbers[:5]) # [0, 1, 2, 3, 4] (first 5)
print(numbers[5:]) # [5, 6, 7, 8, 9] (from index 5)
print(numbers[::2]) # [0, 2, 4, 6, 8] (every 2nd)
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (reverse)
# Negative indices
print(numbers[-3:]) # [7, 8, 9] (last 3)
print(numbers[:-3]) # [0, 1, 2, 3, 4, 5, 6] (all except last 3)
print(numbers[-5:-2]) # [5, 6, 7] (index -5 to -3)
# Step with negative start/stop
print(numbers[8:2:-2]) # [8, 6, 4] (from 8 to 3, step -2)
# Useful tricks
# 1. Get last n elements
n = 3
print(numbers[-n:]) # [7, 8, 9]
# 2. Get every nth element
print(numbers[::3]) # [0, 3, 6, 9]
# 3. Remove first and last elements
print(numbers[1:-1]) # [1, 2, 3, 4, 5, 6, 7, 8]
# 4. Get middle elements
print(numbers[3:7]) # [3, 4, 5, 6]
# 5. Replace slice
numbers[2:5] = [20, 30, 40]
print(numbers) # [0, 1, 20, 30, 40, 5, 6, 7, 8, 9]
# 6. Delete slice
del numbers[2:5]
print(numbers) # [0, 1, 5, 6, 7, 8, 9]
# 7. Copy list using slicing
copy = numbers[:] # Shallow copy
# 8. Reverse list in place
numbers.reverse() # In-place reversal
reversed_copy = numbers[::-1] # New reversed list
How to implement common algorithms with lists?
# 1. Linear search
def linear_search(lst, target):
for i, value in enumerate(lst):
if value == target:
return i
return -1
# 2. Binary search (list must be sorted)
def binary_search(lst, target):
left, right = 0, len(lst) - 1
while left <= right:
mid = (left + right) // 2
if lst[mid] == target:
return mid
elif lst[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# 3. Bubble sort
def bubble_sort(lst):
n = len(lst)
for i in range(n):
for j in range(0, n - i - 1):
if lst[j] > lst[j + 1]:
lst[j], lst[j + 1] = lst[j + 1], lst[j]
return lst
# 4. Selection sort
def selection_sort(lst):
for i in range(len(lst)):
min_idx = i
for j in range(i + 1, len(lst)):
if lst[j] < lst[min_idx]:
min_idx = j
lst[i], lst[min_idx] = lst[min_idx], lst[i]
return lst
# 5. Find second largest
def second_largest(lst):
if len(lst) < 2:
return None
first = second = float('-inf')
for num in lst:
if num > first:
second = first
first = num
elif num > second and num != first:
second = num
return second if second != float('-inf') else None
# 6. Find missing number in sequence
def find_missing(numbers):
n = len(numbers) + 1
expected_sum = n * (n + 1) // 2
actual_sum = sum(numbers)
return expected_sum - actual_sum
# 7. Rotate list
def rotate_list(lst, k):
k = k % len(lst)
return lst[-k:] + lst[:-k]
What are list performance considerations?
# Time Complexity of List Operations:
# Operation | Time Complexity | Example
# ----------------|-----------------|---------
# Index access | O(1) | lst[5]
# Append | O(1) average | lst.append(x)
# Pop last | O(1) | lst.pop()
# Pop intermediate| O(n) | lst.pop(0)
# Insert | O(n) | lst.insert(0, x)
# Delete | O(n) | del lst[0]
# Search | O(n) | x in lst
# Sort | O(n log n) | lst.sort()
# Space Complexity: O(n) where n is number of elements
# Performance tips:
# 1. Use append() instead of insert(0, x) when possible
# Append is O(1), insert at beginning is O(n)
result = []
for i in range(1000000):
result.append(i) # Fast
# 2. Use collections.deque for queue operations
from collections import deque
queue = deque()
queue.append(1) # O(1)
queue.popleft() # O(1) (vs list.pop(0) which is O(n))
# 3. Use set for membership testing
my_list = list(range(1000000))
my_set = set(my_list)
# Slow: O(n)
# if 999999 in my_list: # Linear search
# Fast: O(1) average
if 999999 in my_set: # Hash lookup
pass
# 4. Preallocate list when size is known
size = 1000000
# Slow:
# result = []
# for i in range(size):
# result.append(0)
# Fast:
result = [0] * size
# 5. Use list comprehension instead of loops
# Slow:
# result = []
# for i in range(1000000):
# result.append(i**2)
# Fast:
result = [i**2 for i in range(1000000)]
# 6. Avoid modifying list while iterating
# This causes reindexing and is O(n^2) in worst case
# 7. Use built-in functions (written in C)
# sum(lst) is faster than manual loop summation
Note: These questions cover Python lists and arrays comprehensively. Remember: Lists are mutable, ordered collections that can hold heterogeneous data. For 2D lists, be careful with list multiplication (use list comprehension instead). For numerical computations, consider using numpy arrays. Always be aware of time and space complexity when working with large lists, and use appropriate data structures (like sets or deques) when needed.