How would you characterize C as a programming language and its primary design philosophy?
Answer
C is a procedural language designed for system-level programming, offering direct memory access through pointers and minimal runtime overhead.
These are the C questions you'll encounter in technical interviews, each with a detailed written answer. Master pointers and memory management, then work through advanced topics like function pointers and the preprocessor.
C is a procedural, low-level language that remains foundational to computer science. Its simplicity and efficiency make it ideal for operating systems, embedded systems, and performance-critical applications.
C interviews assess your understanding of pointers, memory management, the preprocessor, and how to write efficient, portable code. You'll discuss memory layouts, function pointers, and low-level programming concepts.
C expertise is invaluable for embedded systems and systems programming roles. Master the language and you'll understand how computers really work.
Reading answers is not the same as giving them. Sit a full mock interview and get scored on how you actually explain them.
Try a mock interviewAnswer
C is a procedural language designed for system-level programming, offering direct memory access through pointers and minimal runtime overhead.
Answer
C's efficiency, minimal overhead, and direct hardware access make it ideal for operating systems, embedded systems, and performance-critical applications.
Answer
Pointers store memory addresses. The & operator gets an address; the * operator dereferences it to access the value at that address.
Answer
Declaration announces a variable/function exists and its type; definition allocates storage or provides implementation. One declaration can have multiple definitions.
Answer
Static variables have file or local scope, persist throughout program execution, and retain their value across function calls.
Answer
typedef creates a new name for an existing type. Example: typedef unsigned int uint32_t makes uint32_t an alias for unsigned int.
Answer
Structures use struct keyword to group mixed data types. Members are accessed via dot notation (var.member) or arrow notation (ptr->member).
Answer
malloc and calloc allocate memory on the heap at runtime. This memory must be freed with free() to avoid leaks.
Answer
malloc(size) returns uninitialized memory. calloc(num, size) returns zero-initialized memory suitable for arrays.
Answer
The preprocessor processes directives before compilation: #include includes files, #define creates macros, #ifdef handles conditional compilation.
Answer
C is a procedural language designed for efficiency and direct hardware control. Its design goals include minimal runtime overhead, direct memory access through pointers, and portability across platforms. C lacks high-level abstractions like automatic memory management or garbage collection, placing responsibility on the programmer. This makes C suitable for operating systems, embedded systems, and performance-critical applications where control and efficiency are paramount. The language provides low-level operations while maintaining enough abstraction to be portable across different hardware platforms.
Answer
A pointer declaration reserves space for a memory address: int *ptr declares ptr as a pointer to int. The & operator gets an address: int x; ptr = &x; makes ptr point to x. The * operator dereferences: *ptr accesses the value at the address. Pointer arithmetic treats pointers as arrays: ptr++ moves to the next element based on the type size. Accessing arrays through pointers: *(ptr + i) accesses the i-th element. Understanding pointer mechanics is crucial for array/string manipulation and dynamic memory allocation.
Answer
malloc(size) allocates size bytes and returns a void pointer; the programmer must cast it. calloc(num, size) allocates space for num elements of size bytes, initializing all bytes to zero. free(ptr) deallocates memory. Common pitfalls include: (1) not checking if allocation succeeded before using the pointer, (2) forgetting to free memory, causing leaks, (3) using freed memory (dangling pointers), (4) freeing the same pointer twice, (5) allocating the wrong size. Double-checking allocation and proper cleanup in all code paths prevents these issues.
Answer
Structures group different data types: struct Point { int x; int y; } defines a structure with two integers. Members are accessed via dot notation for variables: p.x and via arrow notation for pointers: ptr->x (equivalent to (*ptr).x). Memory layout is contiguous with potential padding for alignment. Defining typedef struct simplifies usage. Nested structures are possible. Structures form the foundation for building complex data types and are essential for organizing related data in systems programming.
Answer
Arrays are contiguous memory blocks: int arr[10] allocates space for 10 integers. Multi-dimensional arrays are matrices: int matrix[3][4] creates a 3x4 array. Arrays decay to pointers in most contexts: int *ptr = arr makes ptr point to arr[0]. Pointer arithmetic traverses arrays: arr[i] is equivalent to *(arr + i). Multi-dimensional arrays use pointer-to-array types: int (*ptr)[4] points to an array of 4 integers. Understanding array-pointer equivalence is fundamental to C.
Answer
C uses pass-by-value for all parameters: values are copied into function parameters. To simulate pass-by-reference, pass pointers: void modify(int *x) { *x = 5; } modifies the original variable. For arrays and structures, copying is inefficient, so they're often passed by pointer. Pass-by-value protects the original variable from accidental modification; pass-by-pointer allows intentional modification. Choosing between them depends on whether the function should modify the argument and efficiency concerns.
Answer
Local variables are declared inside functions, visible only within that scope, and exist for the function's duration. Global variables are declared outside functions, visible everywhere (within the same or linked files), and exist for the program's lifetime. Static variables declared inside functions retain their value between calls and are initialized once. Static global variables are visible only in their file. External variables allow visibility across files. Proper scoping prevents naming conflicts and clarifies code intent. Global state makes debugging harder; prefer local scope.
Answer
File operations use FILE pointers: FILE *fp = fopen(filename, mode) opens a file. fopen returns NULL on failure, so always check. Reading uses fgets for strings, fscanf for formatted input, fread for binary data. Writing uses fputs, fprintf, fwrite. Always close files with fclose(fp) to flush buffers and release resources. Unclosed files cause resource leaks and data loss if not flushed. Using fopen/fclose ensures proper resource management. Modern practice uses error checking after every file operation.
Answer
#define creates macros: #define MAX 100 replaces MAX with 100 throughout. Function-like macros: #define ADD(a,b) ((a)+(b)) use parameters. #include tells the preprocessor to include a file: #include <stdio.h> for system headers, #include "custom.h" for local files. #ifdef, #if, #ifndef enable conditional compilation, including/excluding code based on conditions. Preprocessor runs before compilation, expanding macros and including files. Debugging macros is tricky since errors appear in expanded code, not the macro definition.
Answer
C requires explicit memory allocation and deallocation. Failing to free allocated memory causes leaks where memory becomes inaccessible but not reused. Accessing freed memory (use-after-free) causes undefined behavior and crashes. Overwriting allocated memory (buffer overflow) corrupts the heap and adjacent data. Double-freeing the same pointer is undefined behavior. These issues are difficult to debug because errors appear distant from their cause. Best practices include: always check allocation success, write cleanup code immediately after allocation, use consistent allocation/deallocation patterns, and validate buffer sizes before writing.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I'd first enable core dumps to get a stack trace. I'd use gdb to run the program and identify which line caused the fault. Common causes include dereferencing NULL pointers, accessing freed memory (use-after-free), or writing outside allocated buffers. I'd add debug prints or use gdb breakpoints to inspect pointer values and memory state. I'd verify all pointers are properly initialized before use and check array bounds.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I allocated a dynamic array with malloc, used it for calculations, and ensured free() was called in all code paths including error handling. I checked that malloc succeeded before using the pointer. I avoided double-free by setting pointers to NULL after freeing. I used consistent allocation/deallocation patterns throughout the codebase.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I defined a struct node with data and a pointer to the next node. I implemented functions to create, insert at head/tail, search, delete, and print the list. Each function handled memory carefully, checking pointers and properly freeing nodes. Insertions allocated memory; deletions freed it. I wrote a main function to test the operations.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I profiled the code using gprof to identify hot spots. I replaced slow string operations with optimized versions, avoided repeated calculations by caching results, and reduced memory allocations in tight loops. I used efficient algorithms like sorting before searching. I compiled with optimization flags (-O2, -O3) and reviewed generated assembly to understand the performance.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I read the file in binary mode and cast the data to the appropriate struct. I was careful with endianness and structure packing. I checked file operations for errors and read only the expected number of bytes. I validated the data before using it. I used #pragma pack or compiler flags to control structure padding to match file format expectations.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I checked the return values of all file operations: fopen returns NULL on failure, fread returns the number of items read, fclose can fail writing buffered data. When operations failed, I printed error messages using perror() or strerror(), freed allocated resources, and returned error codes. I logged details for debugging while providing user-friendly error messages.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I used const pointers when functions shouldn't modify data, passed large structures by pointer to avoid copying, and validated pointers before dereferencing. I documented which function owns pointer memory and which must free it. I avoided pointer aliasing issues by not assuming multiple pointers refer to the same location.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I used qsort from the standard library, which takes a function pointer for comparison. I defined a comparison function matching qsort's requirements: taking two const void pointers, comparing them, and returning -1, 0, or 1. This made the sort algorithm reusable for any data type. I explained how the callback pattern enables generic code in C.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I showed how the compiler pads structures to align members on word boundaries for efficient hardware access. Example: struct { char c; int i; } wastes space between c and i. Using #pragma pack(1) eliminates padding but sacrifices performance. I demonstrated how to inspect sizeof() and offsetof() to understand memory layout.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I used valgrind --leak-check=full to analyze the program and identify which memory allocations weren't freed. I used gdb to set breakpoints on malloc and free to trace allocation history. I added instrumentation to malloc/free calls to log all allocations and verify they were freed. Once leaks were identified, I traced back to the code causing them.
#include <string.h>
void reverse_string(char *str) {
// Implement string reversal
}Answer
#include <string.h>
void reverse_string(char *str) {
int left = 0;
int right = strlen(str) - 1;
while (left < right) {
char temp = str[left];
str[left] = str[right];
str[right] = temp;
left++;
right--;
}
}#include <stdlib.h>
int** allocate_matrix(int rows, int cols) {
// Allocate matrix
}
void free_matrix(int **matrix, int rows) {
// Free allocated memory
}Answer
#include <stdlib.h>
int** allocate_matrix(int rows, int cols) {
int **matrix = malloc(rows * sizeof(int*));
if (!matrix) return NULL;
for (int i = 0; i < rows; i++) {
matrix[i] = malloc(cols * sizeof(int));
if (!matrix[i]) return NULL;
}
return matrix;
}
void free_matrix(int **matrix, int rows) {
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
}#include <stdio.h>
int count_lines(const char *filename) {
// Count lines in file
}Answer
#include <stdio.h>
int count_lines(const char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) return -1;
int count = 0;
int ch;
while ((ch = fgetc(fp)) != EOF) {
if (ch == '\n') count++;
}
fclose(fp);
return count;
}#include <stdio.h>
void print_array(int *arr, int size) {
// Print array using pointer arithmetic
}Answer
#include <stdio.h>
void print_array(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}#include <stdio.h>
#include <string.h>
typedef struct {
char *name;
int age;
} Person;
Person* create_person(const char *name, int age) {
// Create and allocate person
}Answer
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *name;
int age;
} Person;
Person* create_person(const char *name, int age) {
Person *p = malloc(sizeof(Person));
if (!p) return NULL;
p->name = malloc(strlen(name) + 1);
if (!p->name) { free(p); return NULL; }
strcpy(p->name, name);
p->age = age;
return p;
}
void display_person(Person *p) {
if (p) printf("%s, %d\n", p->name, p->age);
}
void free_person(Person *p) {
if (p) { free(p->name); free(p); }
}#include <string.h>
char* safe_string_copy(const char *source, size_t max_length) {
// Implement safe copy
}Answer
#include <stdlib.h>
#include <string.h>
char* safe_string_copy(const char *source, size_t max_length) {
if (!source) return NULL;
size_t len = strlen(source) + 1;
if (len > max_length) return NULL;
char *dest = malloc(len);
if (!dest) return NULL;
strncpy(dest, source, len - 1);
dest[len - 1] = '\0';
return dest;
}#include <stdlib.h>
int compare_integers(const void *a, const void *b) {
// Implement comparison
}Answer
#include <stdlib.h>
int compare_integers(const void *a, const void *b) {
int val_a = *(int*)a;
int val_b = *(int*)b;
return val_a - val_b;
}#include <stdlib.h>
#include <stdio.h>
void* safe_malloc(size_t size) {
// Allocate with error checking
}Answer
#include <stdlib.h>
#include <stdio.h>
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if (!ptr) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
return ptr;
}#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
float value;
} Record;
Record* read_records(const char *filename, int *count) {
// Read binary records
}Answer
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
float value;
} Record;
Record* read_records(const char *filename, int *count) {
FILE *fp = fopen(filename, "rb");
if (!fp) return NULL;
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
int num_records = size / sizeof(Record);
rewind(fp);
Record *records = malloc(num_records * sizeof(Record));
if (!records) { fclose(fp); return NULL; }
fread(records, sizeof(Record), num_records, fp);
fclose(fp);
*count = num_records;
return records;
}#define SWAP(a, b) \
// Implement safe swap macroAnswer
#define SWAP(a, b) do { \
typeof(a) temp = a; \
a = b; \
b = temp; \
} while (0)