Your experience on this website will be improved by allowing Cookies.
It's the last day for these savings
C programming remains a cornerstone of software development. As one of the earliest high-level programming languages, C has laid the foundation for countless modern languages and continues to play a vital role in system programming, embedded systems, and application development.
This article compiles 52 commonly asked C programming interview questions along with detailed answers to help you navigate topics.
C is a high-level, structured programming language developed by Dennis Ritchie in 1972 at Bell Labs. It is known for its efficiency, simplicity, and versatility.
C is both machine-independent and hardware-accessible, providing low-level memory manipulation capabilities through pointers while maintaining high-level programming constructs. It follows a procedural programming paradigm, dividing tasks into functions to promote modularity and code reuse.
C programming has several features:
Simplicity: C has a straightforward syntax and simple constructs, making it easy to learn and use.
Portability: Programs written in C can run on different platforms with minimal changes.
Efficient Performance: C produces optimized machine-level code, creating high execution speed.
Rich Library Support: It has a wide range of built-in functions for tasks like memory management, string handling, and I/O operations.
Modularity: C supports the division of code into functions, making it modular and promoting code reusability.
Low-level Access: Features like pointers and direct memory manipulation for close interaction with hardware.
Structured Programming: C follows a procedural approach, enabling better organization and maintainability of code.
Extensibility: Users can extend its capabilities by writing custom functions and linking them with libraries.
Dynamic Memory Allocation: C supports efficient memory management through functions like malloc(), calloc(), and free().
C offers several fundamental data types:
Basic Data Types:
- int: Used for integers (e.g., 10, -5).
- float: single-precision floating-point numbers (e.g., 3.14).
- double: double-precision floating-point numbers (e.g., 3.14159).
- char: single characters (e.g., 'A').
Derived Data Types:
- Arrays: Collection of elements of the same type (e.g., int arr[10]).
- Pointers: Variables that store memory addresses (e.g., int *p).
- Structures: Custom data types grouping different data types (e.g., struct { int a; char b; }).
- Unions: Similar to structures but share the same memory space for all members.
Enumeration Data Types (enum): Define a set of named integer constants (e.g., enum Color { RED, GREEN, BLUE };).
Void Data Type: Represents no value or type, primarily used for functions that do not return a value (e.g., void functionName()).
printf() is primarily for displaying data, while scanf() is used for reading data.
Aspect | printf() | scanf() |
Purpose | Used to display output on the screen. | Used to take input from the user. |
Functionality | Outputs formatted data to the console. | Reads formatted data from standard input. |
Return Value | Returns the number of characters printed. | Returns the number of successfully read inputs. |
Syntax | printf("format", arguments); | scanf("format", arguments); |
Input/Output | Works as an output function. | Works as an input function. |
Question 5: What is the use of a header file in C?
A header file in C is a file with a .h extension that contains declarations of functions, macros, constants, and data types.
Function Declarations: They provide prototypes for library and user-defined functions.
Macro Definitions: Header files can define constants and macros using #define.
Code Reusability: You can write code once and include it in multiple programs.
Data Type Definitions: They declare complex data types like struct, union, or enum for use across files.
Library Inclusion: They have standard libraries such as stdio.h, stdlib.h, or string.h for common operations.
typedef is for creating type aliases. macro is for preprocessor-level substitutions.
Aspect | typedef | macro |
Definition | Used to create new names (aliases) for existing data types. | Used to define constants or macros for code substitution. |
Scope | Respects C's scoping rules and works within its declared scope. | Global and processed at the preprocessor stage. |
Type Checking | Provides type safety during compilation. | Does not provide type safety; purely text replacement. |
Syntax | typedef existing_type alias_name; | #define macro_name value_or_expression |
Use Cases | Simplifies complex type declarations. | Defines constants, inline expressions, or code blocks. |
Question 7: What is a pointer in C?
A pointer in C is a variable that stores the memory address of another variable. You can direct access and manipulation of memory locations. Pointers are declared using the * operator and can point to any data type, enabling dynamic memory allocation, array handling, and efficient parameter passing.
Arrays are fixed-size containers for data. Pointers are flexible variables that can reference memory locations dynamically.
Aspect | Arrays | Pointers |
Definition | A collection of elements of the same type stored in contiguous memory. | A variable that stores the memory address of another variable. |
Fixed Size | The size of an array is fixed at compile time. | A pointer can point to any memory location and can dynamically change |
Syntax | Declared using square brackets (e.g., int arr[5];). | Declared with an asterisk (e.g., int *ptr;). |
Memory Allocation | Allocated in contiguous blocks. | Can point to any valid memory location. |
Operations | Cannot be directly incremented or decremented. | Can be incremented or decremented for traversal. |
Storage | Holds the actual data values. | Holds the address of a memory location. |
Size Retrieval | The size of an array can be obtained using sizeof. | sizeof returns the size of the pointer, not the size of the data it points to. |
Relationship | The name of an array acts as a pointer to its first element but is not a modifiable pointer. | A pointer is explicitly modifiable. |
A static variable in C is a variable that retains its value between function calls. It is initialized only once and stored in the static memory area. Static variables have a default value of 0 if not explicitly initialized and have a scope limited to the block in which they are defined but persist for the lifetime of the program.
Aspect | Struct | Union |
Memory Allocation | Allocates separate memory for each member. | All members share the same memory space. |
Size | Size is the sum of all members (plus padding). | Size is equal to the largest member. |
Usage | All members can hold and access values simultaneously. | Only one member can hold a value at a time. |
Purpose | Used when multiple members need to store data. | Used when multiple members represent the same data in different ways. |
Data Overlap | No data overlap between members. | Members overlap in memory, overwriting each other. |
The #define directive creates macros, which are symbolic constants or code fragments that are replaced by their definitions during preprocessing. It is commonly used for defining constants, creating inline code for efficiency, and improving code readability. For example, #define PI 3.14 replaces every occurrence of PI in the code with 3.14.
Aspect | malloc() | calloc() |
Memory Allocation | Allocates a single block of memory. | Allocates multiple blocks of memory. |
Initialization | Does not initialize allocated memory; it contains garbage values. | Initializes all allocated memory to zero. |
Arguments | Takes one argument: the total memory size in bytes. | Takes two arguments: the number of blocks and the size of each block. |
Syntax | void* malloc(size_t size) | void* calloc(size_t num, size_t size) |
The free() function deallocates memory that was previously allocated using functions like malloc(), calloc(), or realloc(). It releases the allocated memory back to the system, making it available for reuse.
Its importance:
Preventing memory leaks by unused memory is not wasted.
Improves program efficiency and resource management.
Better stability and performance, especially in long-running programs.
A memory leak occurs when a program allocates memory dynamically using functions like malloc(), calloc(), or realloc() but does not release it using free(). Leaking causes the allocated memory to remain occupied even when it is no longer needed, making it inaccessible for future use.
A memory leak in C can be avoided by:
Freeing Allocated Memory: Every malloc, calloc, or realloc call has a corresponding free call to release the allocated memory.
Tracking Pointers: Use proper pointer management to avoid losing references to allocated memory.
Using Smart Tools: Utilize tools like Valgrind to detect memory leaks during debugging.
Code Reviews: Regularly review code for all allocated memory is correctly managed.
Avoiding Redundant Allocations: Prevent overwriting pointers to dynamically allocated memory without freeing them first.
sizeof is a operator used during memory allocation to calculate the exact amount of memory required for a particular data type or structure. When functions like malloc, calloc, or realloc are used to allocate memory dynamically, sizeof makes the allocation matches the correct size of the intended data type, resulting in the program being more robust and portable.
The value returned by sizeof reflects the memory footprint of a type or variable on the specific platform, accounting for factors like alignment and architecture. It eliminates the need for hardcoding sizes, reduces errors, and improves code maintainability.
Using sizeof also promotes portability, as the size of data types can vary between systems. For example, the size of an int might differ on a 32-bit versus a 64-bit system, but sizeof can adjust accordingly. This approach prevents issues like buffer overflows, segmentation faults, or memory corruption caused by incorrect allocation sizes.
Stack and heap memory are two distinct regions used during program execution, each serving specific purposes.
Stack memory is primarily used for managing function calls, storing local variables, and handling control flow. It operates on a Last-In, First-Out (LIFO) principle, where memory is automatically allocated when a function is called and deallocated when the function returns.
Stack memory is limited in size, predefined, and offers faster access, making it efficient for temporary data. However, variables stored in the stack are restricted to the scope of their function or block, and exceeding the stack's capacity can result in a stack overflow.
In contrast, heap memory is used for dynamic memory allocation, where memory is allocated and deallocated at runtime as required. It is more flexible and larger than the stack but slower due to the manual nature of its management and the potential for fragmentation.
Memory in the heap is allocated using functions like malloc, calloc, or realloc and must be explicitly released using free. Unlike stack memory, heap memory persists until it is manually freed. It accessible globally through pointers. However, improper management can lead to memory leaks or inefficient memory usage.
The if-else and switch statements are conditional control structures in C.
Aspect | if-else Statement | switch Statement |
Condition Type | Evaluates a range of conditions using relational or logical expressions. | Works with discrete values such as integers or characters |
Complexity | Suitable for complex, multi-condition logic. | Best for simpler, fixed-value comparisons. |
Syntax | Requires multiple if, else if, and else blocks for multiple conditions | Uses case labels within a single switch block. |
Evaluation | Conditions are evaluated sequentially until a match is found. | Directly jumps to the matching case label, improving efficiency in some cases. |
Default Case | The else block handles unmatched conditions. | The default label handles unmatched cases. |
Flexibility | Can use complex expressions and ranges. | Limited to exact matches of discrete values. |
Readability | Can become less readable with many conditions. | Offers cleaner, more organized syntax for multiple discrete conditions. |
The main() function represents the entry point where program execution begins. It is essential for every C program, as the compiler and runtime environment use it to start execution. The main() function typically includes program logic, calls to other functions, and may return an integer value to indicate the program's exit status to the operating system.
A do-while loop executes the loop body at least once because the condition is evaluated after the loop body. Otherwise, a while loop evaluates the condition before executing the loop body, so it may not execute at all if the condition is initially false.
The break statement exits a loop or switch statement prematurely, skipping the remaining iterations or cases. The continue statement is used within loops to skip the current iteration and proceed with the next iteration, bypassing any remaining code in the loop body for that iteration.
A nested loop is a loop placed inside another loop. The inner loop executes completely for each iteration of the outer loop. Nested loops are for tasks like processing multidimensional arrays or performing repetitive tasks in a hierarchical structure.
Functions in C are classified into two main types:
Library Functions: Predefined functions provided by C libraries, such as printf(), scanf(), and sqrt(). They perform common tasks and are included in standard libraries.
User-Defined Functions: Functions created by programmers to perform specific tasks. These allow code reusability and better modularity.
The difference between call by value and call by reference lies in how arguments are passed to functions:
Call by Value: A copy of the argument's value is passed to the function. Changes made to the parameter inside the function do not affect the original variable.
Call by Reference: The address of the argument is passed to the function. Changes made to the parameter inside the function directly affect the original variable.
A function in C cannot directly return multiple values because it supports returning only one value. However, multiple values can be returned indirectly by:
Using pointers to modify multiple variables.
Returning a structure containing multiple values.
Using global variables to store the results.
Recursion in C is a technique - a function calls itself, either directly or indirectly, to solve a problem. It breaks down complex problems into smaller, more manageable sub-problems that follow a similar structure. A recursive function must include two main components:
Base Case: The condition that stops the recursion. Without it, the function would call itself indefinitely, leading to a stack overflow.
Recursive Case: The part of the function where it calls itself, typically with modified parameters, to move closer to the base case.
Recursion process for tasks like traversing data structures (e.g., trees), implementing algorithms (e.g., factorial calculation, Fibonacci sequence, and quicksort), or solving mathematical problems. While recursion can make code more intuitive and elegant, it must be used carefully to avoid excessive memory usage.
Inline functions are requests to the compiler to replace the function call with the actual function code during compilation, eliminating the overhead of a function call. It boosts performance, especially for small, frequently called functions.
Inline functions are used because:
Performance: By reducing function call overhead, they can speed up execution.
Code Optimization: They are useful for short functions where inlining can reduce the time spent on function calls.
Readability and Maintenance: You can define reusable logic without the overhead of traditional function calls.
However, excessive use of inline functions can lead to larger binary sizes, known as "code bloat," they may not always be inlined, as the decision ultimately lies with the compiler.
The string is a sequence of characters terminated by a null character ('\0'). Strings are represented as arrays of characters and are used to store and manipulate text. For example, the string "Hello" is stored as a character array: {'H', 'e', 'l', 'l', 'o', '\0'}.
Aspect | Strings | Character Arrays |
Definition | A sequence of characters terminated by '\0'. | A collection of characters stored in contiguous memory. |
Null Terminator | Always includes a '\0' at the end. | May or may not include a '\0'. |
Purpose | Used specifically to represent and manipulate text. | Can store any type of data, including text or binary. |
Library Functions | Can use functions like strlen(), strcpy(), etc. | Requires manual handling if not used as a string. |
Initialization | Initialized with double quotes, e.g., "Hello". | Requires manual assignment if not treated as a string. |
Usage | Focused on handling textual data. | General-purpose; not limited to textual data. |
Global variables offer shared access but can lead to unintended side effects, whereas local variables help encapsulate data within a specific context.
Aspect | Global Variables | Local Variables |
Scope | Accessible throughout the entire program. | Accessible only within the block or function where they are defined. |
Lifetime | Exists for the program's entire execution. | Exists only during the execution of the function or block. |
Storage | Stored in the global data segment. | Stored in the stack (or registers for some optimizations). |
Initialization | Automatically initialized to zero if not explicitly initialized. | Contains garbage values if not explicitly initialized. |
Usage | Used to share data between multiple functions. | Used for temporary or specific task-related data. |
Declaration | Declared outside any function, typically at the top of the file. | Declared inside a function or block. |
The strlen() function in C determines the length of a string, excluding the null terminator ('\0). It is declared in the <string.h> library. The function takes a pointer to a string as its argument and returns the number of characters in the string as a size_t value.
The purpose of strlen() is to provide the string's length for operations like copying, concatenation, or iteration. It stops counting when it encounters the null terminator, which signifies the end of the string.
Aspect | strcmp() | strncmp() |
Definition | Compares two strings entirely, character by character. | Compares up to a specified number of characters. |
Function Prototype | int strcmp(const char *str1, const char *str2); | int strncmp(const char *str1, const char *str2, size_t n); |
Comparison Length | Compares the full length of both strings. | Compares only the first n characters of the strings. |
Use Case | Used to check complete string equality or ordering. | Used for partial comparison or when only part of the string matters. |
Return Values | Returns: - 0 if strings are equal. - < 0 if str1 is less than str2. - > 0 if str1 is greater than str2. | Returns: - 0 if first n characters are equal. - < 0 if first n characters of str1 < str2. - > 0 if first n characters of str1 > str2. |
Typecasting is the process of converting a variable from one data type to another. It can be done explicitly by the programmer or implicitly by the compiler in certain cases.
Types of Typecasting:
Implicit Typecasting (Type Promotion): The compiler automatically converts smaller data types to larger compatible types to prevent data loss.
Explicit Typecasting: The programmer explicitly specifies the data type for conversion.
A file is opened using the fopen() function from the <stdio.h> library. Syntax:
FILE *fopen(const char *filename, const char *mode);
filename: The name (and path, if necessary) of the file to open.
mode: Specifies the purpose for opening the file.
Modes for File Opening in C:
Mode | Description |
"r" | Open a file for reading. The file must exist; otherwise, fopen() returns NULL. |
"w" | Open a file for writing. If the file exists, it is overwritten; if not, a new file is created. |
"a" | Open a file for appending. Data is written at the end of the file. Creates a new file if it doesn’t exist. |
"r+" | Open a file for reading and writing. The file must exist. |
"w+" | Open a file for reading and writing. If the file exists, it is overwritten; if not, a new file is created. |
"a+" | Open a file for reading and appending. Data can be read and appended at the end. Creates a new file if it doesn’t exist. |
Feature | fread() | fwrite() |
Purpose | Reads data from a file. | Writes data to a file. |
Function | size_t fread(void *ptr, size_t size, size_t count, FILE *stream); | size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream); |
Parameters | - ptr: Pointer to the buffer where data will be stored. - size: Size of each data element. - count: Number of elements to read. - stream: File pointer. | - ptr: Pointer to the buffer containing data to write. - size: Size of each data element. - count: Number of elements to write. - stream: File pointer. |
Return Value | Returns the number of elements successfully read. | Returns the number of elements successfully written. |
Usage | Used to read binary data into memory. | Used to write binary data from memory to a file. |
Feature | exit() | _exit() |
Behavior | Performs cleanup before terminating the program. | Terminates the program immediately. |
Flush Buffers | Flushes standard I/O buffers (e.g., stdout). | Does not flush standard I/O buffers. |
Calls Handlers | Executes registered atexit() functions and closes files properly. | Skips atexit() handlers and file cleanup. |
Use Case | Used for graceful termination. | Used for immediate, low-level termination (e.g., in system calls or child processes). |
Header File | Declared in <stdlib.h>. | Declared in <unistd.h>. |
fprintf() and printf() differ in their target output destination.
Feature | fprintf() | printf() |
Output Destination | Outputs to a specified file or stream. | Outputs to the standard output (stdout). |
Syntax | int fprintf(FILE *stream, const char *format, ...); | int printf(const char *format, ...); |
Use Case | Used for writing formatted data to files or streams. | Used for writing formatted data to the console. |
Macros are a feature offered by the preprocessor. With it, you can define symbolic names or expressions that are replaced in the code before compilation. They are defined using the #define directive and can represent constants, expressions, or even small code snippets.
Macros enhance code readability and maintainability by replacing repetitive code and making it easier to update values or logic across a program. However, unlike functions, macros do not perform type checking, as they are directly replaced by their values or expressions during preprocessing.
Feature | volatile | restrict |
Focus | Ensures memory access is always performed. | Enables compiler optimization. |
Use Case | Deals with unexpected changes in memory. | Ensures no memory aliasing occurs. |
Context | Applied to variables. | Applied to pointers. |
Optimization | Prevents compiler optimization. | Allows better optimization. |
Dynamic memory for a 2D array can be allocated using pointers and functions like malloc or calloc. There are two common methods:
Array of Pointers: Allocate memory for an array of row pointers, then allocate memory for each row separately.
Single Block Allocation: Allocate a single block of memory and calculate indices manually.
Qualifier | const | volatile |
Purpose | Make sure a variable's value cannot be modified after initialization. | Indicates a variable's value can change unexpectedly. |
Use Case | To declare read-only variables. | For variables modified by external factors like hardware or interrupts. |
Behavior | Compiler enforces immutability. | Compiler avoids optimizations for consistent memory access. |
Impact | Prevents modification of the variable. | Every access reads the latest value from memory. |
Typecasting permits the conversion of a variable from one data type to another. It can be done in two ways:
Implicit Typecasting (Type Promotion): Performed automatically by the compiler when assigning a value of a smaller data type to a larger one (e.g., int to float).
Explicit Typecasting (Type Casting by the Programmer): Done manually using the cast operator (type).
Aspect | Compiled Language | Interpreted Language |
Execution | Source code is compiled into machine code before execution. | Source code is executed line by line at runtime. |
Speed | Faster execution due to pre-compilation. | Slower execution due to real-time interpretation. |
Debugging | Errors are identified during the compilation process. | Errors are detected during runtime. |
Portability | Platform-specific; needs recompilation for different systems. | Platform-independent; runs on any system with an interpreter. |
#include is a preprocessor directive used to include the contents of another file into the program before compilation. Roles of #include:
Access Standard Libraries
Include Custom Headers
Code Modularity: separates declarations and definitions for better maintainability.
Avoid Redundancy: Prevents redefining functions or macros by including files only once (usually with header guards or #pragma once).
Conditional compilation directives in C control code compilation based on conditions. They are:
#ifdef / #ifndef: Check if a macro is defined or not.
#if / #elif / #else: Evaluate constant expressions.
#endif: End a conditional block.
#undef: Undefine a macro.
Limitations of C Programming:
No Object-Oriented Features: Lacks concepts like classes and objects.
Manual Memory Management: Requires explicit handling of memory allocation and deallocation.
No Exception Handling: Does not have built-in error or exception handling.
Limited Standard Library: Fewer built-in functions compared to modern languages.
No Namespace Support: Increases risk of name conflicts.
Platform Dependency: Code behavior may vary across systems.
Unsafe Type Checking: Weak type-checking can lead to runtime errors.
No Direct Support for Multithreading: Requires external libraries for concurrency.
Pragmas in C are preprocessor directives that give special instructions to the compiler to modify its default behavior during the compilation process. These instructions are compiler-specific, meaning their behavior may vary across different compilers.
Multiple inclusions of a file in C are avoided using header guards or #pragma once.
Header Guards: A header guard uses preprocessor directives to make the file's content included only once during compilation.
#pragma once: is a compiler-specific directive that prevents multiple inclusions of a file.
Common Runtime Errors in C:
Segmentation Fault: Accessing invalid memory or dereferencing null/invalid pointers.
Buffer Overflow: Writing beyond the bounds of an array or buffer.
Memory Leak: Allocating memory dynamically without freeing it.
Division by Zero: Attempting to divide a number by zero.
Stack Overflow: Excessive recursion causing the stack to exceed its limit.
Dangling Pointer Access: Using a pointer after the memory it points to has been freed.
Uninitialized Variable Access: Using variables without initializing them.
Invalid Memory Access: Reading or writing to unallocated memory.
Integer Overflow/Underflow: Exceeding the range of an integer data type.
Invalid File Handling: Accessing a file that cannot be opened or does not exist.
A segmentation fault in C is a runtime error that occurs when a program attempts to access memory that it does not have permission to access or tries to access a memory location improperly. It typically happens when the program violates memory access rules set by the operating system.
Segmentation faults in C can be avoided by following these practices:
Initialize Pointers: All pointers are initialized before use.
Check Pointer Validity: Verify pointers are not null before dereferencing.
Avoid Dangling Pointers: Do not use pointers to memory that has been freed.
Stay Within Array Bounds: Access elements only within the valid range.
Allocate Sufficient Memory: Memory allocation is adequate for the intended use.
Use Safe Functions: Prefer safer versions of functions, such as strncpy instead of strcpy.
Avoid Writing to Read-Only Memory: Do not modify string literals or other read-only data.
Enable Debugging Tools: Use tools like valgrind or address sanitizers to detect memory access issues during development.
Some courses are very useful for you in the process of learning Programming:
Hands on Debugging in C and C++
In this course you will learn how to use the popular debugger GDB to find errors in your C and C++ code. Learning how to use a debugger will allow you to save time when finding errors and spend more time building better software.
Learn animation using CSS3, Javascript and HTML5
You'll learn how to use CSS3 syntax to create quick and smooth animations without needing javascript. Then you will learn how to leverage jQuery's animation methods, events, and properties to get animating quickly.
LLM - Fine tune with custom data
If you're passionate about taking your machine learning skills to the next level, this course is tailor-made for you. Get ready to embark on a learning journey that will empower you to fine-tune language models with custom datasets, unlocking a realm of possibilities for innovation and creativity.
Well done, you just finished 52 C Programming interview questions with detailed answers. Our blog covered a broad spectrum of topics - syntax, memory management, pointers, data structures, and debugging techniques.
Hope you be better equipped to demonstrate your proficiency, solve problems effectively, and articulate your thought process clearly during interviews.
If you want to learn better in the field of technology, you can register for Skilltrans courses. A lot of new knowledge updated regularly will be the stepping stone leading you to the door of success.
Meet Hoang Duyen, an experienced SEO Specialist with a proven track record in driving organic growth and boosting online visibility. She has honed her skills in keyword research, on-page optimization, and technical SEO. Her expertise lies in crafting data-driven strategies that not only improve search engine rankings but also deliver tangible results for businesses.