Welcome to the Python Interview Questions section. This section covers commonly asked Python questions in Facebook (Meta) interviews, explained in a simple and easy-to-understand manner to help you clearly understand each concept.
1. How Python Handles Memory Management Internally
Python manages memory automatically, so developers do not manually allocate or free memory; instead, Python internally controls memory using reference counting, garbage collection, and optimized allocators.
Core Components of Python Memory Management
- Private Heap Space
- Reference Counting
- Garbage Collection (Cyclic GC)
- Memory Allocator (PyMalloc)
Private Heap Space
All Python objects are stored in a private heap that is fully managed by Python’s memory manager.
Reference Counting
Each object keeps a reference count, and memory is freed immediately when the count reaches zero.
Limitation: Cyclic References
Reference counting cannot free objects that reference each other, which may lead to memory leaks.
Garbage Collection (Cyclic GC)
Python uses a generational garbage collector to identify and clean cyclic references.
Memory Allocator (PyMalloc)
PyMalloc improves performance by efficiently managing memory for small Python objects.
Fragmentation Handling
Freed memory is reused internally instead of being returned immediately to the operating system.
Real-Life Example
In large backend systems, Python quickly reuses memory for temporary objects like request data and responses, reducing system overhead and improving performance.
2. What is the Global Interpreter Lock (GIL)?
The Global Interpreter Lock (GIL) is a mechanism in Python that allows only one thread to execute Python code at a time, even if the system has multiple CPU cores.
Why the GIL Exists
Python manages memory automatically, and the GIL makes this memory management safe by preventing multiple threads from modifying the same objects at the same time. This design keeps Python simple, stable, and less prone to hard-to-debug crashes.
How the GIL Works
When a Python program uses multiple threads, the GIL ensures that only one thread runs Python bytecode at any moment. Other threads must wait until the lock is released, even if they are ready to run on another CPU core.
Impact on Performance
Because of the GIL, CPU-heavy tasks do not gain much speed from multithreading. However, programs that perform I/O operations like file reading, network requests, or database calls still benefit from threads because the GIL is released during waiting periods.
How Developers Work Around the GIL
For CPU-intensive workloads, developers often use multiprocessing instead of multithreading, which runs code in separate processes and bypasses the GIL. For high-concurrency applications, async programming is also commonly used.
Real-Life Example
In a web server handling thousands of requests, threads work well for network calls and database queries. But for tasks like image processing or data analysis, running multiple processes gives better performance because each process has its own Python interpreter without sharing the GIL.
3. Difference between list, tuple, set, and dict internally?
| Type |
Internal Structure |
Ordered |
Mutable |
Lookup Speed |
| List |
Dynamic array |
Yes |
Yes |
O(1) by index |
| Tuple |
Immutable array |
Yes |
No |
O(1) by index |
| Set |
Hash table |
No |
Yes |
O(1) average |
| Dictionary |
Hash table (key-value) |
Yes (Python 3.7+) |
Yes |
O(1) average |
4. What happens when you pass a variable to a function in Python?
When you pass a variable to a function in Python, you are not passing the value and not passing a copy.
You are passing a reference to the object that the variable points to.
def update_data(num, nums):
num += 1 # creates a new integer object
nums.append(4) # modifies the same list object
print("Inside function:")
print("num =", num)
print("nums =", nums)
number = 10
numbers = [1, 2, 3]
update_data(number, numbers)
print("\nOutside function:")
print("number =", number)
print("numbers =", numbers)
5. Explain Python’s garbage collector.
Python’s garbage collector automatically frees memory by removing objects that are no longer needed.
It mainly uses reference counting, where an object is deleted when no variable points to it.
To handle objects that reference each other, Python uses a cyclic garbage collector that finds and removes those unused cycles.
The garbage collector works in generations, checking newer objects more often than older ones to keep programs fast.
Real-Life Example
In a web application, many temporary objects are created while handling a request. Once the request is finished, Python’s garbage collector removes those unused objects, freeing memory so the server can handle the next request smoothly.
6. What are Python descriptors?
Python descriptors are objects that control how attributes of a class are accessed, updated, or deleted.
They work by defining special methods like __get__, __set__, or __delete__.
Whenever you access an attribute, Python automatically calls these methods instead of directly reading or writing the value.
class Age:
def __get__(self, instance, owner):
return instance._age
def __set__(self, instance, value):
if value < 0:
raise ValueError("Age cannot be negative")
instance._age = value
class Person:
age = Age() # descriptor
p = Person()
p.age = 25 # calls __set__
print(p.age) # calls __get__
7. Difference between __new__ and __init__
__new__ and __init__ are both used when creating objects in Python, but they do different jobs.
__new__ is responsible for creating the object.
It is called first and returns a new instance of the class. This method is mainly used when you need control over object creation, such as in immutable objects.
__init__ is responsible for initializing the object.
It runs after __new__ and sets or modifies the object’s attributes.
class Example:
def __new__(cls):
print("__new__ called")
return super().__new__(cls)
def __init__(self):
print("__init__ called")
obj = Example()
8. What is monkey patching?
Monkey patching means changing or adding behavior to existing code at runtime, without modifying the original source code.
In Python, this usually happens when you replace a function or method of a class or module while the program is already running.
class Printer:
def show(self):
print("Original printer output")
p = Printer()
p.show()
# monkey patching the method
def new_show():
print("Modified printer output")
p.show = new_show
p.show()
A decorator in Python is simply a function that takes another function as input and returns a new function with extra behavior added to it.
Instead of changing the original function’s code, the decorator wraps around it.
Using @ is just a shortcut. Even without @, the logic stays the same.
Real-Life Example
Decorators are often used for logging, timing functions, checking user permissions, or validating inputs, without touching the original function code.
10. Difference between shallow copy and deep copy.
A shallow copy creates a new object but does not copy nested objects.
A deep copy creates a new object and also copies all nested objects inside it.
| Shallow Copy |
Deep Copy |
| Copies only the outer object |
Copies both outer and inner objects |
| Nested objects are shared |
Nested objects are independent |
| Faster and uses less memory |
Slower and uses more memory |
Real-Life Example
Shallow copy is like copying a folder shortcut — both shortcuts point to the same files.
Deep copy is like copying the entire folder — changes in one don’t affect the other.
Welcome to the DSA in C++ Interview Questions section. This section covers commonly asked Data Structures and Algorithms questions in C++, frequently seen in top tech company interviews, explained in a simple and easy-to-understand way to help you clearly understand the logic and approach behind each problem.
1. Create a new array where each index stores the product of all elements except the element at that index. You must solve the problem without using division and in O(n) time complexity.
Instead of multiplying all elements for every index (which is slow), we split the work into two parts.
- First, we calculate the product of all elements on the left side of each index.
- Then, we calculate the product of all elements on the right side of each index.
Finally, we multiply the left and right products to get the result for each position.
#include <iostream>
#include <vector>
using namespace std;
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> result(n, 1);
int left = 1;
for (int i = 0; i < n; i++) {
result[i] = left;
left *= nums[i];
}
int right = 1;
for (int i = n - 1; i >= 0; i--) {
result[i] *= right;
right *= nums[i];
}
return result;
}
int main() {
vector<int> nums = {1, 2, 3, 4};
vector<int> output = productExceptSelf(nums);
cout << "Input: ";
for (int x : nums) cout << x << " ";
cout << "\nOutput: ";
for (int x : output) cout << x << " ";
return 0;
}
2. You are given an array of positive numbers and a target value. Your task is to find the smallest length of a continuous subarray whose sum is greater than or equal to the target.
Instead of checking every possible subarray, we use a sliding window.
We keep adding numbers to the window until the sum becomes equal to or greater than the target.
Once that happens, we try to shrink the window from the left to find the smallest possible size.
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int sum = 0;
int minLen = INT_MAX;
for (int right = 0; right < nums.size(); right++) {
sum += nums[right];
while (sum >= target) {
minLen = min(minLen, right - left + 1);
sum -= nums[left];
left++;
}
}
return (minLen == INT_MAX) ? 0 : minLen;
}
int main() {
vector<int> nums = {2, 3, 1, 2, 4, 3};
int target = 7;
cout << "Output: " << minSubArrayLen(target, nums);
return 0;
}
3. You are given a string. Your task is to find the length of the longest substring that contains no repeating characters. A substring must be continuous.
Instead of checking all substrings, we use a sliding window.
We move a window across the string and keep track of characters we have already seen.
If a character repeats, we move the left side of the window forward until the substring becomes unique again.
This approach scans the string only once, so it is fast.
#include <iostream>
#include <unordered_set>
using namespace std;
int longestUniqueSubstring(string s) {
unordered_set<char> window;
int left = 0, maxLength = 0;
for (int right = 0; right < s.length(); right++) {
while (window.count(s[right])) {
window.erase(s[left]);
left++;
}
window.insert(s[right]);
maxLength = max(maxLength, right - left + 1);
}
return maxLength;
}
int main() {
string s = "abcabcbb"; // Input string
int result = longestUniqueSubstring(s);
cout << "Input: " << s << endl;
cout << "Output: " << result << endl;
return 0;
}
4. You are given a list of intervals, where each interval has a start and an end value. If any intervals overlap, merge them into a single interval and return the final list. Intervals that do not overlap should remain as they are.
First, we sort the intervals based on their starting values.
Then we go through them one by one and compare the current interval with the last merged interval.
- If they overlap, we merge them.
- If they don’t overlap, we add the interval as it is.
This way, all overlapping intervals are combined efficiently in a single pass.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> mergeIntervals(vector<vector<int>>& intervals) {
vector<vector<int>> result;
if (intervals.empty())
return result;
sort(intervals.begin(), intervals.end());
result.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] <= result.back()[1]) {
result.back()[1] = max(result.back()[1], intervals[i][1]);
} else {
result.push_back(intervals[i]);
}
}
return result;
}
int main() {
vector<vector<int>> intervals = {{1,3}, {2,6}, {8,10}, {15,18}};
vector<vector<int>> output = mergeIntervals(intervals);
cout << "Input Intervals: ";
for (auto &i : intervals)
cout << "[" << i[0] << "," << i[1] << "] ";
cout << "\nMerged Intervals: ";
for (auto &i : output)
cout << "[" << i[0] << "," << i[1] << "] ";
return 0;
}
5. You are given a square matrix (n × n). Rotate the matrix 90 degrees clockwise, in place, meaning you should not use any extra matrix for the result.
To rotate the matrix in place, we follow two clear steps:
- Transpose the matrix
This swaps rows with columns.
- Reverse each row
This converts the transposed matrix into a 90-degree rotated matrix.
These two steps modify the matrix directly, without using any extra space.
#include <iostream>
#include <vector>
using namespace std;
void rotateMatrix(vector<vector<int>>& matrix) {
int n = matrix.size();
// Step 1: Transpose the matrix
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
swap(matrix[i][j], matrix[j][i]);
}
}
// Step 2: Reverse each row
for (int i = 0; i < n; i++) {
reverse(matrix[i].begin(), matrix[i].end());
}
}
int main() {
vector<vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
rotateMatrix(matrix);
cout << "Rotated Matrix:\n";
for (auto &row : matrix) {
for (int val : row)
cout << val << " ";
cout << endl;
}
return 0;
}
6. You are given a main string and a pattern string; find all starting indices in the main string where a substring is an anagram of the pattern. For example, if the string is "cbaebabacd" and the pattern is "abc", the output is [0, 6] because "cba" and "bac" are valid anagrams of "abc".
An anagram means the same characters with the same frequency but in a different order.
We slide a window of the same length as the pattern string across the main string and compare character counts.
Whenever the counts match, we store the starting index.
This approach is fast because we reuse previous calculations instead of checking from scratch every time.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<int> findAnagrams(string s, string p) {
vector<int> result;
if (s.size() < p.size()) return result;
vector<int> countP(26, 0), countS(26, 0);
for (char c : p)
countP[c - 'a']++;
int windowSize = p.size();
for (int i = 0; i < s.size(); i++) {
countS[s[i] - 'a']++;
if (i >= windowSize)
countS[s[i - windowSize] - 'a']--;
if (countS == countP)
result.push_back(i - windowSize + 1);
}
return result;
}
int main() {
string s = "cbaebabacd";
string p = "abc";
vector<int> output = findAnagrams(s, p);
cout << "Input String: " << s << endl;
cout << "Pattern: " << p << endl;
cout << "Output Indices: ";
for (int index : output)
cout << index << " ";
return 0;
}
7. You are given an array of numbers and a target value. Find two different indices such that the numbers at those indices add up to the target, and return the indices.
Instead of checking every pair, we store numbers in a hash map while scanning the array.
For each number, we check if the remaining value (target − current number) already exists.
This gives a fast solution in one pass.
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> seen;
for (int i = 0; i < nums.size(); i++) {
int need = target - nums[i];
if (seen.count(need))
return {seen[need], i};
seen[nums[i]] = i;
}
return {};
}
int main() {
vector<int> nums = {2, 7, 11, 15};
int target = 9;
vector<int> result = twoSum(nums, target);
cout << "Output: [" << result[0] << ", " << result[1] << "]";
return 0;
}
8. You are given an array of integers and a number k. Count how many continuous subarrays have a total sum equal to k.
We use a prefix sum and a hash map.
As we move through the array, we store how many times a prefix sum has appeared.
If currentSum - k exists in the map, it means a subarray with sum k is found.
This avoids checking all subarrays and works in one pass.
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> prefixCount;
prefixCount[0] = 1;
int sum = 0, count = 0;
for (int num : nums) {
sum += num;
if (prefixCount.count(sum - k))
count += prefixCount[sum - k];
prefixCount[sum]++;
}
return count;
}
int main() {
vector<int> nums = {1, 1, 1};
int k = 2;
cout << "Output: " << subarraySum(nums, k);
return 0;
}
9. You are given an unsorted array of integers. Find the length of the longest sequence of consecutive numbers that appear in the array. The solution must run in O(n) time.
We put all numbers into a hash set so lookup is fast.
Then we only start counting when a number has no previous consecutive number (number − 1).
From there, we keep checking the next numbers until the sequence breaks.
Each number is processed once, so the solution stays linear.
#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;
int longestConsecutive(vector<int>& nums) {
unordered_set<int> s(nums.begin(), nums.end());
int longest = 0;
for (int num : s) {
if (!s.count(num - 1)) {
int current = num;
int length = 1;
while (s.count(current + 1)) {
current++;
length++;
}
longest = max(longest, length);
}
}
return longest;
}
int main() {
vector<int> nums = {100, 4, 200, 1, 3, 2};
cout << "Output: " << longestConsecutive(nums);
return 0;
}
Welcome to the Java Interview Questions section. This section covers commonly asked Java interview questions, frequently seen in top tech company interviews, explained in a simple and easy-to-understand way to help you clearly understand core Java concepts and how they are used in real applications.
1. How does Java achieve platform independence?
Java achieves platform independence by compiling source code into bytecode, not machine code.
This bytecode can run on any system that has a Java Virtual Machine (JVM), no matter the operating system.
The JVM reads the bytecode and converts it into machine-specific instructions.
To improve performance, the JIT (Just-In-Time) compiler translates frequently used bytecode into native machine code at runtime, making Java programs run faster.
2. Difference between == and .equals() in Java
In Java, == compares object references, meaning it checks whether both variables point to the same memory location.
The .equals() method compares actual values or content inside the objects.
For String objects, Java uses a String pool. If two strings are created using string literals, they may point to the same memory location, so == can return true.
But if strings are created using new, they are stored separately in memory, and == will return false even if the content is the same, while .equals() will return true.
3. What happens internally when you create a String in Java?What happens internally when you create a String in Java? String s = "meta"; String s2 = new String("meta");
When Java sees String s = “meta”;, it first checks the
String pool. If "meta" already exists, Java
simply points s to that object. If not, Java creates one String
object in the pool and uses it.
Now look at this line:
String s2 = new String("meta");
Here, Java always creates a new String object in heap memory,
even if "meta" already exists in the String pool. So s2
points to a different object.
"meta" → reused from the String pool
new String("meta") → creates a new object in heap
Java uses the String pool to save memory and improve performance, while
new String() is used when you explicitly need a separate object.
4. Why is String immutable in Java?
Java Strings are immutable, which means once a String is created, it cannot be changed. This design choice solves several important problems.
Thread safety:
Because Strings cannot be modified, multiple threads can use the same String object safely without synchronization. There is no risk that one thread will change the value while another is using it.
Security:
Strings are widely used in sensitive areas like file paths, database URLs, and passwords. If Strings were mutable, someone could change these values after they are used, which would be a serious security risk.
Hashcode caching:
Strings are commonly used as keys in hash-based collections like HashMap. Since a String never changes, its hashcode can be calculated once and reused, which makes lookups faster and more reliable.
5. Difference between final, finally, and finalize()
final
final is a keyword used to prevent changes. A final variable cannot be reassigned, a final method cannot be overridden, and a final class cannot be inherited.
finally
finally is a block used with try-catch. It always runs and is mainly used for cleanup like closing files or database connections.
finalize()
finalize() is a method called by the garbage collector before an object is removed, but it is rarely used and not reliable.
| Term |
Used For |
Purpose |
| final |
Keyword |
Prevents modification |
| finally |
Block |
Always executes after try-catch |
| finalize() |
Method |
Runs before garbage collection |
6. What is the static keyword used for in Java?
The static keyword is used to create members that belong to the class, not to individual objects.
Static variables are shared by all objects, static methods can be called without creating an object, and static blocks run once when the class is loaded.
It is mainly used for shared data, utility methods, and one-time initialization.
class Example {
static int count = 0; // static variable
static { // static block
System.out.println("Static block runs once");
}
static void showCount() { // static method
System.out.println("Count = " + count);
}
public static void main(String[] args) {
Example.count = 5;
Example.showCount();
}
}
7. What performance issues have you faced in Java production systems?
In Java production systems, common performance issues include high memory usage, slow response time, and CPU spikes.
These problems often come from memory leaks, inefficient database queries, excessive object creation, or poor thread management.
In real projects, such issues are usually fixed by tuning garbage collection, using proper caching, optimizing SQL queries, and improving thread pool configuration.
8. What is class loading in Java?
Class loading in Java is the process by which the JVM loads .class files into memory when they are needed.
Java uses a hierarchy of class loaders so core classes are loaded securely and user classes cannot override system classes.
Main Class Loaders
- Bootstrap ClassLoader → loads core Java classes like
java.lang and java.util
- Extension ClassLoader → loads Java extension libraries
- Application ClassLoader → loads classes from your project and classpath
class ClassLoaderDemo {
public static void main(String[] args) {
// Application ClassLoader
ClassLoader appClassLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("Application ClassLoader: " + appClassLoader);
// Extension ClassLoader
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("Extension ClassLoader: " + extClassLoader);
// Bootstrap ClassLoader
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("Bootstrap ClassLoader: " + bootstrapClassLoader);
}
}
9. What are deadlock, livelock, and starvation?
-
Deadlock happens when two or more threads are waiting for each other forever, and none of them can move forward.
-
Livelock happens when threads are active and keep responding to each other, but still make no real progress.
-
Starvation happens when a thread never gets CPU time or resources because other threads keep taking them.
class DeadlockDemo {
static final Object lock1 = new Object();
static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1 locked lock1");
synchronized (lock2) {
System.out.println("Thread 1 locked lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2 locked lock2");
synchronized (lock1) {
System.out.println("Thread 2 locked lock1");
}
}
});
t1.start();
t2.start();
}
}
Welcome to the Database (SQL & NoSQL) Interview Questions section. This section covers commonly asked database interview questions for SQL and NoSQL systems, frequently seen in top tech company interviews, explained in a simple and easy-to-understand way to help you clearly understand database concepts, queries, and how they are used in real-world applications.
1. Write an SQL query to find the top 3 posts with the highest number of likes in the last 24 hours.
We count how many likes each post received in the last 24 hours.
Then we sort the posts by total likes in descending order and select the top 3 posts.
This is done using COUNT, GROUP BY, and ORDER BY.
SQL Query
SELECT
post_id,
COUNT(*) AS total_likes
FROM likes
WHERE created_at >= NOW() - INTERVAL 1 DAY
GROUP BY post_id
ORDER BY total_likes DESC
LIMIT 3;
2. Given tables users(user_id) and friends(user_id, friend_id), write an SQL query to suggest mutual friends for a given user.
To suggest mutual friends, we first look at the friends of a given user.
Then we find other users who share those same friends but are not already direct friends.
This is done using a self-join on the friends table and counting mutual connections.
SQL Query
SELECT
f2.user_id AS suggested_friend,
COUNT(*) AS mutual_friends_count
FROM friends f1
JOIN friends f2
ON f1.friend_id = f2.friend_id
WHERE f1.user_id = :given_user_id
AND f2.user_id != :given_user_id
AND f2.user_id NOT IN (
SELECT friend_id
FROM friends
WHERE user_id = :given_user_id
)
GROUP BY f2.user_id
ORDER BY mutual_friends_count DESC;
3. Given a page_views collection, write a MongoDB aggregation to identify pages with increasing daily views for the last 3 days.
To find pages with increasing views over the last 3 days, we first group
page views by page and date to calculate daily view counts.
Next, we sort the data by date and compare each day’s views with the previous day.
Only pages where views increase day by day for the last 3 days are returned.
MongoDB Aggregation Query
db.page_views.aggregate([
{
$match: {
view_date: {
$gte: new Date(new Date().setDate(new Date().getDate() - 3))
}
}
},
{
$group: {
_id: {
page_id: "$page_id",
date: {
$dateToString: { format: "%Y-%m-%d", date: "$view_date" }
}
},
daily_views: { $sum: 1 }
}
},
{
$sort: { "_id.page_id": 1, "_id.date": 1 }
},
{
$group: {
_id: "$_id.page_id",
views: { $push: "$daily_views" }
}
},
{
$match: {
"views.0": { $lt: "$views.1" },
"views.1": { $lt: "$views.2" }
}
}
]);
4. Given a notifications collection, write a MongoDB aggregation to find users who received more than 50 notifications in a single day.
To find users who received more than 50 notifications in a single day,
we can use a MongoDB aggregation pipeline. The idea is to group notifications
by user and date, count them, and then filter the results.
MongoDB Aggregation Query
db.notifications.aggregate([
{
$group: {
_id: {
userId: "$userId",
date: {
$dateToString: { format: "%Y-%m-%d", date: "$createdAt" }
}
},
notificationCount: { $sum: 1 }
}
},
{
$match: {
notificationCount: { $gt: 50 }
}
}
]);
Explanation
- Notifications are grouped by userId and day.
- $dateToString converts timestamps into daily groups.
- $sum counts notifications per user per day.
- $match filters users with more than 50 notifications.
5. Write an SQL query to find duplicate message records sent by the same user within the same minute.
To find duplicate message records, we look for messages sent by the
same user within the same one-minute time window.
Messages are grouped by user, message content, and the minute they were sent.
If the count is greater than one, those messages are treated as duplicates.
SQL Query
SELECT
sender_id,
message_text,
DATE_FORMAT(sent_time, '%Y-%m-%d %H:%i') AS sent_minute,
COUNT(*) AS duplicate_count
FROM messages
GROUP BY sender_id, message_text, sent_minute
HAVING COUNT(*) > 1;
How this works
- Groups messages by sender, message content, and minute.
- Converts timestamps to minute-level precision.
- Counts how many times the same message appears.
- Returns only records that appear more than once.