As you might already know, not just tech positions but several other positions are offered by Apple, such as operations, administrative, etc. and each post has its own separate set of interview questions. Today, we are going to discuss the important Apple interview questions that are asked during the interview for a technical post.
Interview at a Glance
The entire interview process of Apple for a Software Engineer or other technical post candidate can be divided into 3 phases:
- Pre-screening with the Recruiter
- Technical Telephonic Interview
- On-site Interview
The pre-screen with a recruiter can take around one week. A recruiter will set up a suitable time for a phone call via LinkedIn or email. This phone screen will be somewhere between 15-30 minutes long and the technicality of the questions will not be mild. Some basic questions could be questions like, which one is your favorite Apple product or service and why? What drives you to work for and be a part of Apple?
Your next technical phone interview is likely to be scheduled after a week. There will be one or two technical telephonic interviews. These will consist of questions about your resume and a coding question on data structures and algorithms. Each coding phone screen lasts for about 45-60 minutes, where 30 minutes are assigned for completing the challenge.
The last stage is an on-site interview which is 6 hours long and you get to meet 8-12 employees of Apple. This part comprises behavioral analysis, domain knowledge test, and coding challenges where each interview lasts up to 45 minutes to 1 hour.
Apple Interview Questions
Array and Graph Related Questions
- You are given a list (an array) of interval pairs as input. Each interval has a start and end timestamp and is sorted by starting timestamps. Now, you have to merge the overlapping time intervals and return a new output array. How will you do it?
For Example: For a list of pairs of timestamps (2, 6), (4, 9), (10, 14), (12, 16), the output should be (2, 9), (10, 16)
You can use a linear scan algorithm to solve this problem statement. The list of input timestamps is given, and the output list will have the merged lists. You will see if, for each interval in the input list, the last interval of the output list overlaps with the interval in the input list merging the two intervals. Further, replace the last interval of the output list with the newly merged interval.
Else, simply add the input interval to the output list. The code for the same in C++ would be as follows:
#include <iostream>
#include <vector>
using namespace std;
class Pair{
public:
int first, second;
Pair(int x, int y){
first = x;
second = y;
}
};
vector<Pair> merge_intervals(vector<Pair>& v) {
if(v.size() == 0) {
return v;
}
vector<Pair> result;
result.push_back(Pair(v[0].first, v[0].second));
for(int i = 1 ; i < v.size(); i++){
int x1 = v[i].first;
int y1 = v[i].second;
int x2 = result[result.size() - 1].first;
int y2 = result[result.size() - 1].second;
if(y2 >= x1){
result[result.size() - 1].second = max(y1, y2);
}
else{
result.push_back(Pair(x1, y1));
}
}
return result;
}
int main() {
vector<Pair> v {
Pair(2, 6),
Pair(4, 9),
Pair(10, 14),
Pair(12, 16),
Pair(18, 22)
};
vector<Pair> result = merge_intervals(v);
for(int i = 0; i < result.size(); i++){
cout << "[" << result[i].first << ", " << result[i].second << "] ";
}
}
- Write the code to determine if there are any three integers in the given array of integers that are equal to a given value.
In order to solve this problem statement, first, sort data in the array. Then, fix one array element k and find a pair (a, b) from the remaining array such that given_value-k=a+b.
Start by fixing the first element (k) of the array, then search for a pair (a, b) in the remaining array, i.e. from element arr[1] to arr[n-1] that meets the condition, given_value-k=a+b. If you find such a pair with the first element k that together makes a sum equal to the given value, no further iteration is required. Else, you will repeat the above steps for all the k elements from arr[1] to arr[n-3] until a matching sum is found.
The code for the same in C++ can be as follows:
#include <iostream>
#include <vector>
#include <bits/stdc++.h>
using namespace std;
bool sum_two(vector<int>& m, int value, size_t start ) {
for (int i = start, j = m.size() - 1; i < j;) {
int sum1 = m[i] + m[j];
if (sum1 == value) {
return true;
}
if (sum1 < value) {
++i;
} else {
--j;
}
}
return false;
}
bool sum_three_v3(vector<int> arr, int given_value) {
std::sort(arr.begin(), arr.end());
for (int i = 0; i < arr.size() - 2; ++i) {
int remaining_sum = given_value - arr[i];
if (sum_two(arr, remaining_sum, i + 1)) {
return true;
}
}
return false;
}
int main(){
//For a given array: {27, -12, 36, -5, -15, 4, 6, 8}
vector<int> arr = {27, -12, 36, -5, -15, 4, 6, 8};
cout<<"18: " <<sum_three_v3(arr, 18)<<endl;
cout<<"18: " <<sum_three_v3(arr, 1)<<endl;
return 0;
}
- You’ll be given the root node of a directed graph and you will have to clone the graph by creating a deep copy. The cloned graph is supposed to have the same edges as well as vertices.
We use depth-first traversal and create a copy of each node as we perform traversal of the graph. We will use a hash table to store every completed node so we will not have to revisit nodes stored in that hash table. The hash table key will hold a node of the original graph. So, its value will be the corresponding node in the cloned graph.
#include <iostream>
#include <vector>
#include <bits/stdc++.h>
using namespace std;
struct Node {
int data;
list<Node*> neighbors;
Node(int d) : data(d) {}
};
Node* clone_rec(Node* root,
unordered_map<Node*,
Node*>& nodes_comp) {
if (root == nullptr) {
return nullptr;
}
Node* pNew = new Node(root->data);
nodes_comp[root] = pNew;
for (Node* p : root->neighbors) {
auto x = nodes_comp.find(p);
if (x == nodes_comp.end()){
pNew->neighbors.push_back(clone_rec(p, nodes_comp));
} else {
pNew->neighbors.push_back(x->second /*value*/);
}
}
return pNew;
}
Node* clone(Node* root) {
unordered_map<Node*, Node*> nodes_comp;
return clone_rec(root, nodes_comp);
}
/*this is un-directed graph i.e. if there exists an edge from node1 to node2, there must be an edge from node2 to x
node1. And, there is no self directing edge to a node. So, the maximum number of edges that will exist in the graph is: (nodes * nodes - nodes) / 2 */
void create_test_graph_undirected(int nodes, int edges, vector<Node*>& vertices) {
for (int i = 0; i < nodes; ++i) {
vertices.push_back(new Node(i));
}
vector<pair<int, int>> all_edges;
for (int i = 0; i < vertices.size(); ++i) {
for (int j = i + 1; j < vertices.size(); ++j) {
all_edges.push_back(pair<int, int>(i, j));
}
}
std::random_shuffle(all_edges.begin(), all_edges.end());
for (int i = 0; i < edges && i < all_edges.size(); ++i) {
pair<int, int>& edge = all_edges[i];
vertices[edge.first]->neighbors.push_back(vertices[edge.second]);
vertices[edge.second]->neighbors.push_back(vertices[edge.first]);
}
}
void print_graph(vector<Node*>& vertices) {
for (Node* n : vertices) {
cout << n->data << ": {";
for (Node* t : n->neighbors) {
cout << t->data << " ";
}
cout << "}" << endl;
}
}
void print_graph(Node* root, unordered_set<Node*>& visited_nodes) {
if (root == nullptr || visited_nodes.find(root) != visited_nodes.end()) {
return;
}
visited_nodes.insert(root);
cout << root->data << ": {";
for (Node* n : root->neighbors) {
cout << n->data << " ";
}
cout << "}" << endl;
for (Node* n : root->neighbors) {
print_graph(n, visited_nodes);
}
}
void print_graph(Node* root) {
unordered_set<Node*> visited_nodes;
print_graph(root, visited_nodes);
}
int main() {
vector<Node*> vertices;
create_test_graph_undirected(7, 18, vertices);
print_graph(vertices[0]);
Node* cp = clone(vertices[0]);
cout << endl << "After copy" << endl;
print_graph(cp);
return 0;
}
Tree-Based Questions
- You have the roots of two binary trees and you have to determine if these binary trees are identical, i.e., both the trees have the exact same structure and element at each node.
We can use recursion to solve this problem in which the base case would be if two compared nodes give null or any one of them is null.
We suppose the given binary trees are A and B respectively. Pertaining to these two trees being identical, there are three possibilities:
- The root element of A and B is the same or both roots of A and B are null
- The l subtrees of A and B are identical.
- The r subtrees of A and B are identical.
We keep on comparing the elements at each level of both the trees using a depth-first traversal on both trees at the same time.
bool check_identical(
BinaryTreeNode* root1,
BinaryTreeNode* root2) {
if (root1 == nullptr && root2 == nullptr) {
return true;
}
if (root1 != nullptr && root2 != nullptr) {
return ((root1->data == root2->data) &&
check_identical(root1->l, root2->l) &&
check_identical(root1->r, root2->r));
}
return false;
}
int main() {
BinaryTreeNode *root1 = new BinaryTreeNode(100);
insert_bst(root1, 50);
insert_bst(root1, 200);
insert_bst(root1, 25);
insert_bst(root1, 125);
insert_bst(root1, 350);
display_level_order(root1);
BinaryTreeNode *root2 = create_random_BST(15);
display_level_order(root2);
// Check by changing the roots passed
if(check_identical(root1, root2)) {
cout<< " the trees are identical" << endl;
} else {
cout<< "the trees are not identical" << endl;
}
}
- How would you determine whether the r and l sub-trees in a given binary tree are mirror images?
We can solve this problem statement by writing a recursive function that will take two trees as an argument, and check recursively two roots and sub-trees under the root. It will return true if l and r sub-trees are the mirror and false if trees are not mirrored.
Two trees are mirror images of each other only if the following conditions turn true:
- Their root node’s key must be the same
- l sub-tree of l tree and r sub-tree of r tree have to be mirror images
- r sub-tree of l tree and l sub-tree of r tree have to be mirror images
Its code implementation in C++ would be as follows:
#include <bits/stdc++.h>
using namespace std;
// A structure for Binary Tree Node
struct Node
{
int key;
struct Node *l, *r;
};
// For creation of new Node
Node* newNode(int key)
{
Node* temp = new Node;
temp->key = key;
temp->l = temp->r = NULL;
return (temp);
}
//Checks if root1 and root2 mirror
bool is_mirror(struct Node* root1, struct Node* root2)
{
if (root1 == NULL && root2 == NULL)
return true;
if (root1 && root2 && root1->key == root2->key)
return is_mirror(root1->l, root2->r)
&& is_mirror(root1->r, root2->l);
return false;
}
//Checks if tree is symmetric
bool is_Symmetric(struct Node* root)
{
return is_mirror(root, root);
}
int main()
{
// Construct a tree as per your own accord
Node* root = newNode(1);
root->l = newNode(2);
root->r = newNode(2);
root->l->l = newNode(3);
root->l->r = newNode(4);
root->r->l = newNode(4);
root->r->r = newNode(3);
if(is_Symmetric(root))
cout<<"Symmetric";
else
cout<<"Not symmetric";
return 0;
}
Linked-List Based Questions
- You are asked to add two integers using linked lists. The head of the two linked lists is given and each node stores a digit of the integer. You have to create a new linked list that stores the sum of two integers.
For simplification, you can store the integers inverted in the linked lists, i.e., the most significant digit of the number is stored in the last node of the linked list. Now to start adding, start from the heads of the two linked lists. With each iteration, the digits at the current nodes of the two lists are added and the sum is stored in a new node at the tail of the result-linked list. So, make sure to maintain carry for each step, if any. In this way, you proceed for all digits and create the resultant linked list. If one of the two lists finishes earlier, continue with the other list and remaining carry.
using namespace std;
//assuming both integers are stored in a linked list example, 415 is stored as 5->1->4
LinkedListNode* add_integers(
LinkedListNode* integer1,
LinkedListNode* integer2) {
LinkedListNode* result = nullptr;
LinkedListNode* last = nullptr;
int carry = 0;
while (
integer1 != nullptr ||
integer2 != nullptr ||
carry > 0) {
int first =
(integer1 == nullptr ? 0 : integer1->data);
int second =
(integer2 == nullptr ? 0 : integer2->data);
int sum = first + second + carry;
LinkedListNode* pNew =
new LinkedListNode(sum % 10);
carry = sum / 10;
if (result == nullptr) {
result = pNew;
} else {
last->next = pNew;
}
last = pNew;
if (integer1 != nullptr) {
integer1 = integer1->next;
}
if (integer2 != nullptr) {
integer2 = integer2->next;
}
}
return result;
}
int main(int argc, char* argv[]) {
vector<int> v1 = {1, 2, 3}; // 321
vector<int> v2 = {1, 2}; // 21
LinkedListNode* first = LinkedList::create_linked_list(v1);
LinkedListNode* second = LinkedList::create_linked_list(v2);
// sum should be 321 + 21 = 342 => 2->4->3
LinkedListNode* result = add_integers(first, second);
vector<int> r = {2, 4, 3}; // 342
LinkedListNode* expected = LinkedList::create_linked_list(r);
assert(LinkedList::is_equal(result, expected));
cout << endl << "First:";
LinkedList::display(first);
cout << endl << "Second:";
LinkedList::display(second);
cout << endl << "Result:";
LinkedList::display(result);
result = add_integers(first, nullptr);
assert(LinkedList::is_equal(result, first));
result = add_integers(nullptr, second);
assert(LinkedList::is_equal(result, second));
}
- Two sorted linked lists are given and you need to merge those linked lists in a way that the resulting linked list is also sorted.
Maintain a head and a tail pointer on the resultant merged linked list and the merged linked list will be NULL initially. Choose the head of the merged linked list by comparing the first node of both linked lists.
Compare and pick the smaller current node and link it to the tail of the resultant for all the subsequent nodes in the two linked lists. Move the current pointer of the resultant list to the next node. If one of the two linked lists finishes sooner, link the remaining list to the tail of the merged list.
using namespace std;
typedef LinkedListNode* NodePtr;
NodePtr merge_sorted(NodePtr head1, NodePtr head2) {
// if both lists are empty then merged list is also empty
// if one of the lists is empty then other is the merged list
if (head1 == nullptr) {
return head2;
} else if (head2 == nullptr) {
return head1;
}
NodePtr mergedHead = nullptr;
if (head1->data <= head2->data) {
mergedHead = head1;
head1 = head1->next;
} else {
mergedHead = head2;
head2 = head2->next;
}
NodePtr mergedTail = mergedHead;
while (head1 != nullptr && head2 != nullptr) {
NodePtr temp = nullptr;
if (head1->data <= head2->data) {
temp = head1;
head1 = head1->next;
} else {
temp = head2;
head2 = head2->next;
}
mergedTail->next = temp;
mergedTail = temp;
}
if (head1 != nullptr) {
mergedTail->next = head1;
} else if (head2 != nullptr) {
mergedTail->next = head2;
}
return mergedHead;
}
void test(vector<int>& v1, vector<int>& v2, vector<int>& expected) {
LinkedListNode* list_head1 = LinkedList::create_linked_list(v1);
cout<<"List 1: "<<LinkedList::getList(list_head1)<<endl;
LinkedListNode* list_head2 = LinkedList::create_linked_list(v2);
cout<<"List 2: "<<LinkedList::getList(list_head2)<<endl;
LinkedListNode* merged = merge_sorted(list_head1, list_head2);
cout<<"Result: "<<LinkedList::getList(merged)<<endl;
LinkedListNode* expected_list = LinkedList::create_linked_list(expected);
assert(LinkedList::is_equal(merged, expected_list));
}
int main(int argc, char* argv[]) {
vector<int> v1 = {1, 3, 5, 6};
vector<int> v2 = {2, 4, 6, 20, 34};
vector<int> expected = {1, 2, 3, 4, 5, 6, 6, 20, 34};
test(v1, v2, expected);
v1 = {1, 3, 5, 6};
v2 = {};
expected = {1, 3, 5, 6};
test(v1, v2, expected);
v1 = {1, 3, 5, 6};
v2 = {2, 4, 6, 20};
expected = {1, 2, 3, 4, 5, 6, 6, 20};
test(v1, v2, expected);
v1 = {4, 4};
v2 = {4, 4, 4};
expected = {4, 4, 4, 4 ,4};
test(v1, v2, expected);
}
String Based Questions
- Reverse a given sentence. You are given a sentence and you have to reverse the words of the sentence.
While you have two sentences, you are basically dealing with an array of characters. So, the important and crucial part is to maintain the white spaces between the words. Here’s how you can code for the same in C++:
void rev_str(char * str, int len) {
if (str == nullptr || len < 2) {
return;
}
char * start = str;
char * end = str + len - 1;
while (start < end) {
if (start != nullptr && end != nullptr) {
char temp = * start;
* start = * end;
* end = temp;
}
start++;
end--;
}
}
void reverse_words(char * sentence) {
// Here sentence is a null-terminated string ending with char ''.
if (sentence == nullptr) {
return;
}
/*Now first reverse the string to reverse all the words in the sentence. You get all the words in the desired location, but the order of characters in the word has been reversed*/
int len = strlen(sentence);
rev_str(sentence, len);
char * start = sentence;
char * end;
while (true) {
while (start && * start == ' ') {
++start;
}
if (start == nullptr || * start == '') {
break;
}
end = start + 1;
while (end && * end != '' && * end != ' ') {
++end;
}
// Reverse the word in-place.
if (end != nullptr) {
rev_str(start, (end - start));
}
start = end;
}
}
int main() {
string str = "This is my world!";
char* a = const_cast<char*>(str.c_str());
cout << a << endl;
reverse_words(a);
cout << "Reverse: "<<a << endl;
}
Dynamic Programming Based Question
- For a given positive number ‘n’, determine the maximum sum of any contiguous sub-array of size ‘n’ in a given array of positive integers.
For its solution, first, find all the sub-arrays of size ‘n’ and add the numbers in them each time. Further, compare and return the maximum sum out of the calculated sums. Here’s its coding implementation in C++
#include <iostream>
using namespace std;
// Returns maximum sum in a subarray of size n
int max_Sum(int arr[], int m, int n)
{
// n must be smaller than m
if (m < n)
{
cout << "Invalid";
return -1;
}
// Compute sum of first window of size n
int res = 0;
for (int i=0; i<n; i++)
res += arr[i];
int curr_sum = res;
for (int i=n; i<m; i++)
{
curr_sum += arr[i] - arr[i-m];
res = max(res, curr_sum);
}
return res;
}
int main()
{
int arr[] = {2, 12, 7, 32, 15, 10, 8};
int n = 3;
int m = sizeof(arr)/sizeof(arr[0]);
cout << max_Sum(arr, m, n);
return 0;
}
Backtracking Based Question
- You are given a target number ‘n’ for which you have to find all the possible combinations that sum up to make n.
You can recursively go through all possible sum combinations. Display the combination for which the sum equals ‘n’. Run a ‘for’ loop in each recursive call from the start (i=1) to target (n). The current_sum variable stores the current sum of each recursive call and is incremented every time.
As a value is added to the current_sum, it is simultaneously added to the resultant list in each recursive call. This resultant list is the sum combination for that particular call. An element is added to the result before each recursion. However, after each recursive call, this element must also be removed from the list to reset the list. Whenever current_sum becomes equal to target, the possibility for the resultant list to contain the desired combination arises. This resultant list is finally appended to the output list. Here’s the C++ code for the implementation of the same:
#include <iostream>
using namespace std;
void print(vector<vector<int>> output){
for(int i = 0; i < output.size(); i++){
cout << "[ ";
for(int j = 0; j < output[i].size(); j++){
cout << output[i][j] << ", ";
}
cout << "]" << endl;
}
}
void print_all_sum_rec(
int target,
int current_sum,
int start, vector<vector<int>>& output,
vector<int>& result) {
if (target == current_sum) {
output.push_back(result);
}
for (int i = start; i < target; ++i) {
int temp_sum = current_sum + i;
if (temp_sum <= target) {
result.push_back(i);
print_all_sum_rec(target, temp_sum, i, output, result);
result.pop_back();
} else {
return;
}
}
}
vector<vector<int>> print_all_sum(int target) {
vector<vector<int>> output;
vector<int> result;
print_all_sum_rec(target, 0, 1, output, result);
return output;
}
int main(int argc, const char* argv[]) {
int n = 5;
vector<vector<int>> result = print_all_sum(n);
print (result);
}
Summing Up
So, these were just 10 common coding apple interview questions to help you with the technical interview at Apple. There are many more similar questions that can be asked. You must have got a context to the coding problems that can be posted before you. As far as data structures are concerned, you must practice questions based on arrays, linked lists, trees, graphs, hash maps, stacks, queues, and hash sets. As for algorithms, you must also be well acquainted with binary search, quick sort, merge sort, depth-first, breadth-first search, and dynamic programming. Learn how to ace coding interviews and land a job at Apple smoothly. While you are at it, you may want to upskill yourself with all the software trends and concepts. Why not opt for a PGP in Software Development?
Best of luck with your interview preparation! Happy learning!