Many software engineer roles hire mainly through coding rounds on data structures and algorithms. Candidates who can solve array, string, linked list, tree, graph and dynamic programming problems cleanly in C, and explain complexity, clear these rounds at services and product companies. After joining, engineers often move to C++, Java or Python, but the problem solving base stays the same.
Data structures Algorithms Complexity analysis Problem solving Clean coding
Solved for this roleStart 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
Merge two sorted arrays into a third sorted array.
Walk both arrays with two indexes.
void merge(const int *a, int n, const int *b, int m, int *out) {
int i = 0, j = 0, k = 0;
while (i < n && j < m)
out[k++] = (a[i] <= b[j]) ? a[i++] : b[j++];
while (i < n) out[k++] = a[i++];
while (j < m) out[k++] = b[j++];
}
Time O(n + m). Using <= keeps the merge stable.
In-order visits left, node, right and gives sorted output for a BST. Pre-order visits node, left, right and is used to copy or serialise a tree. Post-order visits left, right, node and is used to delete a tree or evaluate expression trees. Level-order visits nodes level by level using a queue. The first three are depth first and use recursion or an explicit stack.
03
Your linked list program crashes when deleting the last node. What do you check?
Check that the previous node's next pointer is set to NULL after freeing the last node, that the tail pointer is updated if you keep one and that you do not access the node after free. Also handle the case of a one-node list where head itself must become NULL, which requires passing a pointer to the head pointer. Run the code under AddressSanitizer to find invalid accesses.
04
What is a binary search tree and what are its operation costs?
A binary search tree keeps every node's left subtree smaller and right subtree larger than the node. Search, insert and delete follow one path from the root, so they cost O(h), where h is the height. A balanced tree has h of about log n, but inserting sorted data creates a skewed tree with h equal to n. In-order traversal of a BST gives keys in sorted order.
05
Describe a time you failed in an interview or test and what you learned.
Situation: in my first product company interview I froze on a linked list reversal. Task: improve before the next opportunity. Action: I practised pointer problems daily, always drawing boxes and arrows before coding, and did mock interviews with friends where I spoke my thinking aloud. Result: two months later I solved a harder linked list problem calmly and got the offer.
06
A function to insert at the head does not change the list in main. Why?
C passes arguments by value, so the function receives a copy of the head pointer; changing that copy does not affect main. Pass a pointer to the head, as in void push(Node **head, int x), and assign *head = newNode, or return the new head and assign it in the caller with head = push(head, x). This is a very common fresher interview mistake.
07
Describe how you approach a coding problem you have never seen before.
Situation: in a coding round I got an unfamiliar matrix problem. Task: solve it within 30 minutes. Action: I restated the problem, worked through a small example by hand, explained a brute force first, then spotted that a prefix sum would remove repeated work and coded it in C while talking through edge cases like empty rows. Result: I finished in 25 minutes and the interviewer praised my clear reasoning.
C stores multidimensional arrays in row-major order: all elements of row 0, then row 1 and so on, in one contiguous block. Element a[i][j] of an array with C columns is at offset i * C + j. Traversing row by row is cache friendly and much faster than column by column for large matrices. When passing a 2D array to a function, every dimension except the first must be specified.
A singly linked list has a next pointer only and can be traversed one way. A doubly linked list adds a prev pointer, allowing backward traversal and O(1) deletion of a known node, at the cost of more memory. A circular linked list links the last node back to the first, useful for round-robin scheduling. Variants include lists with sentinel or dummy head nodes.
Split, sort halves, merge with a temp buffer.
void merge(int *a, int l, int m, int r, int *tmp) {
int i = l, j = m + 1, k = l;
while (i <= m && j <= r) tmp[k++] = (a[i] <= a[j]) ? a[i++] : a[j++];
while (i <= m) tmp[k++] = a[i++];
while (j <= r) tmp[k++] = a[j++];
for (k = l; k <= r; k++) a[k] = tmp[k];
}
void mergeSort(int *a, int l, int r, int *tmp) {
if (l >= r) return;
int m = l + (r - l) / 2;
mergeSort(a, l, m, tmp);
mergeSort(a, m + 1, r, tmp);
merge(a, l, m, r, tmp);
}
A topological sort orders the vertices of a directed acyclic graph so every edge goes from earlier to later. Kahn's algorithm repeatedly removes vertices with zero in-degree using a queue; DFS adds vertices to the order after finishing their descendants. If not all vertices are processed, the graph has a cycle. It is used for build systems, course prerequisites and task scheduling.
C has no string type. A string is a char array ending with the null character '\0', which marks its end. char s[] = "hi" creates an array of 3 bytes: 'h', 'i' and '\0'. Library functions like strlen and printf scan until the null terminator, so forgetting it, or writing past the array, causes reading of random memory and crashes.
13
A recursive tree traversal crashes on a very large, skewed tree. How do you fix it?
A skewed tree has height close to n, so recursion depth becomes huge and overflows the call stack. Convert the traversal to an iterative version using an explicit stack allocated on the heap, or use Morris traversal for in-order with O(1) extra space. Long term, use a self-balancing tree so height stays around log n.
Count transitions from space to non-space.
#include <ctype.h>
int countWords(const char *s) {
int count = 0, inWord = 0;
for (; *s; s++) {
if (isspace((unsigned char)*s)) {
inWord = 0;
} else if (!inWord) {
inWord = 1;
count++;
}
}
return count;
}
This handles multiple spaces, tabs and leading or trailing spaces correctly.
15
Count the number of islands in a grid of land and water cells. What approach do you take?
Treat each land cell as a graph node connected to its four neighbours. Scan the grid, and when you find unvisited land, increment the count and flood fill with DFS or BFS to mark the whole island visited. Time and space are O(rows times cols). For very large grids, use iterative BFS to avoid recursion depth issues, or union-find.
16
What is a linked list and how is it different from an array?
A linked list is a chain of nodes where each node holds data and a pointer to the next node, allocated separately on the heap. Unlike an array, it grows easily and inserts or deletes in O(1) once you have the position, but it has O(n) access by index, extra memory per node for pointers and poor cache locality. Arrays win for random access and iteration speed.
17
You must schedule the maximum number of non-overlapping meetings in one room. What algorithm do you use?
This is activity selection, solved greedily. Sort meetings by end time, then pick each meeting whose start is not earlier than the end of the last picked one. Choosing the earliest finishing meeting always leaves the most room for the rest, which can be proven by an exchange argument. Sorting dominates, giving O(n log n) time.
Flip each next pointer while walking the list.
typedef struct Node { int data; struct Node *next; } Node;
Node *reverse(Node *head) {
Node *prev = NULL, *cur = head;
while (cur) {
Node *next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
Time O(n), space O(1). The returned prev is the new head.
19
What is the difference between a complete, full and perfect binary tree?
A full binary tree has every node with zero or two children. A complete binary tree has all levels filled except possibly the last, which is filled from left to right; heaps use this shape so they fit an array. A perfect binary tree has all internal nodes with two children and all leaves at the same level, so it has 2 to the power h+1 minus 1 nodes.
An enum defines a set of named integer constants, such as enum Color { RED, GREEN, BLUE };, where values start at 0 unless set. It makes code readable and lets compilers warn about missing cases in a switch with -Wswitch. Enum values are int sized in practice, and C23 allows specifying the underlying type. Use them for states, commands and error codes.
Use a slow pointer moving one step and a fast pointer moving two steps. If the list has a cycle, the fast pointer eventually meets the slow one inside the loop; if fast reaches NULL, there is no cycle. To find the cycle start, reset one pointer to head and move both one step at a time; they meet at the start. It uses O(n) time and O(1) space.
22
What is the difference between char s[] = "hello" and char *s = "hello"?
char s[] = "hello" creates a modifiable array of 6 bytes, usually on the stack, initialised with a copy of the literal. char *s = "hello" makes a pointer to a string literal stored in read-only memory; modifying it is undefined behaviour and often crashes. Declare such pointers as const char * so the compiler catches accidental writes.
auto is the default for local variables, stored on the stack with block lifetime. register hints the variable should live in a CPU register, and its address cannot be taken. static gives a local variable lifetime for the whole program, or limits a global to its file. extern declares a variable defined in another file. Storage class decides scope, lifetime and linkage.
24
What is the time complexity of common linked list operations?
Access or search by value is O(n). Insert or delete at the head is O(1). Insert at the tail is O(1) if you keep a tail pointer, otherwise O(n). Deleting a given node is O(1) in a doubly linked list but O(n) in a singly linked list, since you must find the previous node. Finding length is O(n) unless you store a count.
Track the best sum ending here and the overall best.
long maxSubarray(const int *a, int n) {
long cur = a[0], best = a[0];
for (int i = 1; i < n; i++) {
cur = (cur + a[i] > a[i]) ? cur + a[i] : a[i];
if (cur > best) best = cur;
}
return best;
}
Input {-2, 1, -3, 4, -1, 2, 1, -5, 4} returns 6 for subarray 4, -1, 2, 1.
26
You need to find whether any two numbers in an unsorted array sum to a target. Compare approaches.
Brute force checks all pairs in O(n squared). Sorting first with qsort and then using two pointers takes O(n log n) time and O(1) extra space but loses original indexes. A hash set gives O(n) time with O(n) memory; in C you would implement a simple open addressing table. Choose based on size, memory limits and whether indexes must be returned.
27
How does binary search work and what are common bugs?
Binary search works on sorted data by comparing the middle element with the target and discarding half the range each step, giving O(log n). Common bugs are computing mid as (low + high) / 2, which can overflow, so use low + (high - low) / 2; wrong loop conditions causing infinite loops; and off-by-one errors when finding first or last occurrences. Test with zero, one and two elements.
28
Why are self-balancing trees like AVL and red-black trees needed?
A plain BST can degrade to a linked list with O(n) operations when data arrives in sorted order. AVL trees keep the height difference of subtrees at most one using rotations, giving faster lookups. Red-black trees use colour rules with looser balance, so inserts and deletes need fewer rotations. Both guarantee O(log n). Red-black trees are used in the Linux kernel, C++ std::map and Java TreeMap.
29
A search on a sorted array of 10 lakh elements is done with a linear loop. How would you justify changing it?
Linear search takes up to 10 lakh comparisons, while binary search needs about 20, because log base 2 of 10 lakh is roughly 20. If searches are frequent, this is a huge saving at zero memory cost. If data changes often, keeping it sorted costs inserts, so consider a balanced tree or hash table. Measure with realistic workloads before and after.
Adding an integer to a pointer moves it by that many elements, not bytes, so p + 1 on an int pointer advances by sizeof(int). Subtracting two pointers into the same array gives the number of elements between them as ptrdiff_t. Arithmetic is valid only within an array or one past its end; going further is undefined behaviour. Void pointer arithmetic is not allowed in standard C.
31
What does it mean that arrays decay to pointers when passed to functions?
When you pass an array to a function, C passes only a pointer to its first element, not a copy of the array. So void f(int a[]) is the same as void f(int *a), sizeof a inside gives the pointer size and changes to elements affect the caller's array. Always pass the length as a separate parameter, such as f(arr, n).
32
Find the lowest common ancestor of two nodes in a binary search tree.
Use the BST order to walk down from the root.
TNode *lcaBST(TNode *root, int a, int b) {
while (root) {
if (a < root->key && b < root->key) root = root->left;
else if (a > root->key && b > root->key) root = root->right;
else return root;
}
return NULL;
}
The first node where a and b split is the LCA. Time O(h) and space O(1).
Swap characters from both ends moving inward.
void reverse(char *s) {
size_t n = strlen(s);
if (n < 2) return;
for (size_t i = 0, j = n - 1; i < j; i++, j--) {
char t = s[i];
s[i] = s[j];
s[j] = t;
}
}
Time O(n) and O(1) extra space. The string must be a modifiable array, not a literal.
34
How do you find the middle of a linked list in one pass?
Use slow and fast pointers starting at the head. Move slow by one node and fast by two nodes each step. When fast reaches the end or NULL, slow is at the middle. For an even number of nodes this gives the second middle; start fast at head->next to get the first middle instead. It takes O(n) time and O(1) space.
35
Why is gets() dangerous and what should you use instead?
gets() reads a line into a buffer without knowing its size, so long input overflows the buffer and can be used for code execution attacks. It was removed in C11. Use fgets(buf, sizeof buf, stdin), which stops at the buffer size and keeps the newline, then strip the newline if needed. For formatted input, give scanf field widths like %19s.
Recursion is when a function calls itself on a smaller subproblem until reaching a base case. It expresses tree traversal, divide and conquer and backtracking naturally. Risks are stack overflow when depth is large, repeated work when subproblems overlap, like naive Fibonacci, and function call overhead. Use memoisation for overlapping subproblems and iterative versions when depth may be huge.
37
A recursive Fibonacci solution times out for n = 50. How do you fix it?
Naive recursion recomputes the same subproblems repeatedly, giving about O(1.6 to the n) calls. Memoise results in an array so each value is computed once, giving O(n), or iterate bottom-up keeping only the last two values for O(1) space. Use unsigned long long, since fib(50) exceeds the int range. Matrix exponentiation gives O(log n) for very large n.
38
Write binary search in C that returns the index of the target or -1.
Use an overflow safe midpoint.
int binarySearch(const int *a, int n, int target) {
int lo = 0, hi = n - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (a[mid] == target) return mid;
if (a[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return -1;
}
Time O(log n). The array must be sorted in ascending order.
Height is one plus the larger subtree height.
int height(const TNode *root) {
if (root == NULL) return 0;
int lh = height(root->left);
int rh = height(root->right);
return 1 + (lh > rh ? lh : rh);
}
This counts nodes on the longest root to leaf path; return -1 for empty trees if height is defined in edges. Time O(n).
Situation: in my second year I could not solve even easy array problems in time. Task: become placement ready in six months. Action: I followed a topic-wise sheet, solved two problems daily in C, wrote down the pattern behind each one and revised mistakes every Sunday. Result: I solved over 300 problems, cleared three online coding tests and now explain complexity confidently.
Frequently asked questions
What does a Software Engineer (DSA focused) do?
Many software engineer roles hire mainly through coding rounds on data structures and algorithms. Candidates who can solve array, string, linked list, tree, graph and dynamic programming problems cleanly in C, and explain complexity, clear these rounds at services and product companies. After joining, engineers often move to C++, Java or Python, but the problem solving base stays the same.
Which skills are tested in a Software Engineer (DSA focused) interview?
Expect questions on Data structures, Algorithms, Complexity analysis, Problem solving, Clean coding. Most rounds also ask you to explain a project and solve one practical problem live.
How do I prepare for a Software Engineer (DSA focused) interview?
Revise Arrays and Strings, Linked Lists, Trees and Graphs, Sorting, Searching and Dynamic Programming first using the topic sheets, practise the questions on this page aloud, then do one full mock interview to practise timing.