Apple Data Structures and Algorithms Interview Questions & Answers
Curated interview practice questions with answers. These are practice scenarios, not a verified list of past interview questions.
About Apple hiring for Data Structures and Algorithms roles
Apple designs consumer hardware, software and services including iPhone, Mac, iPad, Apple Watch, iOS and the App Store. Its India teams include engineering, operations, maps and services work, with Hyderabad and Bengaluru as key technical locations. Hiring in India includes software engineers, QA and test engineers, data analysts and engineers supporting services and internal platforms.
For Data Structures and Algorithms roles, this Apple practice set focuses most on Trees and Binary Search Trees (7), Arrays, Strings and Hashing (7), Sorting, Searching and Backtracking (4), Linked Lists, Stacks and Queues (4). It has 3 easy, 18 medium, 9 hard questions, so you can start easy and work up to the harder ones.
Company type
Hardware & semiconductors
Headquarters
Cupertino, California, USA
Main India offices
Hyderabad, Bengaluru, Mumbai, Gurugram
Apple Data Structures and Algorithms interview process
Recruiter screening
Technical phone interview
Team technical interviews (coding + domain)
Manager round
HR round
Typical pattern based on public candidate experiences. Rounds vary by role, team and year.
Solved for AppleStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
Packages with given weights must be shipped in order within D days. How do you find the minimum ship capacity?
The capacity lies between the heaviest package and the total weight, and if a capacity works, any larger one also works, so binary search the answer. For a candidate capacity, greedily load packages day by day, starting a new day when the next package does not fit, and count days. If the days are within D, try smaller; otherwise try larger. Time is O(n log of total weight).
02
Write a function to validate a binary search tree.
def is_valid_bst(root, low=float('-inf'), high=float('inf')):
if not root:
return True
if not (low < root.val < high):
return False
return (is_valid_bst(root.left, low, root.val) and
is_valid_bst(root.right, root.val, high))
# Passing bounds checks the whole-subtree property, not just children.
# O(n) time, O(h) space.
03
In a company org chart stored as a tree, how would you find the closest common manager of two employees?
This is the lowest common ancestor problem. If each employee stores a parent pointer, collect the ancestors of the first employee in a set and walk up from the second until a match is found, O(h). Without parent pointers, use the recursive LCA on the tree from the CEO, O(n). For many repeated queries on a large chart, preprocess with binary lifting to answer each query in O(log n).
A trie, or prefix tree, stores strings character by character, with each edge representing a character and a flag marking word ends. Insert and search take O(L) for a word of length L regardless of how many words are stored, and all words sharing a prefix sit under one node. Tries power autocomplete, spell checkers, IP routing with longest prefix match and word search games. Memory use can be high, so compressed tries exist.
def rotate(nums, k):
n = len(nums)
k %= n
def reverse(l, r):
while l < r:
nums[l], nums[r] = nums[r], nums[l]
l += 1
r -= 1
reverse(0, n - 1) # reverse all
reverse(0, k - 1) # reverse first k
reverse(k, n - 1) # reverse the rest
# [1,2,3,4,5,6,7], k=3 -> [5,6,7,1,2,3,4]; O(n) time, O(1) space
06
You must sort a 10 GB file of records on a machine with 1 GB of RAM. How would you do it?
Use external merge sort. Read the file in chunks of about 800 MB, sort each chunk in memory and write it to disk as a sorted run, giving around 13 runs. Then perform a K-way merge: open all runs, keep a min-heap with the current record from each, repeatedly output the smallest and refill from that run, using buffered I/O. Total work is O(n log n) with roughly two passes over the data.
07
You are building undo and redo for a text editor. Which data structures would you use?
I would use two stacks. Every user action is pushed onto the undo stack as a command object that knows how to reverse itself. Undo pops from the undo stack, reverts the action and pushes it onto the redo stack. Redo pops from the redo stack and re-applies it. Any new action clears the redo stack. For memory limits, cap the undo stack size, for example using a deque that drops the oldest entries.
08
How do you find the lowest common ancestor in a BST versus in a general binary tree?
In a BST, start at the root: if both values are smaller go left, if both are larger go right, otherwise the current node is the LCA, taking O(h). In a general binary tree, recurse: if the node is null or matches either target, return it; otherwise search both subtrees. If both sides return non-null, the current node is the LCA; else return the non-null side. That takes O(n).
09
What are the ways to represent a graph and when would you use each?
An adjacency list stores, for each vertex, a list of neighbours, using O(V plus E) memory; it is best for sparse graphs such as road or social networks and makes iterating neighbours fast. An adjacency matrix uses a V by V grid, O(V squared) memory, with O(1) edge checks; it suits dense or small graphs and Floyd-Warshall. An edge list is a simple list of pairs, convenient for Kruskal's algorithm.
10
How do you handle pressure during a timed online coding test?
Situation: in a 90-minute test I faced three problems, one of them a tough DP. Task: maximise my score. Action: I read all problems first, solved the easiest one quickly to secure marks, wrote a brute-force version of the DP problem for partial credit, then improved it to memoised DP once I spotted the state. I kept an eye on time at fixed checkpoints. Result: I cleared all test cases on two problems and most of the third.
11
Serialize a binary tree to a string and deserialize it back.
If many keys produce the same hash, they pile into a few buckets and operations degrade toward O(n). This can come from a poor custom hashCode, such as returning a constant or using only one field, or from attackers sending crafted keys. Fixes include a better hash function combining all fields, randomised hashing, which Python uses for strings, and tree-based buckets as in Java 8 HashMap, which caps the worst case at O(log n).
13
You must merge two sorted arrays where the first array has enough empty space at the end for the second. How would you do it in place?
Merging from the front would overwrite unread elements, so merge from the back. Use three pointers: the last real element of the first array, the last element of the second, and the last slot of the first array. Repeatedly place the larger of the two candidates at the write position and move pointers left. When the second array is exhausted, the rest is already in place. This takes O(m plus n) time and O(1) space.
14
Find the lowest common ancestor of two nodes in a binary tree.
def lca(root, p, q):
if root is None or root is p or root is q:
return root
left = lca(root.left, p, q)
right = lca(root.right, p, q)
if left and right: # p and q are on different sides
return root
return left or right
# O(n) time, O(h) space; assumes both nodes exist in the tree
15
What is the difference between full, complete and perfect binary trees?
In a full binary tree every node has zero or two children. In a complete binary tree every level except possibly the last is completely filled, and the last level is filled from the left, which is exactly the shape of a binary heap and allows array storage. A perfect binary tree has all internal nodes with two children and all leaves at the same level, containing 2 to the power h plus 1, minus 1 nodes.
16
Generate all subsets of a list of distinct numbers.
def subsets(nums):
result = []
current = []
def backtrack(i):
if i == len(nums):
result.append(current[:])
return
current.append(nums[i]) # include nums[i]
backtrack(i + 1)
current.pop() # exclude nums[i]
backtrack(i + 1)
backtrack(0)
return result
# 2^n subsets, each up to n long: O(n * 2^n) time
17
Find the length of the longest substring without repeating characters.
def length_of_longest_substring(s):
last = {} # char -> last index seen
left = best = 0
for right, ch in enumerate(s):
if ch in last and last[ch] >= left:
left = last[ch] + 1
last[ch] = right
best = max(best, right - left + 1)
return best
# 'abcabcbb' -> 3, 'bbbbb' -> 1, 'pwwkew' -> 3; O(n) time
18
Find the first and last position of a target in a sorted array.
from bisect import bisect_left, bisect_right
def search_range(nums, target):
lo = bisect_left(nums, target) # first index >= target
if lo == len(nums) or nums[lo] != target:
return [-1, -1]
hi = bisect_right(nums, target) - 1 # last index <= target
return [lo, hi]
# [5,7,7,8,8,10], 8 -> [3, 4]; two binary searches: O(log n)
19
Your knapsack DP has n equal to 100 items but capacity up to 10 to the power 9, so the table does not fit in memory. What do you do?
A table indexed by capacity is impossible, so I change the state. If item values are small, use dp[v] as the minimum weight needed to achieve value v, then pick the largest v whose weight fits the capacity; that is O(n times total value). If n is about 40, meet in the middle: enumerate subsets of each half, sort one side and binary search. Otherwise approximation or branch and bound are options.
20
How do you find the longest increasing subsequence in O(n log n)?
The simple DP is O(n squared): dp[i] is the LIS ending at i. The faster method keeps an array tails, where tails[k] is the smallest possible last value of an increasing subsequence of length k plus 1. For each number, binary search tails for the first value not less than it and replace it, or append if none. The length of tails is the LIS length. This patience sorting idea also helps in envelope nesting problems.
21
What is a bipartite graph and how do you check for one?
A graph is bipartite if its vertices can be split into two groups so every edge connects vertices from different groups, which is equivalent to having no odd-length cycle. To check, colour vertices with BFS or DFS: give the start vertex colour 0, its neighbours colour 1 and so on; if any edge connects two vertices of the same colour, it is not bipartite. Run it on each component, O(V plus E).
22
A trading application needs a stack that also returns the current minimum in O(1). How would you design it?
Keep a second stack of minimums. On push, push the value onto the main stack and push the smaller of the value and the current minimum onto the min stack. On pop, pop both. getMin returns the top of the min stack in O(1). To save space, push onto the min stack only when the new value is less than or equal to the current minimum, and pop it only when the popped value equals it.
23
How does quicksort work and why does pivot choice matter?
Quicksort picks a pivot, partitions the array so smaller elements go left and larger go right, then recursively sorts both sides. It sorts in place with O(log n) stack space on average and is very cache friendly, averaging O(n log n). A bad pivot, such as always the first element on sorted input, gives unbalanced partitions and O(n squared). Random pivots, median-of-three and three-way partitioning for duplicates avoid this.
24
Return an array where each element is the product of all other elements, without using division.
def product_except_self(nums):
n = len(nums)
out = [1] * n
prefix = 1
for i in range(n): # product of everything to the left
out[i] = prefix
prefix *= nums[i]
suffix = 1
for i in range(n - 1, -1, -1): # multiply by everything to the right
out[i] *= suffix
suffix *= nums[i]
return out
# [1,2,3,4] -> [24,12,8,6]; O(n) time, O(1) extra space besides output
25
Implement Union-Find with path compression and union by size.
def reverse_k_group(head, k):
# check that k nodes are available
node, count = head, 0
while node and count < k:
node = node.next
count += 1
if count < k:
return head # leave the remainder as is
prev, cur = None, head
for _ in range(k): # reverse k nodes
cur.next, prev, cur = prev, cur, cur.next
head.next = reverse_k_group(cur, k) # old head is now the group tail
return prev
# 1-2-3-4-5, k=2 -> 2-1-4-3-5; O(n) time
28
You have a 100 GB file of integers and only 4 GB of RAM. How would you find the duplicates?
The data does not fit in memory, so I would partition it. Read the file in streaming fashion and write each number to one of, say, 50 bucket files based on hash of the number modulo 50, so equal numbers land in the same bucket. Each bucket of about 2 GB fits in memory, where a hash set finds duplicates. Alternatively use external merge sort and scan adjacent values. A Bloom filter can pre-screen candidates.
A singly linked list node points only to the next node, using less memory but allowing traversal in one direction. A doubly linked list node also points to the previous node, enabling backward traversal and O(1) deletion of a given node, which LRU caches need. A circular list connects the last node back to the first, useful for round-robin scheduling and buffers. Each extra pointer adds memory and update complexity.
Kadane's algorithm finds the maximum sum contiguous subarray in O(n) time and O(1) space. At each element it decides whether to extend the current subarray or start fresh: current = max(x, current + x), and best = max(best, current). The insight is that a negative running sum never helps a future subarray. For an all-negative array it correctly returns the largest single element when initialised with the first value.
Tips to crack the Apple Data Structures and Algorithms interview
Expect team-specific rounds, so study the exact job description and the Apple product or service the team supports.
For QA roles, practise writing detailed test cases for an app or device feature, including edge cases and accessibility.
Show attention to detail and user privacy awareness, both strongly associated with how Apple builds products.
Keep a one-minute story ready for every project on your CV: problem, your part, tools, result.
Practise the hard questions on this page aloud; Apple interviewers usually ask follow-ups on your first answer.
Frequently asked questions
How many rounds are there in the Apple Data Structures and Algorithms interview?
Candidates usually go through 5 stages: Recruiter screening; Technical phone interview; Team technical interviews (coding + domain); Manager round; HR round. The exact rounds change with the role, team, location and your experience, so confirm the process with the recruiter.
Which topics should I prepare for the Apple Data Structures and Algorithms interview?
This practice set leans on Trees and Binary Search Trees (7), Arrays, Strings and Hashing (7), Sorting, Searching and Backtracking (4), Linked Lists, Stacks and Queues (4). Revise those first, then practise explaining one or two of your own projects end to end, because most Apple interviewers ask you to walk through real work.
Are these the exact questions asked at Apple?
No. These are curated practice questions that match the skills and difficulty usually tested in hardware & semiconductors interviews like Apple's. They are not a leaked or verified list of past questions.
Where does Apple hire Data Structures and Algorithms professionals in India?
Apple has large teams in Hyderabad, Bengaluru, Mumbai, Gurugram. Openings and locations change often, so check the Apple careers page and filter by city and experience.
Data Structures and Algorithms questions for similar companies