To excel in data science coding interviews, it's essential to master a variety of questions that test your programming skills and understanding of data science concepts. We have prepared a list of the Top 50 Data Science Interview Questions along with their answers to ace interviews.
1. Write a function to reverse a string in Python
Reversing a string means flipping its order of characters. You can do this by using Python slicing ([::-1]) or by iterating from the end to the start. This is a common operation in string manipulation.
def reverse_string(s):
return s[::-1]
print(reverse_string("hello"))
Output:
olleh
2. Check if a string is a palindrome.
- Compare the original string with its reversed version to see if they are the same.
- If they match, it means the string is a palindrome.
- Return
TrueorFalsebased on this comparison. How can you check a string is palindrome or not in Python:
def is_palindrome(s):
return s == s[::-1]
print(is_palindrome("madam"))
Output:
True
3. Write a function to find the nth Fibonacci number using recursion.
The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones, usually starting with 0 and 1. Let's see the code to find the nth Fibonacci number:
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
n = 5
print(f"The {n}th Fibonacci number is: {fibonacci(n)}")
Output:
The 5th Fibonacci number is: 5
4. Find indices of two numbers that add up to a specific target in an array.
- First we create a dictionary to store numbers and their indices as you iterate through the array.
- For each number, check if its complement (target minus the number) exists in the dictionary.
- If it does, return their indices.
Let's see the code:
def two_sum(nums, target):
num_map = {}
for index, num in enumerate(nums):
complement = target - num
if complement in num_map:
return [num_map[complement], index]
num_map[num] = index
print(two_sum([2, 7, 3, 15], 10))
Output:
[1, 2]
5. Write a Python function to calculate the factorial of a number.
This function calculates the factorial of a number using recursion. If the number is 0 or 1, it returns 1. Otherwise, it multiplies the number by the factorial of the number minus 1.
def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5))
Output:
120
6. How would you count the occurrences of each element in a list?
we use Python's Counter from the collections module to count how often each element appears in a list. Let's do the implementation:
from collections import Counter
def count_occurrences(lst):
return Counter(lst)
print(count_occurrences([1, 2, 2, 3, 3, 3]))
Output:
Counter({3: 3, 2: 2, 1: 1})
7. Find the first non-repeated character in a string.
Track the frequency of each character with a dictionary, then return the first character whose count is exactly 1.
def first_non_repeated_char(s):
char_count = {}
for char in s:
char_count[char] = char_count.get(char, 0) + 1
for char in s:
if char_count[char] == 1:
return char
return None
print(first_non_repeated_char("swiss"))
print(first_non_repeated_char("aabbcc"))
Output:
w
None
8. Check if two strings are anagrams of each other.
Two strings are anagrams if they contain exactly the same characters in the same frequency. Sorting both and comparing is the simplest approach.
def are_anagrams(str1, str2):
if len(str1) != len(str2):
return False
return sorted(str1) == sorted(str2)
print(are_anagrams("listen", "silent"))
print(are_anagrams("hello", "world"))
Output:
True
False
9. Explain *args and **kwargs with an example.
*argscollects extra positional arguments into a tuple;**kwargscollects extra keyword arguments into a dictionary.- Both let a function accept a variable number of inputs — useful when writing flexible data-processing utility functions.
def summarize(*args, **kwargs):
print("Positional:", args)
print("Keyword:", kwargs)
summarize(1, 2, 3, model="linear_regression", verbose=True)
Output:
Positional: (1, 2, 3)
Keyword: {'model': 'linear_regression', 'verbose': True}
10. Write a generator function that lazily yields squares of numbers.
Generators (using yield) produce values one at a time and don't hold the entire sequence in memory — critical when streaming large datasets that don't fit in RAM.
def square_generator(n):
for i in range(n):
yield i * i
for val in square_generator(5):
print(val, end=" ")
Output:
0 1 4 9 16
11. Write a decorator that times how long a function takes to run.
Decorators wrap a function to add behavior (logging, timing, caching) without modifying its internal code — commonly used to profile slow data pipelines.
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time() - start:.4f}s")
return result
return wrapper
@timer
def slow_square(n):
return [i * i for i in range(n)]
slow_square(1000000)
12. Explain the difference between shallow copy and deep copy, with an example.
- A shallow copy duplicates the outer object but nested objects still point to the same memory; a deep copy recursively duplicates everything.
- This matters a lot when copying nested lists/DataFrames before mutating them for feature engineering experiments.
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0][0] = 99
print("Shallow:", shallow) # affected: shares inner lists
print("Deep:", deep) # unaffected: fully independent
Output:
Shallow: [[99, 2], [3, 4]]
Deep: [[1, 2], [3, 4]]
13. Handle multiple exception types and use finally in a data-loading function.
Robust data pipelines need to distinguish between error types (missing file vs. bad format) and guarantee cleanup regardless of outcome.
def load_number(value):
try:
return 1 / int(value)
except ZeroDivisionError:
print("Cannot divide by zero")
except ValueError:
print(f"'{value}' is not a valid integer")
finally:
print("Attempted to process:", value)
load_number("0")
load_number("abc")
Output:
Cannot divide by zero
Attempted to process: 0
'abc' is not a valid integer
Attempted to process: abc
14. Explain how a hash table works, with an example.
A hash table stores key-value pairs. A hash function maps each key to an index (bucket) in an underlying array, giving average O(1) lookup, insert, and delete.
hash_table = {}
hash_table["name"] = "Rihan"
hash_table["age"] = 22
hash_table["city"] = "America"
print(hash_table["name"])
print(hash_table["age"])
print(hash_table["city"])
15. Create a class to represent a Person with basic attributes.
__init__ initializes state, a normal method mutates state, and __str__ controls how print() displays the object.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def birthday(self):
self.age += 1
def __str__(self):
return f"Name: {self.name}, Age: {self.age}"
person1 = Person("Alice", 30)
print(person1)
person1.birthday()
print(person1)
Output:
Name: Alice, Age: 30
Name: Alice, Age: 31
16. Find the median of two sorted arrays of different sizes.
Merge and sort both arrays, then pick the middle element (or average the two middle elements if the combined length is even).
def find_median(arr1, arr2):
merged = sorted(arr1 + arr2)
n = len(merged)
return (merged[n // 2] + merged[n // 2 - 1]) / 2 if n % 2 == 0 else merged[n // 2]
print(find_median([1, 3], [2, 4, 5]))
Output:
3
17. Implement a sliding window to find the maximum sum of a subarray of size k.
- Compute the sum of the first window, then slide it forward one element at a time — adding the new element and subtracting the one that fell out.
- This avoids recomputing the sum from scratch (O(n) instead of O(n·k)).
def max_sum_subarray(arr, k):
window_sum = sum(arr[:k])
max_sum = window_sum
for i in range(k, len(arr)):
window_sum += arr[i] - arr[i - k]
max_sum = max(max_sum, window_sum)
return max_sum
print(max_sum_subarray([2, 1, 5, 1, 3, 2], 3))
Output:
9
Q18. Find the kth smallest element in an unsorted array.
heapq.nsmallest returns the k smallest elements in sorted order; the last one is the kth smallest.
import heapq
def kth_smallest(arr, k):
return heapq.nsmallest(k, arr)[-1]
print(kth_smallest([7, 10, 4, 3, 20, 15], 3))
Output:
7
19. Generate all possible permutations of a list of numbers.
itertools.permutations generates every possible ordering.
from itertools import permutations
def generate_permutations(arr):
return list(permutations(arr))
print(generate_permutations([1, 2, 3]))
Output:
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
Q20. Find the maximum sum of a contiguous subarray (Kadane's Algorithm).
Given an integer array, find the sum of the largest contiguous subarray. E.g., for A = [0,-1,-5,-2,3,14], the answer is 17 (from [3, 14]). If all elements are negative, the function must return 0.
def max_subarray(arr):
max_sum = 0
curr_sum = 0
for num in arr:
curr_sum += num
max_sum = max(max_sum, curr_sum)
if curr_sum < 0:
curr_sum = 0
return max_sum
print(max_subarray([0, -1, -5, -2, 3, 14])) # 17
print(max_subarray([-1, -2, -3])) # 0 (now correct)
21. Implement binary search on a sorted array.
Binary search repeatedly halves the search space — O(log n) — by comparing the target to the middle element. It's the most frequently asked "warm-up" DSA question in data-science coding rounds.
def binary_search(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
print(binary_search([1, 3, 5, 7, 9, 11], 7))
Output:
3
22. Implement Merge Sort.
Merge sort recursively splits the array in half, sorts each half, then merges the two sorted halves. Guaranteed O(n log n) — a common "implement a sort from scratch" ask.
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left, right = merge_sort(arr[:mid]), merge_sort(arr[mid:])
merged, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i]); i += 1
else:
merged.append(right[j]); j += 1
merged.extend(left[i:])
merged.extend(right[j:])
return merged
print(merge_sort([5, 2, 9, 1, 5, 6]))
Output:
[1, 2, 5, 5, 6, 9]
Q23. Reverse a singly linked list.
This is one of the most common data-structure questions; it tests pointer manipulation, not just built-in functions.
class Node:
def __init__(self, val):
self.val = val
self.next = None
def reverse_linked_list(head):
prev = None
current = head
while current:
nxt = current.next
current.next = prev
prev = current
current = nxt
return prev # new head
# Build 1 -> 2 -> 3, reverse it, print result
head = Node(1); head.next = Node(2); head.next.next = Node(3)
new_head = reverse_linked_list(head)
while new_head:
print(new_head.val, end=" ")
new_head = new_head.next
Output:
3 2 1
Q24. Detect a cycle in a linked list (Floyd's Tortoise and Hare).
Use two pointers moving at different speeds. If they ever meet, there's a cycle; if the fast pointer reaches the end, there isn't.
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
25. Check for valid (balanced) parentheses using a stack.
Push opening brackets onto a stack; on a closing bracket, check it matches the top of the stack.
def is_valid_parentheses(s):
stack = []
pairs = {')': '(', ']': '[', '}': '{'}
for char in s:
if char in "([{":
stack.append(char)
elif char in pairs:
if not stack or stack.pop() != pairs[char]:
return False
return not stack
print(is_valid_parentheses("{[()]}"))
print(is_valid_parentheses("{[(])}"))
Output:
True
False
26. Implement an LRU (Least Recently Used) Cache.
An LRU cache evicts the least recently used item once it reaches capacity. OrderedDict makes this simple in Python and it's a very common "design a system component" coding question.
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
lru = LRUCache(2)
lru.put(1, 'a'); lru.put(2, 'b')
print(lru.get(1)) # 'a' -> 1 becomes most recently used
lru.put(3, 'c') # evicts key 2 (least recently used)
print(lru.get(2)) # -1, evicted
27. Implement BFS and DFS traversal of a graph.
BFS explores neighbor-by-neighbor using a queue (good for shortest paths); DFS explores as deep as possible using a stack/recursion (good for connectivity/cycle checks).
from collections import deque
def bfs(graph, start):
visited, queue, order = {start}, deque([start]), []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return order
def dfs(graph, start, visited=None, order=None):
if visited is None:
visited, order = set(), []
visited.add(start)
order.append(start)
for neighbor in graph[start]:
if neighbor not in visited:
dfs(graph, neighbor, visited, order)
return order
graph = {'A': ['B', 'C'], 'B': ['D'], 'C': ['D'], 'D': []}
print(bfs(graph, 'A'))
print(dfs(graph, 'A'))
Output:
['A', 'B', 'C', 'D']
['A', 'B', 'D', 'C']
28. Solve the 0/1 Knapsack problem using Dynamic Programming.
DP questions test whether a candidate can recognize overlapping subproblems and build a solution bottom-up — frequently asked to assess algorithmic maturity beyond basic scripting.
def knapsack(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(capacity + 1):
if weights[i - 1] <= w:
dp[i][w] = max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]])
else:
dp[i][w] = dp[i - 1][w]
return dp[n][capacity]
print(knapsack(weights=[1, 3, 4, 5], values=[1, 4, 5, 7], capacity=7))
Output:
9
29. Write a function to calculate the element-wise sum of two Numpy arrays.
This adds corresponding elements of two Numpy arrays, creating a new array with the results.
import numpy as np
arr1 = np.array([1, 2])
arr2 = np.array([4, 5])
result = np.add(arr1, arr2)
print(result)
Output:
[5 7]
30. How would you extract the diagonal elements of a Numpy matrix?
In this question we have to retreive the diagonal elements in the numpy matrix and we do this with the help of a function called 'np.diagonal'.
import numpy as np
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(np.diagonal(matrix))
Output:
[1 5]
31. Write code to reshape a 1D Numpy array into a 2D array with 3 rows.
To reshape 1D Numpy array into 2D array we use the function named as 'array.reshape' and this concept iscalled reshaping the Array
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 6])
reshaped = arr.reshape(3, 2)
print(reshaped)
Output:
[[1 2]
[3 4]
[5 6]]
32. How do you transpose a NumPy array?
import numpy as np
array = np.array([[1, 2], [4, 5]])
print(array.T)
Output:
[[1 4]
[2 5]]
33. Explain NumPy broadcasting with an example.
- Broadcasting lets NumPy perform element-wise operations on arrays of different shapes without explicit loops.
- This is why vectorized NumPy code is faster than Python loops.
import numpy as np
matrix = np.array([[1, 2, 3], [4, 5, 6]])
row_vector = np.array([10, 20, 30])
print(matrix + row_vector) # row_vector is "broadcast" across every row
Output:
[[11 22 33]
[14 25 36]]
34. Use np.where to conditionally replace values in an array.
np.where(condition, value_if_true, value_if_false) is the vectorized equivalent of an if/else applied element-wise — commonly used to cap outliers or flag values.
import numpy as np
arr = np.array([5, -3, 12, -8, 7])
result = np.where(arr < 0, 0, arr) # replace negatives with 0
print(result)
Output:
[ 5 0 12 0 7]
35. How would you load a CSV file into a Pandas DataFrame?
import pandas as pd
df = pd.read_csv('file.csv')
36. How would you handle missing values in a DataFrame?
fillna()replaces missing values (e.g., with the column mean); dropna() removes rows containing any missing value.
df.fillna(df.mean(), inplace=True) # fill with mean
df.dropna(inplace=True) # or drop rows with NaN
37. How do you read and write data from a file in Python?
To read and write data from file in Python, we use the built-in open() function, which provides a way to open a file and perform various operations on it, such as reading or writing.
- Reading a file:
with open('file.txt', 'r') as file:
content = file.read()
print(content)
- Writing a file:
with open('file.txt', 'w') as file:
file.write("Hello, world!")
38. Perform one-hot encoding of categorical variables in a dataset.
import pandas as pd
def one_hot_encode(data, column):
return pd.get_dummies(data, columns=[column])
df = pd.DataFrame({'Color': ['Red', 'Blue', 'Green']})
print(one_hot_encode(df, 'Color'))
Output:
Color_Blue Color_Green Color_Red
0 0 0 1
1 1 0 0
2 0 1 0
39. Handle missing data using imputation.
from sklearn.impute import SimpleImputer
import numpy as np
def impute_missing_data(data):
imputer = SimpleImputer(strategy='mean')
return imputer.fit_transform(data)
data = np.array([[1, 2], [np.nan, 3], [7, 6]])
print(impute_missing_data(data))
40. Group a dataset by a column and calculate the rolling average for another column.
the multi-index produced by groupby().rolling() can look unfamiliar the first time you see it — the level_1 column is the original row index preserved after reset_index(). This is expected pandas behavior, not an error.
import pandas as pd
def rolling_average(df, group_col, target_col, window):
return df.groupby(group_col)[target_col].rolling(window=window).mean().reset_index()
data = {'Group': ['A', 'A', 'B', 'B'], 'Value': [10, 20, 30, 40]}
df = pd.DataFrame(data)
print(rolling_average(df, 'Group', 'Value', 2))
Output:
Group level_1 Value
0 A 0 NaN
1 A 1 15.0
2 B 2 NaN
3 B 3 35.0
41. Create a pivot table from raw transactional data.
import pandas as pd
def create_pivot_table(df, index, columns, values, aggfunc):
return pd.pivot_table(df, index=index, columns=columns, values=values, aggfunc=aggfunc)
data = {'Category': ['A', 'A', 'B'], 'Type': ['X', 'Y', 'X'], 'Value': [10, 20, 30]}
df = pd.DataFrame(data)
print(create_pivot_table(df, index='Category', columns='Type', values='Value', aggfunc='sum'))
Output:
Type X Y
Category
A 10.0 20.0
B 30.0 NaN
42. Merge two DataFrames using different join types (inner, left, right, outer).
Merging is one of the most tested Pandas skills — interviewers want to see you understand which rows survive under each join type.
import pandas as pd
customers = pd.DataFrame({'id': [1, 2, 3], 'name': ['A', 'B', 'C']})
orders = pd.DataFrame({'customer_id': [1, 2, 4], 'amount': [100, 200, 300]})
inner = customers.merge(orders, left_on='id', right_on='customer_id', how='inner')
left = customers.merge(orders, left_on='id', right_on='customer_id', how='left')
print(inner)
print(left)
43. Find and remove duplicate rows, and count value frequencies.
import pandas as pd
df = pd.DataFrame({'id': [1, 2, 2, 3], 'city': ['NY', 'LA', 'LA', 'NY']})
print(df.duplicated().sum()) # count of duplicate rows
df_clean = df.drop_duplicates()
print(df['city'].value_counts()) # frequency of each category
44. Convert a column to datetime and extract day/month/year features.
Feature engineering from timestamps (day-of-week, month, is-weekend) is a near-universal step in real DS pipelines and frequently tested.
import pandas as pd
df = pd.DataFrame({'order_date': ['2024-01-15', '2024-03-22']})
df['order_date'] = pd.to_datetime(df['order_date'])
df['year'] = df['order_date'].dt.year
df['month'] = df['order_date'].dt.month
df['day_of_week'] = df['order_date'].dt.day_name()
print(df)
45. Detect and remove outliers using the IQR method.
import pandas as pd
def remove_outliers_iqr(df, column):
Q1, Q3 = df[column].quantile(0.25), df[column].quantile(0.75)
IQR = Q3 - Q1
lower, upper = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR
return df[(df[column] >= lower) & (df[column] <= upper)]
df = pd.DataFrame({'value': [10, 12, 11, 13, 12, 300]})
print(remove_outliers_iqr(df, 'value'))
46. Write an SQL query to retrieve all columns from a table named employees where the age is greater than 30.
This query selects all columns from the employees table where the age column value is greater than 30.
SELECT * FROM employees
WHERE age > 30;
47. Write an SQL query to join two tables: orders and customers, where the customer_id in orders matches the id in customers.
TheJOIN operation merges orders and customers based on the matching customer_id and id columns.
SELECT orders, customers.
FROM orders
JOIN customers ON orders.customer_id = customers.id;
48. Write an SQL query to find the average salary for each department in a company table, but only for departments with more than 10 employees.
This query calculates the average salary by department but only for those departments that have more than 10 employees, using HAVING to filter the groups.
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING COUNT(employee_id) > 10;
49. Find all employees whose salary is greater than the company average.
SELECT *
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
50. Find total sales per product.
SELECT product_id, SUM(sales_amount) AS total_sales
FROM sales
GROUP BY product_id;
51. Find the Nth highest salary in the employees table.
A classic SQL interview staple, testable with LIMIT/OFFSET or a window function.
-- Using LIMIT/OFFSET (find the 2nd highest salary)
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
-- Using a window function (works across SQL dialects that support it)
SELECT salary FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
) ranked
WHERE rnk = 2;
52. Rank employees by salary within each department.
RANK(), DENSE_RANK(), and ROW_NUMBER() are the most commonly tested window functions — know the difference: RANK() skips numbers after ties, DENSE_RANK() doesn't, ROW_NUMBER() never ties.
SELECT
employee_id,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;
53. Find duplicate rows in a table.
SELECT email, COUNT(*) AS occurrences
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
54. Use a CTE (WITH clause) to simplify a multi-step query.
CTEs make complex, multi-stage queries (aggregate → filter → join) far more readable than nested subqueries — interviewers look for this as a sign of SQL maturity.
WITH dept_avg AS (
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
SELECT e.employee_id, e.department, e.salary, d.avg_salary
FROM employees e
JOIN dept_avg d ON e.department = d.department
WHERE e.salary > d.avg_salary;
55. How do you create a simple “Hello, World!” app in Flask?
- To create a simple Flask app, you define a route using the @app.route() decorator and return a response from a view function.
- This will start a basic Flask app that responds with “Hello, World!” when the root URL is accessed.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello, World!"
if __name__ == '__main__':
app.run()
56. How can you return JSON data in a Flask route?
To return JSON data, you can use the jsonify() function, which converts Python dictionaries to JSON format.This route will return the dictionary as a JSON response.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/data')
def data():
return jsonify({"name": "John", "age": 30})
if __name__ == '__main__':
app.run()
57. Can you explain how Flask handles HTTP methods like GET and POST?
- Flask allows you to specify which HTTP methods a route should respond to by using the
methodsparameter in the@app.route()decorator. - By default, Flask routes respond to GET requests, but you can specify others such as POST, PUT, DELETE, etc
from flask import request
@app.route('/form', methods=['GET', 'POST'])
def handle_form():
if request.method == 'POST':
return 'Form submitted!'
return 'Form not yet submitted.'
58. Write a Python function to calculate the mean, median, and standard deviation of a list of numbers.
we calculate the mean, median, and standard deviation of a list using Numpy's built-in functions. Now we see the implementation of this:
import numpy as np
lst = [10, 20, 30, 40]
mean = np.mean(lst)
median = np.median(lst)
std_dev = np.std(lst)
print(mean)
print(median)
print(std_dev)
Output:
25.0 25.0 11.180339887498949
59. Simulate a biased coin flip using a fair coin function (von Neumann's trick).
Flip the fair coin twice; if the two results differ, return the first flip — otherwise repeat. This removes bias regardless of how skewed the "fair" coin actually is.
import random
def biased_coin():
flip1, flip2 = random.randint(0, 1), random.randint(0, 1)
if flip1 != flip2:
return flip1
return biased_coin()
print(biased_coin())
60. Calculate the confidence interval for a dataset
- A confidence interval gives a range that we expect to contain the true population parameter with a stated probability (e.g., 95%).
- It's computed from the sample mean plus/minus a margin of error based on the standard deviation and the z-score for the chosen confidence level.
import numpy as np
from scipy.stats import norm
def confidence_interval(data, confidence=0.95):
mean, std = np.mean(data), np.std(data, ddof=1)
z = norm.ppf((1 + confidence) / 2)
margin_of_error = z * (std / np.sqrt(len(data)))
return mean - margin_of_error, mean + margin_of_error
print(confidence_interval([1, 2, 3, 4, 5]))
61. Implement the Chi-squared test for independence on a contingency table.
from scipy.stats import chi2_contingency
def chi_squared_test(contingency_table):
chi2, p, dof, expected = chi2_contingency(contingency_table)
return chi2, p
table = [[10, 20], [20, 40]]
print(chi_squared_test(table))
62. Generate random numbers following a given probability distribution.
import numpy as np
def generate_random_numbers(elements, probabilities, size):
return np.random.choice(elements, size=size, p=probabilities)
print(generate_random_numbers([1, 2, 3], [0.2, 0.5, 0.3], 10))
63. Compute a correlation matrix and flag highly correlated features.
Checking multicollinearity before modeling is one of the most common practical DS coding tasks.
import pandas as pd
df = pd.DataFrame({
'x1': [1, 2, 3, 4, 5],
'x2': [2, 4, 6, 8, 10], # perfectly correlated with x1
'x3': [5, 3, 6, 2, 9]
})
corr_matrix = df.corr()
high_corr = corr_matrix[(corr_matrix.abs() > 0.9) & (corr_matrix != 1.0)]
print(corr_matrix)
print(high_corr.dropna(how='all').dropna(axis=1, how='all'))
64. Implement k-nearest neighbors (KNN) from scratch.
import numpy as np
from collections import Counter
def knn(X_train, y_train, X_test, k):
distances = [np.linalg.norm(x - X_test) for x in X_train]
k_neighbors = [y_train[i] for i in np.argsort(distances)[:k]]
return Counter(k_neighbors).most_common(1)[0][0]
X_train = np.array([[1, 2], [2, 3], [3, 4]])
y_train = [0, 1, 1]
X_test = np.array([2.5, 3])
print(knn(X_train, y_train, X_test, 2))
Output:
1
65.Write a function to calculate the silhouette score for clustering results.
from sklearn.metrics import silhouette_score
from sklearn.datasets import make_blobs
def calculate_silhouette_score(X, labels):
return silhouette_score(X, labels)
X, labels = make_blobs(n_samples=10, centers=2, random_state=0)
print(calculate_silhouette_score(X, labels))
66. Implement Principal Component Analysis (PCA) to reduce dimensionality.
import numpy as np
def pca(X, n_components):
X_centered = X - np.mean(X, axis=0)
covariance_matrix = np.cov(X_centered, rowvar=False)
eigenvalues, eigenvectors = np.linalg.eig(covariance_matrix)
# sort eigenvectors by descending eigenvalue (the fix)
sorted_idx = np.argsort(eigenvalues)[::-1]
eigenvectors = eigenvectors[:, sorted_idx]
principal_components = eigenvectors[:, :n_components]
return X_centered.dot(principal_components).real
X = np.array([[1, 2], [3, 4], [5, 6]])
print(pca(X, 1))
67. Split data into train/test sets and perform k-fold cross-validation.
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=200, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = LogisticRegression()
scores = cross_val_score(model, X_train, y_train, cv=5)
print("CV scores:", scores)
print("Mean CV accuracy:", scores.mean())
68. Implement Linear Regression using Gradient Descent from scratch.
This is one of the most-asked "implement an ML algorithm without sklearn" questions — it checks whether you understand the optimization loop, not just the .fit() API.
import numpy as np
def linear_regression_gd(X, y, lr=0.01, epochs=1000):
n, m = X.shape
weights, bias = np.zeros(m), 0.0
for _ in range(epochs):
y_pred = X.dot(weights) + bias
error = y_pred - y
dw = (2 / n) * X.T.dot(error)
db = (2 / n) * np.sum(error)
weights -= lr * dw
bias -= lr * db
return weights, bias
X = np.array([[1], [2], [3], [4]], dtype=float)
y = np.array([3, 5, 7, 9], dtype=float) # y = 2x + 1
weights, bias = linear_regression_gd(X, y)
print("weights:", weights, "bias:", bias)
69. Compute Confusion Matrix, Precision, Recall, and F1-score from scratch.
def evaluate_classifier(y_true, y_pred):
tp = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 1)
tn = sum(1 for t, p in zip(y_true, y_pred) if t == 0 and p == 0)
fp = sum(1 for t, p in zip(y_true, y_pred) if t == 0 and p == 1)
fn = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 0)
precision = tp / (tp + fp) if (tp + fp) else 0
recall = tp / (tp + fn) if (tp + fn) else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) else 0
return {"confusion_matrix": {"TP": tp, "TN": tn, "FP": fp, "FN": fn},
"precision": precision, "recall": recall, "f1_score": f1}
y_true = [1, 0, 1, 1, 0, 1]
y_pred = [1, 0, 0, 1, 0, 1]
print(evaluate_classifier(y_true, y_pred))
70. Implement K-Means clustering from scratch.
import numpy as np
def kmeans(X, k, iterations=100):
centroids = X[np.random.choice(len(X), k, replace=False)]
for _ in range(iterations):
distances = np.array([[np.linalg.norm(x - c) for c in centroids] for x in X])
labels = np.argmin(distances, axis=1)
new_centroids = np.array([X[labels == i].mean(axis=0) for i in range(k)])
if np.allclose(centroids, new_centroids):
break
centroids = new_centroids
return labels, centroids
X = np.array([[1, 2], [1, 4], [1, 0], [10, 2], [10, 4], [10, 0]])
labels, centroids = kmeans(X, k=2)
print(labels)
print(centroids)
71. Find the most common n-grams in a text dataset.
from nltk import ngrams
from collections import Counter
def most_common_ngrams(text, n, top_k):
words = text.split()
n_grams = list(ngrams(words, n))
return Counter(n_grams).most_common(top_k)
text = "data science is fun and data science is interesting"
print(most_common_ngrams(text, 2, 2))
72. Extract named entities (people, locations) from text using spaCy.
import spacy
def extract_entities(text):
nlp = spacy.load("en_core_web_sm")
doc = nlp(text)
return [(ent.text, ent.label_) for ent in doc.ents]
print(extract_entities("Barack Obama was born in Hawaii."))
73. Write a Python function to tokenize a sentence into words, removing punctuation.
import re
def simple_tokenizer(text):
return re.findall(r'\b\w+\b', text.lower())
print(simple_tokenizer("Hello, how are you?"))
74. Implement a simple NER function using regular expressions.
import re
def simple_ner(text):
names = re.findall(r'\b[A-Z][a-z]*\b', text)
locations = re.findall(r'\b(?:New York|Paris|London)\b', text)
dates = re.findall(r'\b\d{1,2}/\d{1,2}/\d{4}\b', text)
return {'names': names, 'locations': locations, 'dates': dates}
text = "John went to New York on 12/12/2020"
entities = simple_ner(text)
print(entities)
75. Remove stopwords and compute TF-IDF vectors for a small text corpus.
TF-IDF is one of the most-tested feature-extraction techniques for text data, and stopword removal is a standard preprocessing step before it.
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
"data science is fun",
"machine learning is a part of data science"
]
vectorizer = TfidfVectorizer(stop_words='english')
tfidf_matrix = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names_out())
print(tfidf_matrix.toarray())
76. Write a function to convert a color image into grayscale.
import cv2
def to_grayscale(image_path):
image = cv2.imread(image_path)
return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
77. Write a custom loss function that penalizes large predictions more heavily.
import tensorflow as tf
def custom_loss(y_true, y_pred):
mse = tf.reduce_mean(tf.square(y_true - y_pred))
penalty = tf.reduce_mean(tf.square(y_pred)) * 0.01
return mse + penalty
78. Implement a custom activation function that combines ReLU and Sigmoid.
import tensorflow as tf
from tensorflow.keras.layers import Layer
class CustomActivation(Layer):
def call(self, inputs):
return tf.nn.relu(inputs) * tf.sigmoid(inputs)
model = tf.keras.Sequential([
tf.keras.layers.Dense(128, activation=CustomActivation(), input_shape=(784,))
])
79. Build and train a minimal CNN on image data using Keras.
A hands-on end-to-end example — build a small convolutional network, compile it, and fit it — is a common follow-up once candidates show they know individual building blocks (loss functions, activations).
import tensorflow as tf
from tensorflow.keras import layers, models
model = models.Sequential([
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(64, activation='relu'),
layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.summary()