Part-1: Java interview question
Created on: Dec 26, 2025
-
Contract between hashcode and equals in java
Principles:
- reflexive: an object must equal itself
- symmetric: x.equals(y) must return the same result as y.equals(x)
- transitive: if x.equals(y) and y.equals(z), then also x.equals(z)
- consistent: the value of .equals() should change only if a property that is contained in .equals() changes.
Contracts:
- Equality: If two objects are equal according to the equals() method, then their hash codes must be the same. However, the reverse is not true, as two objects with the same hash code may not be equal.
- If two objects are not equal according to the equals(Object object) method, then hashCode() method can produce different or same integer result depending upon your implementation.
- Consistency: The hashCode() method should return the same integer value every time it's called on the same object, unless an object property used in the equals() method is modified.
import java.util.Objects; public class Employee { private String name; private int age; public Employee(String name, int age) { this.name = name; this.age = age; } public int hashCode() { return Objects.hash(this.name, this.age); } public boolean equals(Object object){ if(this==object) return true; if(object==null || getClass() != object.getClass()) return false; Employee that = (Employee) object; return Objects.equals(this.name, this.name) && this.age == that.age ; } } -
What concern arises when we don't override equals and hashcode in java.<br>
If we don't override equals and hashcode, it will be a concern when working with a collection. Overriding equals and hashCode is crucial when working with collections. Collections like HashSet, HashMap, or Hashtable rely on the hashCode method to organize and search for objects efficiently.
If we don’t override equals and hashCode correctly, these collections may not function as expected.
For example, in below code both employee are same, so in hashset two elements will be inserted. Correct way is to override equal and hashcode.
class Employee{ String name; String dept; public Employee(String name, String dept) { this.name = name; this.dept = dept; } } public class Test{ public static void main(String[] args) { Employee emp1 = new Employee("John", "Finance"); Employee emp2 = new Employee("John", "Finance"); HashSet<Employee> hashSet = new HashSet<>(); hashSet.add(emp1); hashSet.add(emp2); System.out.println(hashSet.size());// output 2 } }Whenever we override the equals method, we must also override the hashCode method to maintain the contract between these two method
Failure to override the hashCode method can lead to inconsistent behavior when objects are used in hash-based collections.
-
What is Coupling and Cohesion
Cohesion: Cohesion refers to the degree to which the elements within a module (e.g., a class or a function) work together to achieve a common goal. In other words, it measures how closely the components of a module are related to each other. High cohesion is desirable because it leads to more readable, maintainable, and modular code.
Consider example of order service from a e-commerce system,
public class OrderService { public void placeOrder(String itemId, int quantity) { if (checkStock(itemId, quantity)) { System.out.println("Item is available."); } else { System.out.println("Item is out of stock."); return; } processPayment(itemId, quantity); sendConfirmationEmail(); } private boolean checkStock(String itemId, int quantity) { return true; } private void processPayment(String itemId, int quantity) { System.out.println("Payment processed for " + itemId); } private void sendConfirmationEmail() { System.out.println("Confirmation email sent."); } }Above code is low cohesive and has some concerns
- Violets single responsibility principle since OrderService is doing too many unrelated tasks stock validation, payment processing, and sending confirmation emails.
- It is harder to test because each function is bundled together in one class.
public class OrderService { private StockService stockService; private PaymentService paymentService; private EmailService emailService; public OrderService(StockService stockService, PaymentService paymentService, EmailService emailService) { this.stockService = stockService; this.paymentService = paymentService; this.emailService = emailService; } public void placeOrder(String itemId, int quantity) { if (stockService.checkStock(itemId, quantity)) { System.out.println("Item is available."); } else { System.out.println("Item is out of stock."); return; } paymentService.processPayment(itemId, quantity); emailService.sendConfirmationEmail(); } } public class StockService { public boolean checkStock(String itemId, int quantity) { return true; } } public class PaymentService { public void processPayment(String itemId, int quantity) { System.out.println("Payment processed for " + itemId); } } public class EmailService { public void sendConfirmationEmail() { System.out.println("Confirmation email sent."); } }Above code has high cohesion which is better than above.
- Modularity: Each service has a clear and focused responsibility. This makes it easier to understand and modify one piece of the code without affecting others.
- Reusability: Each module can be used in multiple places.
- Maintainability: We can modify individual module without touching other module.
- Testability: Each code can be tested in isolation.
Coupling: Coupling refers to the degree of interdependence between modules. Low coupling is desirable as it promotes reusability and maintainability. If modules are loosely coupled, changes in one module are less likely to affect others.
In OrderService, first implementation is highly coupled and second implementation is low coupled.
-
Is Java pass-by-reference or pass-by-value ? Java is a pass by value both in primitive data types as well as objects.
class Dog{ String name; public Dog(String name) { this.name = name; } public String getName() { return name; } } public class Test{ public static void main(String[] args) { Dog aDog = new Dog("Max"); Dog oldDog = aDog; foo(aDog); aDog.getName().equals("Max"); // true aDog.getName().equals("Fifi"); // false System.out.println( aDog == oldDog); // true } public static void foo(Dog d) { d.getName().equals("Max"); d = new Dog("Fifi"); d.getName().equals("Fifi"); // true } }In this example, aDog.getName() will still return "Max". The value aDog within main is not changed in the function foo by creating new Dog with name member variable set to "Fifi" because the object reference is passed by value. If the object reference was passed by reference, then the aDog.getName() in main would return "Fifi" after the call to foo. Check here for more details.
-
What is immutable class. In Java, an immutable class is a class whose instances cannot be modified once they are created.
final class Employee{ private final String name; private int age; private final List<String> phoneNo; private Address address; public Employee(String name, int age, List<String> phoneNo, Address address) { this.name = name; this.age = age; this.phoneNo = new ArrayList<>(phoneNo); this.address = address; } public String getName() { return name; } public int getAge() { return age; } public List<String> getPhoneNo() { return phoneNo; } public Address getAddress() { return address; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + ", phoneNo=" + phoneNo + ", address=" + address + '}'; } } final class Address implements Cloneable{ private final String street; private final String city; private final String country; public Address(String street, String city, String country) { this.street = street; this.city = city; this.country = country; } public String getStreet() { return street; } public String getCity() { return city; } public String getCountry() { return country; } @Override public String toString() { return "Address{" + "street='" + street + '\'' + ", city='" + city + '\'' + ", country='" + country + '\'' + '}'; } } public class Test{ public static void main(String[] args) { Address address = new Address("Some street", "Some city", "Country"); List<String> phoneNo = new ArrayList<>(); phoneNo.add("1111111111"); Employee employee = new Employee("John Doe", 34, phoneNo, address); phoneNo.add("22222222"); Address address1 = employee.getAddress(); address1 = new Address("stree2", "city2", "country2"); System.out.println(employee); } }Guideline for creating Immutable class.
- Declare the class final.
- Make all fields private and final, so they cannot be modified after initialization.
- Do not provide any setters for the fields.
- Initialize all fields via constructor only, and make sure to deep-copy any mutable objects.
- If the class has any mutable objects as fields, return a copy of those objects instead of the original in getter methods.
- Make sure the class is thread-safe.
Advantages of immutable classes
- Thread-safety
- Security: Since it can't be modified, It is preventing unexpected or unauthorized changes to data
- Better performance: Immutable objects can be cached, shared, and reused, leading to better performance by reducing the need for object creation.
-
What is
Cloneableinterface. The Java.lang.Cloneable interface is a marker interface. Cloneable interface is implemented by a class to make Object.clone() method valid thereby making field-for-field copy. This interface allows the implementing class to have its objects to be cloned instead of using a new operator. g4g ref -
String pool vs string object
A Java String Pool is a place in heap memory where all the strings defined in the program are stored.
In Java, a string literal is a sequence of characters enclosed in double quotes ("").If a literal is already present in poll, new literal will refer to same.
String object create a new string reference even which is not related to string pool. It is stored in string pool.
String str = new String(“GeeksForGeeks”);public class Hello { public static void main(String[] args) { String s1 = "Hello"; String s2 = "Hello"; System.out.println(s1.equals(s2)); String s3 = new String("Hello"); System.out.println(s1.equals(s3)); String s4 = new String("Hello"); System.out.println(s4.equals(s3)); // false System.out.println(s4 == s3); // false System.out.println(s3==s1); // false System.out.println(s1==s2); // true } }- equals methid check for equality of character sequence
==check for reference of varaible object
-
Static initializer This code inside the static block(initializer) is executed only once: the first time the class is loaded into memory.
class Test { // Static block static { System.out.print( "Static block can be printed without main method"); } } -
Different types of classloader in java.
- Done in internal working
-
How to do JVM configuration
-Xms<heap size>[unit] -Xmx<heap size>[unit] -XX:MetaspaceSizeWe can mark units as ‘g’ for GB, ‘m’ for MB, and ‘k’ for KB.
<!-- minimum 2 GB and maximum 5 GB to JVM --> -Xms2G -Xmx5G <!-- Sets the initial heap size to 512 MB --> -Xms512m <!-- set metaspace size --> -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512mGarbage collection
- Serial Garbage Collector
- Parallel Garbage Collector
- CMS Garbage Collector
- G1 Garbage Collector
-XX:+UseSerialGC -XX:+UseParallelGC -XX:+USeParNewGC -XX:+UseG1GC -
What are profilers ? explain with a example. A Java Profiler is a tool that monitors Java bytecode constructs and operations at the JVM level.
- JProfiler
- Java visual vm
-
What are the different garbage collection.
- Serial Garbage Collector: this GC implementation freezes all application threads when it runs.
java -XX:+UseSerialGC -jar Application.java-
Parallel Garbage Collector: It uses multiple threads for managing heap space, but it also freezes other application threads while performing GC. It is default from Java 5 until Java 8.
java -XX:+UseParallelGC -jar Application.java -
G1 Garbage Collector
- Heap size is divided into multiple regions with each region being continuous block of memory. Eden - new object ,Survivor - survived from old collection and old generation - long lived object.
initialize G1_GC { divide heap into multiple regions of equal size classify regions as young, old, or free } trigger garbage collection when needed { if memory usage exceeds threshold { calculate time spent in previous GC cycles if pause time exceeds desired max { adjust GC strategy } // Identify regions with the most garbage garbage_regions = identify_high_garbage_regions() // Perform garbage collection for each region in garbage_regions { if region is young { // Minor GC: Collect garbage from young generation perform_minor_gc(region) } else if region is old { // Major GC: Collect garbage from old generation perform_major_gc(region) } } // Evacuate live objects to free up memory for each region in garbage_regions { live_objects = evacuate_live_objects(region) // Move live objects to other regions move_to_free_regions(live_objects) } // Compact the heap to reduce fragmentation compact_heap() } } // Function to identify regions with high garbage function identify_high_garbage_regions() { // Score regions based on the amount of garbage collected return sorted_regions_by_garbage_score } // Function to perform minor GC function perform_minor_gc(region) { // Identify and remove unreachable objects in the young generation } // Function to perform major GC function perform_major_gc(region) { // Identify and remove unreachable objects in the old generation } // Function to evacuate live objects function evacuate_live_objects(region) { // Gather live objects for relocation return live_objects } // Function to move live objects to free regions function move_to_free_regions(live_objects) { // Place live objects into available free regions } // Function to compact the heap function compact_heap() { // Reduce fragmentation by reorganizing memory layout }Key Features:
- Region-Based Heap: The heap is divided into regions of equal size, allowing for flexible memory allocation and reclamation.
- Concurrent Marking: G1 performs most of its work concurrently with the application threads, minimizing pause times.
- Evacuation: G1 copies live objects to free regions during garbage collection, reducing fragmentation.
- Pause Time Goal: G1 aims to meet a user-specified pause time goal by selecting appropriate regions for collection.
Benefits of G1:
-
Low Pause Times: G1's concurrent marking and region-based approach help minimize pause times.
-
Efficient Memory Utilization: G1's ability to compact memory and reclaim space efficiently helps to reduce fragmentation.
-
Scalability: G1 is well-suited for large heaps and multi-core processors.
-
-XX:+UseG1GC
-
CMS Garbage Collector The Concurrent Mark-Sweep (CMS) garbage collector is a Java algorithm that manages memory and reclaims unused objects while minimizing application pauses:
CMS runs some phases of garbage collection concurrently with the application threads. This keeps garbage collection-induced pauses short
-XX:+UseConcMarkSweepGC
-
Can we create our own marker interface. Custom marker interface using instanceOf of Reflection api.
interface Auditable { } class User implements Auditable { private String name; private String email; public User(String name, String email) { this.name = name; this.email = email; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } public class Test { public static void main(String[] args) { User user = new User("John Doe", "john.doe@example.com"); // Check if the object is Auditable if (user instanceof Auditable) { System.out.println("User is auditable."); } else { System.out.println("User is not auditable."); } // Check for Auditable marker interface using Reflection api if (Auditable.class.isAssignableFrom(user.getClass())) { System.out.println("User is auditable."); } else { System.out.println("User is not auditable."); } } } -
Difference between CMS and G1 garbage collector
CMS (Concurrent Mark-Sweep)
- Concurrent Marking: Most of the work is done concurrently with the application threads, minimizing pauses.
- Mark-Sweep Algorithm: Identifies and reclaims garbage objects without compacting the heap.
- Fragmentation: Over time, fragmentation can occur, leading to performance degradation.
- Full GC: If fragmentation becomes severe, a full GC is triggered, which can be a lengthy process.
G1 (Garbage-First)
-
Region-Based Memory: Divides the heap into regions, allowing for more flexible memory management.
-
Concurrent Marking: Similar to CMS, most work is done concurrently.
-
Evacuation: Live objects are copied to free regions, reducing fragmentation.
-
Pause Time Goal: G1 aims to meet a specific pause time goal by prioritizing the collection of regions with the most garbage.
-
Compactation: G1 performs compaction during the garbage collection process, preventing fragmentation.
Feature CMS G1 Memory Layout Continuous Region-based Compaction No Yes Pause Time Goal Not directly Yes Fragmentation Prone to fragmentation Less prone to fragmentation
- CMS: Suitable for applications with large heaps and low GC pause time requirements. However, it can suffer from fragmentation issues.
- G1: Well-suited for large heaps and applications with specific pause time goals. It offers better performance and reduced fragmentation compared to CMS.
-
I have used java 11 for movie booking system. Which contains mostly rest API. which gc is best use case for this.
- G1 Garbage Collector
- Z Garbage Collector: ZGC is a low-latency garbage collector designed for applications that require extremely short pause times
-
AWS related to deployment We can deploy backend in Auto Scaling Groups.
java -Xms512m -Xmx2048m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:MaxRAM=4000g -jar your-application.jar -
which gc is best for consumer application which consumes from kafka and run multi thread
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 # Set target maximum pause time -Xms512m -Xmx4g -
What will be output of below program and why.
class Parent { public void show() { System.out.println("Parent's show()"); } public void display() { System.out.println("Parent's display()"); } public static void staticMethod() { System.out.println("Parent's staticMethod()"); } } class Child extends Parent { @Override public void show() { System.out.println("Child's show()"); } public void display() { System.out.println("Child's display()"); } public static void staticMethod() { System.out.println("Child's staticMethod()"); } } class Test { public static void main(String[] args) { Parent p = new Child(); p.show(); p.display(); p.staticMethod(); } }Child's show() Child's display() // because display method is override in child class Parent's staticMethod() // static method are resolved at compile time -
What is exception hirarchy
Throwable / \ Error Exception / \ / \ Checked UncheckedError: represents serious problems that applications should not try to catch.
- VirtualMachineError
- StackOverflowError
- OutOfMemoryError
Exception:
Exception represents problems that a program might want to catch. There are two types:
- Checked exceptions: IOException, ClassNotFoundException
- Unchecked exceptions: NullPointerException, ArithmeticException
-
What is string pool ? Where string pool is stored ? Why it is not clean when garbage collector runs ?
String Pool is a special memory area in java where String literals are stored. It is located in non heap memory ( Metaspace ) from java 8.
-
What is inner class ?
