0% found this document useful (0 votes)
33 views74 pages

Java Fundamentals for Developers

Uploaded by

devansh.shr
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views74 pages

Java Fundamentals for Developers

Uploaded by

devansh.shr
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

The Complete Java Developer's

Reference: From Fundamentals to Big


Tech Interviews

Part I: The Java Language Core

This initial part is foundational. It ensures a rock-solid understanding of the Java language's
syntax, structure, and the paradigm it is built upon: Object-Oriented Programming. We will
meticulously cover the building blocks before constructing more complex applications.

Section 1: Java Fundamentals

This section covers the absolute essentials needed to write, compile, and run a Java program.
We will move quickly but thoroughly through the syntax and basic constructs, always adhering
to the "theory -> code -> practice" model.

1.1 Introduction to Java and the JVM

Theoretical Explanation

Java is a high-level, object-oriented programming language known for its philosophy of "Write
Once, Run Anywhere" (WORA).1 This is made possible by the Java Virtual Machine (JVM), an
abstract computing machine that enables a computer to run a Java program. When you compile
Java code, it is not converted into platform-specific machine code but into an intermediate
format called bytecode, which is stored in .class files. The JVM, which is platform-dependent,
then interprets this platform-independent bytecode, translating it into native machine code for
the specific operating system it is running on.2
The Java ecosystem consists of three main components 2:
● JDK (Java Development Kit): The full-featured software development kit for Java,
including the JRE and development tools like the compiler (javac).
● JRE (Java Runtime Environment): The software package that provides the Java class
libraries, the JVM, and other components necessary to run Java applications.
● JVM (Java Virtual Machine): The core component of the JRE that executes Java
bytecode.

Code Example: "Hello, World!"

The traditional first program for any language is "Hello, World!". In Java, it demonstrates the
basic structure of a class and the main entry point for execution.1

Java

public class HelloWorld {


/**
* The main method is the entry point for any Java application.
* JVM starts execution from this method.
*/
public static void main(String args) {
// Prints the string "Hello, World!" to the standard output console.
[Link]("Hello, World!");
}
}

Breakdown of the Code:


● public class HelloWorld: Defines a class named HelloWorld. In Java, all code resides within
classes. The public keyword is an access modifier that makes the class accessible to any
other class.
● public static void main(String args): This is the main method.
○ public: It is accessible globally so the JVM can invoke it.
○ static: The method can be invoked without creating an instance of the HelloWorld
class.
○ void: The method does not return any value.
○ main: This is the name of the method, which the JVM specifically looks for as the
starting point of the program.
○ String args: This parameter is an array of strings that can receive command-line
arguments.
● [Link]("Hello, World!");: This line prints the text to the console.
○ System: A final class in the [Link] package.
○ out: A static member of the System class, which is an instance of PrintStream.
○ println(): A method of the PrintStream class that prints the argument passed to it,
followed by a new line.

Practice Problems
1. Introduce Yourself: Modify the HelloWorld program to print your name and a short
greeting.
2. Multiple Lines: Write a program that prints three separate lines of text, each with a
different fact about Java.
3. ASCII Art: Write a program that uses several [Link]() statements to print a
simple ASCII art shape, like a tree or a smiley face.
4. Command-Line Arguments: Modify the main method to print the first command-line
argument passed to the program. For example, if you run java HelloWorld User, it should
print "Hello, User!". (Hint: The argument will be in args).
5. Compile and Run: Ensure you can compile the Java source file (.java) into a bytecode file
(.class) using the javac command and then run the program using the java command from
your terminal.

1.2 Variables, Data Types, and Operators

Theoretical Explanation

Variables are containers for storing data values. In Java, a variable must be declared with a
specific data type, which determines the size and layout of the variable's memory, the range of
values that can be stored, and the set of operations that can be applied to it. 5 Java's type
system is divided into two main categories: primitive types and reference types.6

This distinction is not merely academic; it has significant practical consequences for
performance and memory usage. Primitive types (int, double, etc.) store their values directly in
memory, typically on the stack for local variables. This makes them extremely fast and memory-
efficient. Reference types (all objects, including arrays and strings) store a reference—
essentially a memory address—that points to the actual object's data, which resides on the
heap.

This leads to a crucial behavior: when you pass a primitive to a method, its value is copied.
When you pass an object, the reference is copied, meaning both the original variable and the
method parameter point to the same object on the heap. This is why the Java Collections
Framework, a powerful set of data structures, can only store objects. To store a primitive like int
in an ArrayList, it must first be "boxed" into its corresponding wrapper class (Integer). This
process, known as autoboxing, adds a slight performance and memory overhead compared to
using a simple primitive array (int). Understanding this trade-off is key to making informed
decisions in performance-critical situations versus general application development where the
convenience of collections is paramount.

Primitive Data Types


Java has eight primitive data types predefined by the language 6:
● byte: 8-bit signed integer. Range: -128 to 127.
● short: 16-bit signed integer. Range: -32,768 to 32,767.
● int: 32-bit signed integer. The default type for integer values.
● long: 64-bit signed integer. Used when a wider range than int is needed. Suffix with L (e.g.,
100L).
● float: 32-bit single-precision floating-point number. Suffix with f (e.g., 3.14f).
● double: 64-bit double-precision floating-point number. The default type for decimal values.
● boolean: Represents one bit of information, with possible values of true or false.
● char: 16-bit Unicode character. Enclosed in single quotes (e.g., 'A').

Reference Data Types


Reference types refer to objects. Examples include String, Array, and any class you create.
Their default value is null.
Variable Declaration and Initialization
Variables are declared with a type and a name. They can be initialized at declaration or later.

Java

int score; // Declaration


score = 100; // Initialization

String playerName = "Alice"; // Declaration and initialization

Since Java 10, you can use the var keyword for local variable type inference, where the
compiler determines the type from the initializer.5

Java

var userCount = 150; // Inferred as int


var greeting = "Hello"; // Inferred as String

Operators
Java provides a rich set of operators to perform operations on variables and values.5
● Arithmetic Operators: + (addition), - (subtraction), * (multiplication), / (division), %
(modulus).
● Assignment Operators: = (assignment), +=, -=, *=, /=, %=.
● Unary Operators: + (unary plus), - (unary minus), ++ (increment), -- (decrement), ! (logical
complement).
● Relational Operators: == (equal to), != (not equal to), > (greater than), < (less than), >=
(greater than or equal to), <= (less than or equal to).
● Logical Operators: && (logical AND), || (logical OR).
● Bitwise Operators: & (bitwise AND), | (bitwise OR), ^ (bitwise XOR), ~ (bitwise
complement), << (left shift), >> (signed right shift), >>> (unsigned right shift).

Code Example: Data Types and Operations

Java

public class DataTypesDemo {


public static void main(String args) {
// Primitive types
int score = 95;
double price = 49.99;
char grade = 'A';
boolean isActive = true;

[Link]("Initial Score: " + score);

// Using operators
int newScore = score + 5; // Arithmetic
newScore++; // Unary
[Link]("New Score: " + newScore);

// Relational and Logical operators


if (newScore == 101 && isActive) {
[Link]("Congratulations! You've reached a new level.");
}

// Type casting
int priceAsInt = (int) price; // Explicit casting from double to int
[Link]("Price as integer: " + priceAsInt);

double scoreAsDouble = score; // Implicit casting from int to double


[Link]("Score as double: " + scoreAsDouble);
}
}
Practice Problems
1. Area Calculator: Write a program that declares two double variables, length and width.
Assign them values and calculate the area of the rectangle. Print the result.
2. Temperature Converter: Create a program that converts a temperature from Celsius to
Fahrenheit. The formula is F=(C×9/5)+32. Use floating-point variables.
3. Simple Interest Calculator: Write a program to calculate simple interest. Use variables for
principal, rate, and time. The formula is SI=(P×R×T)/100.
4. Even or Odd: Write a program that takes an integer and uses the modulus operator (%) to
determine if it is even or odd. Print the result.
5. Swap Two Numbers: Declare two integer variables, a and b. Write code to swap their
values without using a third variable. (Hint: Use arithmetic operations).
6. Circle Calculations: Given a radius (as a double), calculate and print the circle's diameter,
circumference (2×π×r), and area (π×r2). You can use [Link] for the value of π.

1.3 Control Flow Statements

Theoretical Explanation

Control flow statements are fundamental constructs that allow you to dictate the execution path
of your program based on certain conditions or by repeating a block of code.5 They are the
decision-making and looping mechanisms of the language.8
● Decision-Making Statements:
○ if-then-else: The most basic control flow statement. The if block executes if a condition
is true. The optional else if and else blocks provide alternative execution paths.8
○ switch: Allows a variable to be tested for equality against a list of values. Modern Java
(14+) introduced switch expressions, which are more concise, less error-prone (no fall-
through by default), and can return a value.5
● Looping Statements:
○ for: Executes a block of code a specific number of times. It consists of an initialization,
a termination condition, and an increment/decrement expression.8 The enhanced
for-each loop provides a simpler syntax for iterating over arrays or collections.
○ while: Repeats a block of code as long as a given condition is true. The condition is
checked before the loop body is executed.
○ do-while: Similar to while, but the condition is checked after the loop body is executed,
guaranteeing the loop runs at least once.
● Branching Statements:
○ break: Terminates the innermost switch, for, while, or do-while loop.
○ continue: Skips the current iteration of a loop and proceeds to the next one.
○ return: Exits from the current method and can optionally return a value.

Code Example 1: if-else and for loop


Java

public class ControlFlowDemo {


public static void main(String args) {
int scores = {88, 92, 75, 64, 95, 55};
[Link]("Grading student scores:");

// Using an enhanced for-each loop


for (int score : scores) {
char grade;
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else if (score >= 60) {
grade = 'D';
} else {
grade = 'F';
}
[Link]("Score: " + score + ", Grade: " + grade);
}
}
}

Code Example 2: while loop and switch expression

Java

public class VowelCounter {


public static void main(String args) {
String text = "Java Programming Is Fun";
int index = 0;
int vowelCount = 0;

while (index < [Link]()) {


char ch = [Link]([Link](index));
// Using a modern switch expression
int isVowel = switch (ch) {
case 'a', 'e', 'i', 'o', 'u' -> 1;
default -> 0;
};

vowelCount += isVowel;
index++;
}
[Link]("The text contains " + vowelCount + " vowels.");
}
}

Practice Problems
1. FizzBuzz: Write a program that prints the numbers from 1 to 100. For multiples of three,
print "Fizz" instead of the number. For multiples of five, print "Buzz". For numbers which
are multiples of both three and five, print "FizzBuzz".
2. Prime Number Check: Write a program that takes an integer as input and determines
whether it is a prime number. A prime number is a natural number greater than 1 that has
no positive divisors other than 1 and itself.
3. Factorial Calculator: Create a program that calculates the factorial of a non-negative
integer using a for loop. The factorial of n (denoted as n!) is the product of all positive
integers up to n.
4. Number Guessing Game: Write a simple number guessing game. Generate a random
number between 1 and 100. Prompt the user to guess the number. Use a while loop to
continue prompting until the user guesses correctly. Provide hints like "Too high" or "Too
low".
5. Palindrome Checker: Write a program that checks if a given string is a palindrome (reads
the same forwards and backward), ignoring spaces and case. For example, "A man a plan
a canal Panama" is a palindrome.
6. Fibonacci Sequence: Write a program to print the first n numbers in the Fibonacci
sequence. In this sequence, each number is the sum of the two preceding ones, starting
from 0 and 1. (e.g., 0, 1, 1, 2, 3, 5, 8,...).

1.4 Arrays and Strings

Theoretical Explanation

Arrays
An array is a container object that holds a fixed number of values of a single type. The length of
an array is established when it is created and cannot be changed afterward.9 Elements are
stored in contiguous memory locations and are accessed via a numerical index, which starts at
0.10
● Declaration: dataType arrayName; or dataType arrayName;
● Instantiation: arrayName = new dataType[size];
● Initialization: Can be done at declaration using an array literal: int numbers = {10, 20, 30};
or element by element using indices: numbers = 10;.
● Multi-dimensional Arrays: Java supports arrays of arrays, allowing for multi-dimensional
structures like matrices.9

Strings
A String in Java is an object that represents a sequence of characters. A critical characteristic of
Java strings is their immutability—once a String object is created, its value cannot be changed.1
Operations that appear to modify a string, like concatenation, actually create a new
String object.

For situations requiring mutable strings, Java provides two classes 7:


● StringBuilder: A mutable sequence of characters. It is not thread-safe, making it faster for
single-threaded environments.
● StringBuffer: A thread-safe, mutable sequence of characters. It is suitable for multi-
threaded environments where multiple threads might modify the string.

Code Example 1: Array Operations

Java

public class ArrayOperations {


public static void main(String args) {
// Declare and initialize an array of integers
int numbers = {5, 12, 8, 21, 3, 15, 9};

int max = numbers;


int sum = 0;

// Iterate through the array to find the max value and sum
for (int number : numbers) {
if (number > max) {
max = number;
}
sum += number;
}
double average = (double) sum / [Link];

[Link]("Array elements:");
for (int i = 0; i < [Link]; i++) {
[Link](numbers[i] + " ");
}
[Link]("\nMaximum value: " + max);
[Link]("Sum of elements: " + sum);
[Link]("Average of elements: " + average);
}
}

Code Example 2: String, StringBuilder, and StringBuffer

Java

public class StringDemo {


public static void main(String args) {
// String is immutable
String s1 = "Hello";
[Link](" World"); // This creates a new string, but s1 remains unchanged
[Link]("s1 is: " + s1); // Output: s1 is: Hello

String s2 = [Link](" World"); // The new string must be assigned to a new reference
[Link]("s2 is: " + s2); // Output: s2 is: Hello World

// StringBuilder is mutable and efficient for single-threaded use


StringBuilder sb = new StringBuilder("Initial");
[Link](" String");
[Link](0, "My ");
[Link]();
[Link]("StringBuilder result: " + [Link]()); // Output: gnirtS laitini ym

// StringBuffer is mutable and thread-safe


StringBuffer sbf = new StringBuffer("Thread-safe");
[Link](" modification");
[Link]("StringBuffer result: " + [Link]());
}
}
Practice Problems
1. Find Missing Number: You are given an array of n-1 integers in the range from 1 to n.
There are no duplicates in the array. One of the integers is missing. Write a program to find
the missing integer.
2. Rotate Array: Write a program to rotate an array of n elements to the right by k steps. For
example, with n = 7 and k = 3, the array {1, 2, 3, 4, 5, 6, 7} is rotated to {5, 6, 7, 1, 2, 3, 4}.
3. Reverse Words in a String: Given an input string, reverse the string word by word. For
example, the input "the sky is blue" should return "blue is sky the". Use StringBuilder for
efficient string manipulation.
4. Check for Anagrams: Write a program that determines if two strings are anagrams of
each other. Anagrams are words or phrases formed by rearranging the letters of a different
word or phrase, typically using all the original letters exactly once (e.g., "listen" and
"silent").
5. Matrix Transpose: Write a program to find the transpose of a 2D array (matrix). The
transpose of a matrix is a new matrix whose rows are the columns of the original.
6. Remove Duplicates from Sorted Array: Given a sorted array, remove the duplicates in-
place such that each element appears only once and return the new length of the modified
array. You do not need to worry about the elements beyond the new length.

Section 2: Object-Oriented Programming (OOP) in Depth

A deep, practical understanding of Object-Oriented Programming is non-negotiable for an


intermediate Java developer. This programming paradigm organizes code into self-contained,
reusable modules called objects.1 This section dissects each of the four fundamental pillars of
OOP: encapsulation, inheritance, polymorphism, and abstraction. While each pillar is a distinct
concept, their true power emerges from their synergistic interaction. Effective software design
relies on understanding how these principles work together. For instance, inheritance is the
mechanism that enables runtime polymorphism and the implementation of abstract contracts.
Encapsulation, in turn, protects the integrity of an inheritance hierarchy by controlling access to
a superclass's state, preventing corruption by subclasses. Mastering this interplay is a hallmark
of a proficient object-oriented designer.

2.1 Classes and Objects

Theoretical Explanation

The core concepts of OOP are classes and objects.12


● Class: A class is a blueprint or template for creating objects. It defines a set of properties
(fields or attributes) and behaviors (methods) that its objects will have.13 For example, a
Car class could define properties like color and model, and behaviors like drive() and
stop().
● Object: An object is an instance of a class. It is a concrete entity created from the class
blueprint, with its own specific state.13 For example,
myCar could be an object of the Car class with the color "Red" and model "Tesla Model S".

To create an object, you use the new keyword, which allocates memory for the object and
returns a reference to it. The state of an object is typically initialized using a special method
called a constructor. A constructor has the same name as the class and no return type.13

The this keyword is a reference to the current object instance. It is commonly used to resolve
ambiguity between instance variables and parameters with the same name, or to call another
constructor from within a constructor (constructor chaining).7

Code Example: Defining and Using a Dog Class

Java

public class Dog {


// Instance variables (fields)
String name;
String breed;
int age;

// Constructor to initialize a new Dog object


public Dog(String name, String breed, int age) {
[Link] = name; // 'this' refers to the current object's field
[Link] = breed;
[Link] = age;
}

// Method to describe the dog's behavior


public void bark() {
[Link](name + " says: Woof! Woof!");
}

// Method to display dog's details


public void displayInfo() {
[Link]("Name: " + [Link] + ", Breed: " + [Link] + ", Age: " + [Link]);
}
public static void main(String args) {
// Creating two Dog objects (instances of the Dog class)
Dog myDog = new Dog("Buddy", "Golden Retriever", 5);
Dog anotherDog = new Dog("Lucy", "Poodle", 3);

// Calling methods on the objects


[Link]();
[Link]();

[Link]();
[Link]();
}
}

Practice Problems
1. Create a Book Class: Design a Book class with fields for title, author, and publicationYear.
Include a constructor to initialize these fields and a method displayDetails() that prints all
the book's information to the console.
2. Create a Rectangle Class: Develop a Rectangle class with width and height as fields. Add
a constructor and two methods: calculateArea() and calculatePerimeter(), which return the
respective values.
3. Create a BankAccount Class: Model a BankAccount with fields for accountNumber,
accountHolderName, and balance. Implement methods for deposit(double amount) and
withdraw(double amount). Ensure that a withdrawal cannot result in a negative balance.
Add a method checkBalance() to display the current balance.
4. Multiple Constructors: Enhance the Book class from problem 1 by adding a second
constructor that only takes title and author, setting the publicationYear to a default value
(e.g., 2024).
5. Student Roster: Create a Student class with studentId, name, and an array or ArrayList of
grades. Implement a constructor and methods to addGrade(int grade) and
calculateAverageGrade(). In your main method, create a few student objects, add grades
to them, and display their average grades.

2.2 Pillar 1: Encapsulation

Theoretical Explanation

Encapsulation is the practice of bundling an object's data (fields) and the methods that operate
on that data into a single unit, the class.14 A key aspect of encapsulation is

data hiding, which means restricting direct access to an object's internal state from outside the
object's class.16 This is achieved by declaring the fields as

private.17

To allow controlled access to these private fields, you provide public methods known as getters
(to retrieve a value) and setters (to modify a value). This approach has several benefits 16:
● Data Integrity: Setters can include validation logic to ensure that the object's state remains
valid (e.g., preventing a negative age).
● Flexibility and Maintainability: The internal implementation of the class can be changed
without affecting the code that uses it, as long as the public getter/setter methods remain
the same.
● Control: You can create read-only fields (by providing only a getter) or write-only fields (by
providing only a setter).

Code Example: Encapsulated Employee Class

This example demonstrates an Employee class where the data is encapsulated. The salary can
only be set if it's a positive value, and the employeeId is read-only after creation.

Java

public class Employee {


private String employeeId;
private String name;
private double salary;

public Employee(String employeeId, String name) {


[Link] = employeeId;
[Link] = name;
[Link] = 0.0; // Default salary
}

// Getter for employeeId (read-only)


public String getEmployeeId() {
return employeeId;
}

// Getter for name


public String getName() {
return name;
}
// Setter for name
public void setName(String name) {
if (name!= null &&![Link]().isEmpty()) {
[Link] = name;
}
}

// Getter for salary


public double getSalary() {
return salary;
}

// Setter for salary with validation


public void setSalary(double salary) {
if (salary > 0) {
[Link] = salary;
} else {
[Link]("Error: Salary must be a positive value.");
}
}

public void displayDetails() {


[Link]("ID: " + employeeId + ", Name: " + name + ", Salary: $" + salary);
}
}

class Company {
public static void main(String args) {
Employee emp1 = new Employee("E1001", "John Doe");

// Use setters to modify data


[Link](50000.0);
[Link]("Johnathan Doe");

// Attempting an invalid operation


[Link](-1000); // This will print an error message

// Use getters to retrieve data


[Link]("Employee Name: " + [Link]());
[Link]();
}
}

Practice Problems
1. Person Class with Age Validation: Create a Person class with private fields for name
and age. Implement a setter for age that only allows values between 0 and 130.19
2. BankAccount with Secure Balance: Design a BankAccount class with a private balance.
The deposit method should only accept positive amounts. The withdraw method should
only allow withdrawals if the amount is positive and does not exceed the current balance.19
3. Rectangle with Immutable Dimensions: Create a Rectangle class with private length and
width. Make these fields read-only by providing only getter methods. The dimensions
should be set once via the constructor.19
4. Car with Mileage Tracking: Implement a Car class with private fields for model and
mileage. The mileage should be read-only (only a getter). Create a drive(int miles) method
that increases the mileage. The mileage can never decrease.19
5. Student with Grade Management: Create a Student class with private fields studentId,
name, and a list of grades. Provide a public method addGrade(int grade) that adds a grade
to the list, but only if the grade is between 0 and 100. Provide a getter that returns a copy
of the grades list to prevent external modification of the original list.19
6. Book with Price Discount: Design a Book class with private title, author, and price.
Provide getters and setters for all fields. Add a method applyDiscount(double percentage)
that reduces the price by the given percentage, ensuring the discount percentage is
between 0 and 100.19

2.3 Pillar 2: Inheritance

Theoretical Explanation

Inheritance is a fundamental OOP mechanism that allows a new class (the subclass or child
class) to acquire the properties and methods of an existing class (the superclass or parent
class).15 This establishes an "is-a" relationship; for example, a

Dog "is an" Animal. The primary benefits are code reusability and the ability to create a
hierarchical classification of classes.13

In Java, inheritance is implemented using the extends keyword. Java supports single
inheritance (a class can extend only one superclass) and multi-level inheritance (a chain of
inheritance, e.g., C extends B, and B extends A).21 Java does not support

multiple inheritance with classes to prevent the "diamond problem," where ambiguity arises if
a class inherits methods with the same signature from two different superclasses.18

Key keywords and concepts in inheritance include:


● extends: The keyword used to establish an inheritance relationship.
● super: A keyword used to refer to the immediate parent class object. It can be used to call
the superclass's constructor (super()) or to access the superclass's members
([Link]()).21 A call to
super() must be the first statement in a subclass constructor.
● @Override: An annotation used to indicate that a method in a subclass is intended to
override a method in its superclass. While not mandatory, it helps prevent errors by having
the compiler check if the method is correctly overridden.20

Code Example: Vehicle Hierarchy

This example shows a base Vehicle class and a Car subclass that inherits from it, adding its
own specific functionality.

Java

// Superclass
class Vehicle {
protected String brand; // 'protected' allows access by subclasses

public Vehicle(String brand) {


[Link] = brand;
[Link]("Vehicle constructor called.");
}

public void startEngine() {


[Link]("The vehicle's engine starts.");
}

public void stopEngine() {


[Link]("The vehicle's engine stops.");
}
}

// Subclass
class Car extends Vehicle {
private int numberOfDoors;

public Car(String brand, int numberOfDoors) {


super(brand); // Call the superclass constructor
[Link] = numberOfDoors;
[Link]("Car constructor called.");
}
// Overriding the startEngine method
@Override
public void startEngine() {
[Link]("The car's " + brand + " engine starts with a key turn.");
}

// New method specific to Car


public void honk() {
[Link]("The car honks!");
}

public void displayInfo() {


[Link]("Brand: " + brand + ", Doors: " + numberOfDoors);
}
}

public class InheritanceDemo {


public static void main(String args) {
Car myCar = new Car("Toyota", 4);

[Link](); // Method from Car class


[Link](); // Overridden method from Car class
[Link](); // Method from Car class
[Link](); // Inherited method from Vehicle class
}
}

Practice Problems
1. Animal Hierarchy: Create a base class Animal with a method eat(). Create two
subclasses, Herbivore and Carnivore, that extend Animal. Override the eat() method in
each subclass to print what they eat (e.g., "Eats plants" and "Eats meat").
2. Shape and Rectangle: Create a Shape class with properties color and a method getArea()
that returns 0. Create a Rectangle subclass that extends Shape and has length and width
properties. Override the getArea() method to calculate and return the correct area of the
rectangle.25
3. Employee and Manager: Design an Employee class with name and salary. Create a
Manager subclass that adds a bonus property. Override a calculatePay() method in both
classes. The manager's pay should include the bonus.26
4. Person and Student: Create a Person class with name and age. Then, create a Student
class that extends Person and adds a studentId and major. Ensure the constructors are
chained correctly using super().25
5. BankAccount and SavingsAccount: Create a BankAccount class with a withdraw()
method. Create a SavingsAccount subclass that overrides withdraw() to prevent the
balance from dropping below a minimum threshold (e.g., $100).25
6. Add a Method to Bird: Given an Animal class with a walk() method and a Bird class that
extends Animal and has a fly() method, add a sing() method to the Bird class and call all
three methods (walk, fly, sing) from the main method.27

2.4 Pillar 3: Polymorphism

Theoretical Explanation

Polymorphism, meaning "many forms," is an OOP principle that allows a single action to be
performed in different ways.28 In Java, polymorphism is primarily achieved through method
overloading and method overriding.15
1. Compile-Time Polymorphism (Static Binding): This is achieved through method
overloading. It allows a class to have multiple methods with the same name, as long as
their parameter lists are different (either in the number of parameters, the type of
parameters, or both). The compiler determines which method to call at compile time based
on the method signature.29
2. Runtime Polymorphism (Dynamic Binding): This is achieved through method
overriding and is a more powerful form of polymorphism. It occurs when a subclass
provides a specific implementation for a method that is already defined in its superclass.
The decision of which method to execute is made at runtime, based on the actual type of
the object being referenced, not the type of the reference variable.28 This allows for writing
flexible and extensible code. For example, a reference variable of a superclass type can
point to an object of any of its subclasses, and calling an overridden method on that
reference will invoke the subclass's version.

Code Example 1: Method Overloading (Compile-Time Polymorphism)

Java

public class MathOperations {


// Method to add two integers
public int add(int a, int b) {
[Link]("Adding two integers");
return a + b;
}

// Overloaded method to add three integers


public int add(int a, int b, int c) {
[Link]("Adding three integers");
return a + b + c;
}

// Overloaded method to add two doubles


public double add(double a, double b) {
[Link]("Adding two doubles");
return a + b;
}

public static void main(String args) {


MathOperations calculator = new MathOperations();
[Link]("Sum: " + [Link](5, 10));
[Link]("Sum: " + [Link](5, 10, 15));
[Link]("Sum: " + [Link](3.5, 2.7));
}
}

Code Example 2: Method Overriding (Runtime Polymorphism)

Java

class Shape {
public void draw() {
[Link]("Drawing a generic shape");
}
}

class Circle extends Shape {


@Override
public void draw() {
[Link]("Drawing a circle");
}
}

class Square extends Shape {


@Override
public void draw() {
[Link]("Drawing a square");
}
}
public class PolymorphismDemo {
public static void main(String args) {
// A Shape reference can hold a Shape, Circle, or Square object.
Shape myShape;

myShape = new Shape();


[Link](); // Calls Shape's draw() method

myShape = new Circle();


[Link](); // Calls Circle's draw() method at runtime

myShape = new Square();


[Link](); // Calls Square's draw() method at runtime
}
}

Practice Problems
1. Area Calculator with Overloading: Create a Geometry class with overloaded
calculateArea() methods. One method should take the radius of a circle (double), and
another should take the length and width of a rectangle (two doubles).
2. Animal Sounds: Create a base class Animal with a method makeSound(). Create
subclasses Dog, Cat, and Cow, each overriding makeSound() to print "Bark", "Meow", and
"Moo" respectively. In your main method, create an array of Animal objects, populate it with
instances of Dog, Cat, and Cow, and then loop through the array, calling makeSound() on
each object.32
3. Employee Salary Calculation: Create an Employee base class with a calculateSalary()
method. Create Manager and Programmer subclasses. Override calculateSalary() in each
to calculate salary based on different rules (e.g., managers get a bonus, programmers get
overtime pay).32
4. Vehicle Speed Up: Create a Vehicle class with a speedUp() method. Create Car and
Bicycle subclasses. Override speedUp() in each to increase speed by a different amount
(e.g., car by 10, bicycle by 2).32
5. Print Formatter: Design a Printer class with overloaded print() methods that can accept
and correctly format different data types for printing (e.g., an int, a double with two decimal
places, and a String in quotes).
6. Drawing Shapes: Extend the Shape example (Code Example 2) by adding a Triangle
class. Create a method drawShapes(Shape shapes) that takes an array of Shape objects
and calls the draw() method on each one, demonstrating that the method works for any
shape.

2.5 Pillar 4: Abstraction


Theoretical Explanation

Abstraction is the concept of hiding complex implementation details and showing only the
essential features of an object.15 It helps in managing complexity by providing a simplified, high-
level interface. In Java, abstraction is achieved using

abstract classes and interfaces.34


● Abstract Class: An abstract class is a class that cannot be instantiated on its own and
must be subclassed. It is declared with the abstract keyword.
○ It can contain both abstract methods (methods without a body) and concrete methods
(methods with implementation).35
○ If a class has one or more abstract methods, it must be declared abstract.
○ It can have fields, constructors, and static methods.
○ A subclass must implement all abstract methods of its superclass, or it must also be
declared abstract.
○ Use an abstract class when you want to provide a common base class with some
shared implementation for several related subclasses.
● Interface: An interface is a completely abstract type that is used to specify a contract of
behavior that classes must implement. It is declared with the interface keyword.
○ By default, all methods in an interface are public and abstract (before Java 8).
○ All variables declared in an interface are public, static, and final by default.35
○ A class uses the implements keyword to implement an interface. A class can
implement multiple interfaces, which is how Java achieves a form of multiple
inheritance.
○ Use an interface when you want to define a role or capability that different, possibly
unrelated, classes can perform.

Key Differences Between Abstract Classes and Interfaces 35

Feature Abstract Class Interface

Method Types Can have abstract and Can only have abstract
concrete methods. methods (Java 8+ allows
default and static methods
with implementation).

Multiple Inheritance A class can extend only one A class can implement
abstract class. multiple interfaces.
Variables Can have final, non-final, Variables are implicitly
static, and non-static public, static, and final.
variables.

Constructor Can have a constructor Cannot have a constructor.


(called by subclasses).

Access Modifiers Members can be private, Members are public by


protected, public. default.

Code Example 1: Abstract Class

Java

// Abstract superclass
abstract class BankAccount {
private String accountNumber;
protected double balance;

public BankAccount(String accountNumber) {


[Link] = accountNumber;
[Link] = 0.0;
}

// Abstract method - must be implemented by subclasses


public abstract void deposit(double amount);
public abstract void withdraw(double amount);

// Concrete method - shared by all subclasses


public double getBalance() {
return balance;
}

public String getAccountNumber() {


return accountNumber;
}
}
// Concrete subclass
class SavingsAccount extends BankAccount {
private double interestRate;

public SavingsAccount(String accountNumber, double interestRate) {


super(accountNumber);
[Link] = interestRate;
}

@Override
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}

@Override
public void withdraw(double amount) {
if (amount > 0 && (balance - amount) >= 100) { // Minimum balance rule
balance -= amount;
} else {
[Link]("Withdrawal failed for Savings Account.");
}
}
}

Code Example 2: Interface

Java

// Interface defining a contract for playable media


interface Playable {
void play();
void pause();
void stop();
}

// A class implementing the interface


class MP3Player implements Playable {
private String currentSong;

public MP3Player(String currentSong) {


[Link] = currentSong;
}

@Override
public void play() {
[Link]("Playing MP3: " + currentSong);
}

@Override
public void pause() {
[Link]("Pausing MP3: " + currentSong);
}

@Override
public void stop() {
[Link]("Stopping MP3: " + currentSong);
}
}

// Another unrelated class implementing the same interface


class VideoPlayer implements Playable {
private String currentMovie;

public VideoPlayer(String currentMovie) {


[Link] = currentMovie;
}

@Override
public void play() {
[Link]("Playing video: " + currentMovie);
}

@Override
public void pause() {
[Link]("Pausing video: " + currentMovie);
}

@Override
public void stop() {
[Link]("Stopping video: " + currentMovie);
}
}

Practice Problems
1. Abstract Animal Class: Create an abstract class Animal with an abstract method sound()
and a concrete method sleep(). Create Lion and Tiger subclasses that extend Animal and
implement the sound() method.36
2. Shape Interface: Create an interface Shape with abstract methods calculateArea() and
calculatePerimeter(). Create Circle and Triangle classes that implement this interface.36
3. Instrument Interface: Design an interface Instrument with abstract methods play() and
tune(). Create Violin and Piano classes that implement the Instrument interface with their
specific behaviors.36
4. Abstract Employee Class: Create an abstract Employee class with an abstract method
calculateSalary() and a concrete method displayInfo(). Implement Manager and
Programmer subclasses that provide specific salary calculation logic.36
5. Drawable Interface: Create an interface Drawable with a single method draw(). Have
Circle, Rectangle, and Triangle classes implement this interface. Write a program that can
draw any object that is Drawable.
6. Abstract vs. Interface Choice: Consider a system for different types of vehicles (Car,
Motorcycle, Truck). Would you use an abstract class or an interface as the base type?
Justify your choice. Implement a basic version of your chosen design. (Hint: Consider if
there is any common behavior or state all vehicles share).

Part II: Intermediate Java and Essential APIs

This part transitions from language syntax to the powerful frameworks and advanced features
that are used daily in professional Java development, forming the backbone of modern, robust
applications.

Section 3: The Java Collections Framework & Generics

The Java Collections Framework is a unified architecture for representing and manipulating
collections, which are objects that group multiple elements into a single unit.37 It provides a set
of interfaces and classes that significantly reduce programming effort and increase performance
by offering optimized, reusable data structures and algorithms.38

3.1 Introduction to the Collections Framework


Theoretical Explanation

Before the Collections Framework, Java relied on basic constructs like arrays, Vector, and
Hashtable. These lacked a common interface, making the code hard to extend and maintain.
The framework, located in the [Link] package, solves this by providing a hierarchy of
interfaces (Collection, List, Set, Map) and standard implementations (ArrayList, HashSet,
HashMap).39

The core of the framework is the Collection interface, which extends the Iterable interface.
Iterable ensures that every collection can be traversed, typically using an Iterator or a for-each
loop. The Collection interface defines the basic operations for all collections, such as adding,
removing, and querying elements.39

3.2 Generics

Theoretical Explanation

Generics are the mechanism that provides type-safety to the Collections Framework. They allow
you to create classes, interfaces, and methods that operate on types as parameters. A generic
type is declared with angle brackets (<>) containing a type parameter, conventionally a single
uppercase letter like T for Type or E for Element.40

The primary advantages of generics are 41:


1. Compile-Time Type Safety: Generics allow the compiler to enforce the type of elements
stored in a collection. This catches type mismatch errors at compile time rather than
causing a ClassCastException at runtime.
2. Elimination of Casts: When you retrieve an element from a generic collection, the
compiler already knows its type, so you don't need to perform an explicit cast.
3. Code Reusability: You can write a single generic algorithm or data structure that works
with many different types.

Code Example: Generics with ArrayList

Java

import [Link];
import [Link];
public class GenericsDemo {
public static void main(String args) {
// Without generics (raw type) - Not recommended
List rawList = new ArrayList();
[Link]("Hello");
[Link](123); // No compile-time error, but potential for runtime error

// String myString = (String) [Link](1); // This would throw ClassCastException at runtime

// With generics - Type-safe


List<String> stringList = new ArrayList<>();
[Link]("Hello");
[Link]("World");
// [Link](123); // This line causes a compile-time error, ensuring type safety

for (String str : stringList) {


[Link]([Link]()); // No casting needed
}
}
}

Practice Problems
1. Generic Box: Create a generic class Box<T> that can hold a single item of any type. It
should have setItem(T item) and getItem() methods.
2. Generic Pair: Create a generic class Pair<K, V> that holds a key and a value of any two
types.
3. Generic Swap Method: Write a static generic method swap(T array, int i, int j) that swaps
the elements at indices i and j in an array of any type.
4. Bounded Type Parameter: Write a generic method findMax(List<T> list) that finds the
maximum element in a list. Use a bounded type parameter (<T extends Comparable<T>>)
to ensure that the elements can be compared.
5. Wildcard Usage: Write a method printList(List<?> list) that can print any list, regardless of
its element type. Then, write another method sumOfList(List<? extends Number> list) that
calculates the sum of a list of numbers (e.g., List<Integer>, List<Double>).

3.3 The List Interface

Theoretical Explanation

The List interface represents an ordered collection of elements, also known as a sequence. It
allows duplicate elements and provides positional access to elements via an integer index.39
The two most common implementations are

ArrayList and LinkedList.


● ArrayList: Implements a dynamic array that grows as needed. It offers fast random access
to elements using their index (O(1) time complexity) because it's backed by an array.
However, adding or removing elements from the middle of the list is slow (O(n)) because it
requires shifting subsequent elements.43
● LinkedList: Implements a doubly-linked list. It is slow for random access (O(n)) because it
may require traversing the list from the beginning or end. However, it excels at adding or
removing elements from its beginning or end (O(1)).44

Code Example: ArrayList vs. LinkedList

Java

import [Link];
import [Link];
import [Link];

public class ListDemo {


public static void main(String args) {
// ArrayList: Good for random access
List<String> arrayList = new ArrayList<>();
[Link]("Apple");
[Link]("Banana");
[Link](1, "Cherry"); // Slower insertion
[Link]("ArrayList: " + arrayList);
[Link]("Element at index 2: " + [Link](2)); // Fast access

// LinkedList: Good for adding/removing at ends


LinkedList<String> linkedList = new LinkedList<>();
[Link]("Mango");
[Link]("Orange");
[Link]("Grape"); // Fast insertion at the beginning
[Link]("Pineapple"); // Fast insertion at the end
[Link]("LinkedList: " + linkedList);
[Link]();
[Link]("After removing first: " + linkedList);
}
}
Practice Problems
1. Reverse an ArrayList: Write a program to reverse the elements in an ArrayList without
using any library functions like [Link]().
2. Merge Two Sorted Lists: Write a method that takes two sorted List<Integer> objects and
merges them into a single sorted list.
3. Find the Middle Element: Implement a method to find the middle element of a LinkedList
in a single pass. (Hint: Use two pointers, one moving twice as fast as the other).
4. Remove Duplicates: Write a program to remove duplicate elements from an ArrayList.
5. List to Array: Convert an ArrayList of strings to a String array.
6. Performance Comparison: Write a small benchmark program to measure the time it
takes to add 100,000 elements to the beginning of an ArrayList versus a LinkedList. Print
the time taken for each.

3.4 The Set Interface

Theoretical Explanation

The Set interface represents a collection that contains no duplicate elements. It models the
mathematical set abstraction.37 The three main implementations are:
● HashSet: Stores elements in a hash table. It offers the best performance for add, remove,
and contains operations (average O(1) time), but it makes no guarantees about the
iteration order.39
● LinkedHashSet: A subclass of HashSet that also maintains a doubly-linked list running
through all of its entries. This preserves the insertion order of elements.
● TreeSet: Stores elements in a sorted order (natural ordering or by a specified Comparator).
It is implemented using a red-black tree, providing O(log n) time complexity for most
operations.

A critical requirement for objects stored in a HashSet or LinkedHashSet is the proper


implementation of the hashCode() and equals() methods. If two objects are equal according to
equals(), they must have the same hash code.

Code Example: Using HashSet to Find Unique Words

Java

import [Link];
import [Link];
import [Link];

public class SetDemo {


public static void main(String args) {
String sentence = "the quick brown fox jumps over the lazy dog";
String words = [Link](" ");

// HashSet does not maintain order


Set<String> uniqueWords = new HashSet<>();
for (String word : words) {
[Link](word);
}
[Link]("Unique words (unordered): " + uniqueWords);

// TreeSet maintains sorted order


Set<String> sortedUniqueWords = new TreeSet<>(uniqueWords);
[Link]("Unique words (sorted): " + sortedUniqueWords);
}
}

Practice Problems
1. Union of Two Sets: Write a method that takes two Set objects and returns a new Set
containing the union of the two (all elements from both sets).
2. Intersection of Two Sets: Write a method that takes two Set objects and returns a new
Set containing only the elements that are present in both sets (the intersection).
3. Custom Object in HashSet: Create a Student class with id and name. Override the
hashCode() and equals() methods. Create a HashSet<Student> and add several Student
objects, including duplicates, to demonstrate that the set correctly handles uniqueness.
4. Find First Recurring Character: Given a string, find the first character that repeats. Use a
Set to keep track of the characters you've seen.
5. Check for Duplicates in Array: Write a method that takes an integer array and returns
true if it contains any duplicate values, and false otherwise. Use a HashSet for an efficient
solution.

3.5 The Queue Interface

Theoretical Explanation

The Queue interface represents a collection designed for holding elements prior to processing,
following a First-In, First-Out (FIFO) principle.43 This is like a line of people waiting for a service;
the first one in line is the first one served.
Common implementations include:
● LinkedList: As seen before, LinkedList implements the Queue interface, making it a
common choice for a general-purpose FIFO queue.
● ArrayDeque: A resizable-array implementation of a double-ended queue (Deque), which
can be used as a highly efficient FIFO queue. It is generally preferred over LinkedList for
queue operations when iteration is not needed.
● PriorityQueue: An implementation that orders elements based on their natural ordering or
a supplied Comparator. The head of this queue is the "least" element with respect to the
specified ordering. It does not follow a strict FIFO order but rather a priority-based order. 39

Code Example: Simulating a Printer Queue

Java

import [Link];
import [Link];

public class PrinterQueueDemo {


public static void main(String args) {
Queue<String> printQueue = new LinkedList<>();

// Add print jobs to the queue


[Link]("[Link]");
[Link]("[Link]");
[Link]("[Link]");

[Link]("Print jobs in queue: " + printQueue);

// Process jobs in FIFO order


while (![Link]()) {
String job = [Link](); // Retrieves and removes the head of the queue
[Link]("Printing: " + job);
}

[Link]("All jobs printed. Queue is now empty: " + [Link]());


}
}

Practice Problems
1. Ticket Counter Simulation: Simulate a ticket counter where customers arrive and are
served in the order they arrive. Use a Queue to manage the line of customers.
2. Priority Task Scheduler: Use a PriorityQueue to manage a list of tasks. Each task should
have a priority (e.g., 1 for high, 5 for low). The program should always process the task
with the highest priority first.
3. Breadth-First Search (BFS) Traversal: Implement a simple BFS algorithm for a graph or
tree. A Queue is essential for keeping track of the nodes to visit at each level.
4. Implement a Queue using Two Stacks: A classic interview question. Implement a
MyQueue class that has enqueue and dequeue methods, using only two Stack objects for
storage.
5. Sliding Window Maximum: Given an array of integers and a window size k, find the
maximum value in each sliding window of size k. A Deque (like ArrayDeque) is often used
for an efficient O(n) solution.

3.6 The Map Interface

Theoretical Explanation

The Map interface represents a collection of key-value pairs. Each key must be unique, and it
maps to exactly one value. Maps are ideal for fast lookups, insertions, and deletions based on a
key.37

The main implementations are:


● HashMap: Stores key-value pairs in a hash table. It provides average O(1) time complexity
for get and put operations. The iteration order is not guaranteed.39
● LinkedHashMap: Extends HashMap and maintains a doubly-linked list of entries,
preserving the insertion order.
● TreeMap: Stores key-value pairs in a sorted order based on the natural ordering of the
keys or a provided Comparator. It is implemented as a red-black tree, offering O(log n) time
complexity for get and put.

Just like with HashSet, the keys in a HashMap or LinkedHashMap must have a correct
implementation of hashCode() and equals().

Code Example: Word Frequency Counter

Java

import [Link];
import [Link];
public class WordFrequencyCounter {
public static void main(String args) {
String text = "the quick brown fox jumps over the lazy dog and the fox was quick";
String words = [Link](" ");

Map<String, Integer> wordCount = new HashMap<>();

for (String word : words) {


// getOrDefault is a convenient way to handle new keys
[Link](word, [Link](word, 0) + 1);
}

[Link]("Word Frequencies:");
// Iterate over the map's entry set
for ([Link]<String, Integer> entry : [Link]()) {
[Link]([Link]() + ": " + [Link]());
}
}
}

Practice Problems
1. Character Frequency: Write a program to count the frequency of each character in a
given string and store the results in a HashMap.
2. Two Sum Problem: Given an array of integers and a target sum, find the indices of the
two numbers that add up to the target. Use a HashMap for an efficient O(n) solution.
3. Phone Book: Create a simple phone book application using a HashMap to store names
(keys) and phone numbers (values). Implement functionalities to add, remove, and look up
a contact.
4. Sort a Map by Value: Write a program to sort a HashMap by its values. (Hint: This is tricky
because maps are not inherently sortable by value. You might need to transfer the entries
to a List and sort the list).
5. LRU Cache: Implement a Least Recently Used (LRU) cache. This is a classic interview
problem that can be solved efficiently using a LinkedHashMap. The cache should have a
fixed capacity and evict the least recently used item when full.

Table 1: Time Complexity of Common Collection Operations

This table provides an at-a-glance reference for the performance characteristics of the most
common Collection implementations. Understanding these complexities is crucial for selecting
the right data structure for a given problem, a frequent topic in technical interviews.
Data Get/ Search Insertion Insertion Deletion Deletion
Structure Access (End) (Mid) (End) (Mid)

ArrayList O(1) O(n) O(1) O(n) O(1) O(n)


amortized

LinkedLis O(n) O(n) O(1) O(n) O(1) O(n)


t

HashMap O(1) avg O(1) avg O(1) avg N/A O(1) avg N/A

HashSet N/A O(1) avg O(1) avg N/A O(1) avg N/A

TreeMap O(log n) O(log n) O(log n) N/A O(log n) N/A

TreeSet N/A O(log n) O(log n) N/A O(log n) N/A

Section 4: Advanced Java Concepts

This section covers topics essential for building robust, real-world applications and are
frequently tested in interviews for intermediate roles.

4.1 Exception Handling

Theoretical Explanation

Exception handling is a powerful mechanism for managing runtime errors in a structured and
robust way, allowing a program to handle unexpected situations gracefully instead of crashing.1
An exception is an event that disrupts the normal flow of the program's instructions.

In Java, all exceptions are objects that are subclasses of the Throwable class. The hierarchy is:
● Throwable: The root class.
○ Error: Represents serious problems that a reasonable application should not try to
catch, such as OutOfMemoryError.
○ Exception: Represents conditions that a reasonable application might want to catch.
■ Checked Exceptions: Exceptions that are checked at compile-time (e.g.,
IOException, SQLException). A method that might throw a checked exception
must either handle it using a try-catch block or declare it using the throws keyword.
■ Unchecked Exceptions (Runtime Exceptions): Subclasses of RuntimeException
(e.g., NullPointerException, IllegalArgumentException). They are not checked at
compile-time and typically indicate programming errors.

Keywords:
● try: The block of code to be monitored for exceptions.
● catch: The block of code that handles the exception.
● finally: The block of code that is always executed, whether an exception is thrown or not.
It's often used for cleanup, like closing resources.
● throw: Used to manually throw an exception.
● throws: Used in a method signature to declare the exceptions that the method might throw.

The try-with-resources statement (Java 7+) is the recommended way to handle resources that
implement the AutoCloseable interface, as it ensures the resource is closed automatically.7

Code Example: Reading a File with Exception Handling

Java

import [Link];
import [Link];
import [Link];

public class ExceptionHandlingDemo {


public static void main(String args) {
// Using try-with-resources for automatic resource management
try (BufferedReader reader = new BufferedReader(new FileReader("[Link]"))) {
String line;
while ((line = [Link]())!= null) {
[Link](line);
}
} catch (IOException e) {
// Handle the exception gracefully
[Link]("An error occurred while reading the file: " + [Link]());
} finally {
[Link]("Finished file reading attempt.");
}
}
}

Practice Problems
1. Custom Exception: Create a custom checked exception called InvalidAgeException.
Write a method that takes an age as input and throws this exception if the age is negative.
2. Input Validation: Write a program that prompts the user for a number. Use a try-catch
block to handle InputMismatchException if the user enters non-numeric input.
3. Multiple Catch Blocks: Write a method that might throw either an ArithmeticException
(e.g., division by zero) or a NullPointerException. Use separate catch blocks to handle
each exception differently.
4. Chained Exceptions: Create a scenario where a low-level method throws an exception,
which is caught by a mid-level method. The mid-level method should then wrap the original
exception in a new, more specific custom exception and throw it again.7
5. Resource Cleanup: Write a program that opens a file for writing and writes some text to it.
Use a finally block to ensure the file writer is closed, even if an error occurs during writing.
Then, rewrite the program using a try-with-resources statement.

4.2 Multithreading and Concurrency

Theoretical Explanation

Multithreading is the ability of a program to execute multiple threads concurrently, allowing it to


perform multiple operations at the same time. A thread is the smallest unit of execution within a
process. A single process can have multiple threads, all sharing the same memory space,
which makes communication between them efficient but also introduces challenges.7

Creating Threads:
There are two primary ways to create a thread in Java:
1. Extending the Thread class: Create a new class that extends Thread and override its
run() method.
2. Implementing the Runnable interface: Create a new class that implements Runnable
and its run() method. Then, pass an instance of this class to a Thread object's constructor.
This is the preferred approach as it allows the class to extend another class if needed.

Synchronization:
When multiple threads access shared resources, concurrency issues like race conditions
(where the outcome depends on the non-deterministic timing of thread execution) can occur. To
prevent this, Java provides synchronization mechanisms:
● synchronized keyword: Can be applied to methods or blocks of code. It ensures that only
one thread can execute a synchronized block of code for a given object instance at a time,
by acquiring an intrinsic lock (or monitor).
● Locks: The [Link] package provides more advanced and flexible
locking mechanisms, like ReentrantLock.

Thread States:
A thread can be in one of several states: NEW, RUNNABLE, BLOCKED, WAITING,
TIMED_WAITING, and TERMINATED.
[Link] Package:
This package provides high-level concurrency utilities that simplify the development of
concurrent applications. Key components include:
● Executor Framework: Manages a pool of worker threads, decoupling task submission
from task execution (e.g., ExecutorService, ThreadPoolExecutor).
● Concurrent Collections: Thread-safe versions of collections like ConcurrentHashMap.
● Atomic Variables: Classes like AtomicInteger that support lock-free, thread-safe
programming on single variables.

Code Example: Simple Bank Account Synchronization

Java

public class BankAccount {


private double balance;

public BankAccount(double initialBalance) {


[Link] = initialBalance;
}

// This method is synchronized to prevent race conditions


public synchronized void deposit(double amount) {
[Link]([Link]().getName() + " is depositing " + amount);
balance += amount;
[Link]("New balance after deposit by " + [Link]().getName() + " is
" + balance);
}

public double getBalance() {


return balance;
}
}
class DepositTask implements Runnable {
private BankAccount account;

public DepositTask(BankAccount account) {


[Link] = account;
}

@Override
public void run() {
for (int i = 0; i < 5; i++) {
[Link](100);
try {
[Link](100); // Simulate some work
} catch (InterruptedException e) {
[Link]();
}
}
}
}

public class ConcurrencyDemo {


public static void main(String args) {
BankAccount account = new BankAccount(1000);

Thread t1 = new Thread(new DepositTask(account), "Thread-1");


Thread t2 = new Thread(new DepositTask(account), "Thread-2");

[Link]();
[Link]();
}
}

Practice Problems
1. Producer-Consumer Problem: Implement the classic producer-consumer problem using
wait() and notify() on a shared buffer (e.g., a LinkedList). A producer thread adds items to
the buffer, and a consumer thread removes them.
2. Using ExecutorService: Rewrite the Producer-Consumer problem using a thread pool
created with [Link]() and a thread-safe queue like
ArrayBlockingQueue.
3. Deadlock Creation: Write a program that creates a deadlock scenario between two
threads that are trying to acquire locks on two different resources in a different order.
4. Singleton with Double-Checked Locking: Implement a thread-safe Singleton pattern
using the double-checked locking idiom and the volatile keyword.
5. Web Crawler: Design a simple multi-threaded web crawler. Use an ExecutorService to
manage worker threads that download web pages. Use a concurrent collection to keep
track of visited URLs to avoid redundant work.
6. Parallel Summation: Write a program that calculates the sum of a large array of numbers
using multiple threads. Divide the array into segments and assign each segment to a
different thread. Finally, aggregate the results from all threads.

4.3 Functional Java: Lambdas and Streams (Java 8+)

Theoretical Explanation

Java 8 introduced functional programming features, most notably lambda expressions and the
Stream API, which provide a more expressive, concise, and powerful way to work with data,
especially collections.1
● Lambda Expressions: A lambda expression is an anonymous function (a function without
a name) that you can use to create instances of functional interfaces (interfaces with a
single abstract method). They provide a clear and compact way to represent a piece of
functionality.
○ Syntax: (parameters) -> { body } or (parameters) -> expression
● Stream API: A stream is a sequence of elements that supports various aggregate
operations. It is not a data structure that stores elements; instead, it carries values from a
source (like a collection) through a pipeline of operations.
○ Pipeline: A stream pipeline consists of a source, zero or more intermediate
operations, and a terminal operation.
○ Intermediate Operations: These are lazy operations that return a new stream,
allowing operations to be chained. Examples include filter() (selects elements based on
a predicate), map() (transforms each element), and sorted().
○ Terminal Operations: These are eager operations that produce a result or a side-
effect, triggering the execution of the pipeline. Examples include forEach() (performs
an action for each element), collect() (gathers elements into a collection), and reduce()
(combines elements into a single result).

Code Example: Processing a List of People with Streams

Java

import [Link];
import [Link];
import [Link];

class Person {
private String name;
private int age;

public Person(String name, int age) {


[Link] = name;
[Link] = age;
}

public String getName() { return name; }


public int getAge() { return age; }

@Override
public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }
}

public class StreamsDemo {


public static void main(String args) {
List<Person> people = [Link](
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35),
new Person("David", 22),
new Person("Eve", 30)
);

// Find the names of people older than 25, sorted by age, in uppercase.
List<String> result = [Link]() // 1. Get a stream from the list
.filter(p -> [Link]() > 25) // 2. Intermediate op: filter by age
.sorted((p1, p2) -> [Link]([Link](), [Link]())) // 3. Intermediate op: sort by
age
.map(p -> [Link]().toUpperCase()) // 4. Intermediate op: transform name to uppercase
.collect([Link]()); // 5. Terminal op: collect results into a new list

[Link](result);
}
}

Practice Problems
1. Filter and Count: Given a list of strings, use a stream to count how many strings have a
length greater than 5.
2. Map and Sum: Given a list of Product objects (with name and price fields), use a stream to
calculate the total price of all products.
3. Find First: Given a list of integers, use a stream to find the first even number greater than
10. Use Optional to handle the case where no such number exists.
4. Grouping By: Given the Person list from the code example, use [Link]() to
group the people by their age into a Map<Integer, List<Person>>.
5. FlatMap: You have a list of lists of integers, List<List<Integer>>. Use flatMap to flatten it
into a single list of integers, List<Integer>.
6. Reduce to Find Longest String: Given a list of strings, use the reduce operation to find
the longest string in the list.

4.4 Java Database Connectivity (JDBC)

Theoretical Explanation

Java Database Connectivity (JDBC) is a Java API that enables Java applications to connect
and interact with databases.7 It provides a standard, vendor-independent way to execute SQL
statements against a relational database.46

The core steps for using JDBC are as follows 47:


1. Load the JDBC Driver: This step registers the database driver with the DriverManager.
For modern JDBC (4.0+), this step is often automatic as long as the driver's JAR file is on
the classpath.
2. Establish a Connection: Use [Link]() with a database URL,
username, and password to create a Connection object.
3. Create a Statement: From the Connection object, create a Statement or
PreparedStatement object. PreparedStatement is generally preferred as it is more secure
(prevents SQL injection) and often more efficient for repeated queries.
4. Execute the Query:
○ For queries that return data (e.g., SELECT), use executeQuery(), which returns a
ResultSet.
○ For data manipulation statements (e.g., INSERT, UPDATE, DELETE), use
executeUpdate(), which returns the number of rows affected.
5. Process the ResultSet: If the query returns a ResultSet, iterate through it using a
while([Link]()) loop to access the data from each row.
6. Close Resources: It is crucial to close the ResultSet, Statement, and Connection objects
to release database resources. The try-with-resources statement is the best practice for
ensuring resources are closed automatically.48

Code Example: Querying a MySQL Database

This example assumes you have a MySQL database with a table named employees and the
MySQL Connector/J driver JAR in your project's classpath.

Java

import [Link].*;

public class JdbcDemo {


// Database credentials and URL
static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase";
static final String USER = "root";
static final String PASS = "password";

public static void main(String args) {


String sql = "SELECT id, name, position, salary FROM employees WHERE salary >?";

// Using try-with-resources to ensure connection is closed


try (Connection conn = [Link](DB_URL, USER, PASS);
PreparedStatement pstmt = [Link](sql)) {

[Link]("Connection to database established successfully.");

// Set the parameter for the prepared statement


[Link](1, 60000.0);

// Execute the query


ResultSet rs = [Link]();

[Link]("Employees with salary > 60000:");


// Process the result set
while ([Link]()) {
int id = [Link]("id");
String name = [Link]("name");
String position = [Link]("position");
double salary = [Link]("salary");
[Link]("ID: %d, Name: %s, Position: %s, Salary: %.2f%n", id, name, position,
salary);
}
// Resources are closed automatically by try-with-resources
} catch (SQLException e) {
[Link]("Database connection error: " + [Link]());
[Link]();
}
}
}

Practice Problems
1. Insert a Record: Write a JDBC program that inserts a new record into the employees table
using a PreparedStatement.
2. Update a Record: Write a program to update the salary of a specific employee (identified
by their ID) in the employees table.
3. Delete a Record: Create a program that deletes an employee from the table based on
their ID.
4. Transaction Management: Write a program that transfers a certain amount from one bank
account to another. This should be done within a single transaction. Use
[Link](false), [Link](), and [Link]() to ensure the atomicity of
the operation.
5. Database Metadata: Use the DatabaseMetaData interface to retrieve and print information
about the connected database, such as the database product name, version, and a list of
tables.

4.5 JVM Internals Primer

Theoretical Explanation

While deep JVM expertise is a specialized field, a high-level understanding of its architecture is
crucial for any intermediate Java developer to diagnose performance issues, understand
memory management, and write efficient code.2 The JVM can be broken down into three main
subsystems.2
1. Classloader Subsystem: Responsible for loading, linking, and initializing class files. It
dynamically loads classes into the JVM at runtime as they are needed.3
2. Runtime Data Areas: These are the memory areas the JVM uses during program
execution.
○ Method Area: A shared resource among all threads that stores class-level data, such
as the runtime constant pool, field and method data, and the code for methods.
○ Heap: Another shared resource where all objects and arrays are allocated. This is the
area managed by the Garbage Collector.
○ Stack: Each thread has its own private JVM stack, created at the same time as the
thread. It stores frames. Each time a method is invoked, a new frame is created and
pushed onto the stack for that thread. A frame holds local variables, operand stacks,
and data for method dispatch. When the method completes, its frame is popped. This
is why a deep, unterminated recursion results in a StackOverflowError.
○ PC (Program Counter) Registers: Each thread has a PC register that stores the
address of the JVM instruction currently being executed.
○ Native Method Stacks: Used for native (non-Java) code.
3. Execution Engine: This is responsible for executing the bytecode.
○ Interpreter: Reads, interprets, and executes bytecode instructions one by one.
○ JIT (Just-In-Time) Compiler: To improve performance, the JIT compiler analyzes the
bytecode as it runs and compiles frequently executed parts ("hotspots") into native
machine code, which can be executed much faster than interpreted code.3
○ Garbage Collector (GC): An automatic memory management process. The GC
reclaims heap memory used by objects that are no longer referenced by the program,
preventing memory leaks. Different GC algorithms exist (e.g., Serial, Parallel, G1) with
different performance characteristics.49

Practice Problems (Conceptual)


1. Heap vs. Stack: Explain where the following would be stored in memory: int x = 10;, String
name = "Java";, and an object new Person().
2. StackOverflowError: Write a simple recursive method that does not have a base case,
and explain why it will cause a StackOverflowError.
3. OutOfMemoryError: Describe a scenario or write a simple program (e.g., creating a huge
number of objects in a loop) that would likely cause an OutOfMemoryError: Java heap
space.
4. Garbage Collection: Explain what happens to an object when all references to it are set to
null. What is the role of the Garbage Collector in this situation?
5. String Pool: Explain the difference in memory allocation between String s1 = "test"; and
String s2 = new String("test");. Where do these objects reside?

Part III: Mastering Data Structures & Algorithms for


Interviews

This part is the dedicated interview preparation section, focusing on the implementation and
analysis of core Data Structures and Algorithms (DSA). A strong command of DSA is the single
most important factor for success in technical interviews at top-tier companies.50 For each topic,
we will provide a from-scratch implementation to build a deep understanding, followed by a
discussion of its real-world counterpart in the Java Collections Framework.

A key to bridging academic knowledge with professional practice is recognizing that the Java
Collections Framework is a production-grade, highly optimized implementation of these
fundamental data structures. ArrayList is a dynamic array, LinkedList is a doubly-linked list,
HashMap is a hash table, and TreeMap is a self-balancing binary search tree. Understanding
the underlying DSA will demystify how these collections work and empower you to choose the
right tool for the job based on performance characteristics.
Section 5: Core Data Structures

5.1 Arrays

Theoretical Explanation

An array is the simplest data structure, storing a collection of elements of the same type in a
contiguous block of memory. Its key feature is constant-time access, O(1), to any element via its
index. However, its size is fixed upon creation, and insertions or deletions in the middle are
expensive (O(n)) because they require shifting elements.51

Code Example: Custom Dynamic Array (Simplified ArrayList)

This implementation shows how a dynamic array can automatically resize itself when it runs out
of capacity.

Java

import [Link];

public class DynamicArray<T> {


private Object data;
private int size;
private static final int INITIAL_CAPACITY = 10;

public DynamicArray() {
[Link] = new Object;
[Link] = 0;
}

@SuppressWarnings("unchecked")
public T get(int index) {
if (index < 0 |

| index >= size) {


throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return (T) data[index];
}

public void add(T element) {


if (size == [Link]) {
resize();
}
data[size++] = element;
}

private void resize() {


int newCapacity = [Link] * 2;
data = [Link](data, newCapacity);
[Link]("Resized array to capacity: " + newCapacity);
}

public int size() {


return size;
}

public static void main(String args) {


DynamicArray<Integer> numbers = new DynamicArray<>();
for (int i = 1; i <= 15; i++) {
[Link](i);
}
[Link]("Size of dynamic array: " + [Link]());
[Link]("Element at index 5: " + [Link](5));
}
}

Practice Problems 53
1. Find the Duplicate Number: Given an array containing n + 1 integers where each integer
is between 1 and n (inclusive), prove that at least one duplicate number must exist.
Assume that there is only one duplicate number, find the duplicate.
2. Maximum Subarray: Given an integer array, find the contiguous subarray (containing at
least one number) which has the largest sum and return its sum (Kadane's Algorithm).
3. Product of Array Except Self: Given an integer array nums, return an array answer such
that answer[i] is equal to the product of all the elements of nums except nums[i]. You must
solve it in O(n) time and without using the division operation.
4. Merge Intervals: Given an array of intervals where intervals[i] = [start_i, end_i], merge all
overlapping intervals, and return an array of the non-overlapping intervals that cover all the
intervals in the input.
5. Container With Most Water: You are given an integer array height of length n. There are
n vertical lines drawn such that the two endpoints of the i-th line are (i, 0) and (i, height[i]).
Find two lines that together with the x-axis form a container, such that the container
contains the most water.
6. Trapping Rain Water: Given n non-negative integers representing an elevation map
where the width of each bar is 1, compute how much water it can trap after raining.

5.2 Linked Lists

Theoretical Explanation

A linked list is a linear data structure where elements are not stored at contiguous memory
locations. Instead, elements are stored in nodes, where each node contains its data and a
reference (or pointer) to the next node in the sequence.45
● Singly Linked List: Each node points only to the next node.
● Doubly Linked List: Each node points to both the next and the previous node, allowing for
bidirectional traversal. Java's LinkedList class is a doubly-linked list.44

Linked lists offer dynamic size and efficient insertions/deletions at the ends (O(1)), but slow
element access (O(n)).56

Code Example: Custom Singly Linked List

Java

public class SinglyLinkedList<T> {


private Node<T> head;
private int size;

private static class Node<T> {


T data;
Node<T> next;

Node(T data) {
[Link] = data;
[Link] = null;
}
}
public void add(T data) {
Node<T> newNode = new Node<>(data);
if (head == null) {
head = newNode;
} else {
Node<T> current = head;
while ([Link]!= null) {
current = [Link];
}
[Link] = newNode;
}
size++;
}

public void display() {


Node<T> current = head;
while (current!= null) {
[Link]([Link] + " -> ");
current = [Link];
}
[Link]("null");
}

public static void main(String args) {


SinglyLinkedList<String> list = new SinglyLinkedList<>();
[Link]("A");
[Link]("B");
[Link]("C");
[Link]();
}
}

Practice Problems 57
1. Reverse a Linked List: Given the head of a singly linked list, reverse the list, and return
the new head.
2. Detect Cycle in a Linked List: Given head, the head of a linked list, determine if the
linked list has a cycle in it (Floyd's Tortoise and Hare algorithm).
3. Merge Two Sorted Lists: You are given the heads of two sorted linked lists. Merge the
two lists into one sorted list and return the head of the new list.
4. Remove Nth Node From End of List: Given the head of a linked list, remove the nth node
from the end of the list and return its head.
5. Intersection of Two Linked Lists: Given the heads of two singly linked-lists, return the
node at which the two lists intersect. If the two linked lists have no intersection at all, return
null.
6. Palindrome Linked List: Given the head of a singly linked list, return true if it is a
palindrome.

5.3 Stacks

Theoretical Explanation

A stack is a linear data structure that follows the Last-In, First-Out (LIFO) principle. Think of it
like a stack of plates: the last plate you put on top is the first one you take off. 59 The primary
operations are:
● push: Adds an element to the top of the stack.
● pop: Removes and returns the element from the top of the stack.
● peek: Returns the element at the top without removing it.

In Java, the legacy Stack class exists, but it's recommended to use a Deque implementation like
ArrayDeque for stack operations, as it is more efficient and consistent.61

Code Example: Custom Stack using an Array

Java

public class ArrayStack<T> {


private Object array;
private int top;
private static final int DEFAULT_CAPACITY = 10;

public ArrayStack() {
[Link] = new Object;
[Link] = -1;
}

public void push(T item) {


if (top == [Link] - 1) {
throw new StackOverflowError("Stack is full");
}
array[++top] = item;
}
@SuppressWarnings("unchecked")
public T pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return (T) array[top--];
}

@SuppressWarnings("unchecked")
public T peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return (T) array[top];
}

public boolean isEmpty() {


return top == -1;
}

public static void main(String args) {


ArrayStack<Integer> stack = new ArrayStack<>();
[Link](10);
[Link](20);
[Link](30);
[Link]("Top element is: " + [Link]()); // 30
[Link]("Popped element: " + [Link]()); // 30
[Link]("Top element now: " + [Link]()); // 20
}
}

Practice Problems 60
1. Valid Parentheses: Given a string containing just the characters (, ), {, }, [ and ], determine
if the input string is valid. An input string is valid if open brackets are closed by the same
type of brackets and in the correct order.
2. Min Stack: Design a stack that supports push, pop, top, and retrieving the minimum
element in constant time.
3. Evaluate Reverse Polish Notation: Evaluate the value of an arithmetic expression in
Reverse Polish Notation (e.g., ["2", "1", "+", "3", "*"] -> (2 + 1) * 3 = 9).
4. Daily Temperatures: Given a list of daily temperatures, produce a list that, for each day in
the input, tells you how many days you would have to wait until a warmer temperature. If
there is no future day for which this is possible, put 0 instead.
5. Implement Queue using Stacks: Implement a FIFO queue using only two stacks. The
implemented queue should support all the functions of a normal queue (push, peek, pop,
and empty).

5.4 Queues

Theoretical Explanation

A queue is a linear data structure that follows the First-In, First-Out (FIFO) principle. Elements
are added to the rear (enqueue) and removed from the front (dequeue).43 This models a real-
world queue or line.
● enqueue (or add/offer): Adds an element to the rear of the queue.
● dequeue (or remove/poll): Removes and returns the element from the front of the queue.
● peek (or element): Returns the element at the front without removing it.

In Java, Queue is an interface. LinkedList and ArrayDeque are common implementations.

Code Example: Custom Queue using a Linked List

Java

public class LinkedQueue<T> {


private static class Node<T> {
T data;
Node<T> next;
Node(T data) { [Link] = data; }
}

private Node<T> front;


private Node<T> rear;

public void enqueue(T item) {


Node<T> newNode = new Node<>(item);
if (rear!= null) {
[Link] = newNode;
}
rear = newNode;
if (front == null) {
front = rear;
}
}

public T dequeue() {
if (front == null) {
throw new IllegalStateException("Queue is empty");
}
T data = [Link];
front = [Link];
if (front == null) {
rear = null;
}
return data;
}

public boolean isEmpty() {


return front == null;
}

public static void main(String args) {


LinkedQueue<Integer> queue = new LinkedQueue<>();
[Link](10);
[Link](20);
[Link](30);
[Link]("Dequeued: " + [Link]()); // 10
[Link]("Dequeued: " + [Link]()); // 20
}
}

Practice Problems
1. Implement Stack using Queues: Implement a LIFO stack using only two queues. The
implemented stack should support all the functions of a normal stack (push, top, pop, and
empty).
2. Number of Islands: Given a 2D grid map of '1's (land) and '0's (water), count the number
of islands. An island is surrounded by water and is formed by connecting adjacent lands
horizontally or vertically. (BFS using a queue is a common approach).
3. Level Order Traversal of a Binary Tree: Given the root of a binary tree, return the level
order traversal of its nodes' values (i.e., from left to right, level by level). A queue is
essential for this algorithm.
4. Generate Binary Numbers: Given a number n, write a function that generates and prints
all binary numbers with decimal values from 1 to n. Use a queue.
5. Moving Average from Data Stream: Given a stream of integers and a window size,
calculate the moving average of all integers in the sliding window.
5.5 Hash Tables

Theoretical Explanation

A hash table (or hash map) is a data structure that stores key-value pairs. It uses a hash
function to compute an index, also called a hash code, into an array of buckets or slots, from
which the desired value can be found.62 This allows for average-case constant time complexity,
O(1), for insertion, deletion, and retrieval.64

A collision occurs when two different keys hash to the same index. Common collision
resolution strategies include 63:
● Separate Chaining: Each bucket is the head of a linked list. All keys that hash to the same
bucket are stored in that list. This is the strategy used by Java's HashMap.
● Open Addressing: When a collision occurs, the algorithm probes the table for the next
empty slot.

Java provides HashMap, HashSet, and the legacy Hashtable as implementations.

Code Example: Custom Hash Map with Separate Chaining

Java

import [Link];

public class SimpleHashMap<K, V> {


private static class Entry<K, V> {
K key;
V value;
Entry(K key, V value) {
[Link] = key;
[Link] = value;
}
}

private LinkedList<Entry<K, V>> table;


private int capacity;

@SuppressWarnings("unchecked")
public SimpleHashMap(int capacity) {
[Link] = capacity;
[Link] = new LinkedList[capacity];
for (int i = 0; i < capacity; i++) {
table[i] = new LinkedList<>();
}
}

private int hash(K key) {


return [Link]([Link]() % capacity);
}

public void put(K key, V value) {


int index = hash(key);
LinkedList<Entry<K, V>> bucket = table[index];
for (Entry<K, V> entry : bucket) {
if ([Link](key)) {
[Link] = value; // Update existing key
return;
}
}
[Link](new Entry<>(key, value)); // Add new entry
}

public V get(K key) {


int index = hash(key);
LinkedList<Entry<K, V>> bucket = table[index];
for (Entry<K, V> entry : bucket) {
if ([Link](key)) {
return [Link];
}
}
return null; // Key not found
}

public static void main(String args) {


SimpleHashMap<String, Integer> map = new SimpleHashMap<>(10);
[Link]("Alice", 95);
[Link]("Bob", 88);
[Link]("Charlie", 76);
[Link]("Bob's score: " + [Link]("Bob"));
}
}

Practice Problems 65
1. Two Sum: Given an array of integers nums and an integer target, return indices of the two
numbers such that they add up to target. You may assume that each input would have
exactly one solution, and you may not use the same element twice.
2. Group Anagrams: Given an array of strings, group the anagrams together. You can return
the answer in any order.
3. Logger Rate Limiter: Design a logger system that receives a stream of messages along
with their timestamps. Each unique message should only be printed at most every 10
seconds.
4. First Unique Character in a String: Given a string, find the first non-repeating character
in it and return its index. If it does not exist, return -1.
5. Ransom Note: Given two strings, ransomNote and magazine, return true if ransomNote
can be constructed from the letters in magazine, and false otherwise. Each letter in
magazine can only be used once.
6. Insert Delete GetRandom O(1): Implement a data structure that supports inserting,
deleting, and retrieving a random element, all in average O(1) time.

5.6 Trees

Theoretical Explanation

A tree is a hierarchical, non-linear data structure consisting of nodes connected by edges. It


has a special root node, and each node can have zero or more child nodes. A node with no
children is a leaf node.67
● Binary Tree: A tree where each node has at most two children, referred to as the left child
and the right child.
● Binary Search Tree (BST): A binary tree with a specific ordering property: for any given
node, all values in its left subtree are less than the node's value, and all values in its right
subtree are greater than the node's value. This property allows for efficient searching,
insertion, and deletion (average O(log n)).69

Tree Traversal Methods:


● In-order: Left -> Root -> Right (yields sorted data for a BST).
● Pre-order: Root -> Left -> Right.
● Post-order: Left -> Right -> Root.
● Level-order (BFS): Visits nodes level by level.

Code Example: Custom Binary Search Tree

Java
public class BinarySearchTree {
private Node root;

private class Node {


int value;
Node left, right;

Node(int value) {
[Link] = value;
left = right = null;
}
}

public void insert(int value) {


root = insertRecursive(root, value);
}

private Node insertRecursive(Node current, int value) {


if (current == null) {
return new Node(value);
}
if (value < [Link]) {
[Link] = insertRecursive([Link], value);
} else if (value > [Link]) {
[Link] = insertRecursive([Link], value);
}
return current;
}

public void inOrderTraversal() {


inOrderRecursive(root);
[Link]();
}

private void inOrderRecursive(Node current) {


if (current!= null) {
inOrderRecursive([Link]);
[Link]([Link] + " ");
inOrderRecursive([Link]);
}
}

public static void main(String args) {


BinarySearchTree bst = new BinarySearchTree();
[Link](50);
[Link](30);
[Link](70);
[Link](20);
[Link](40);
[Link](60);
[Link](80);

[Link]("In-order traversal: ");


[Link](); // Output: 20 30 40 50 60 70 80
}
}

Practice Problems 70
1. Maximum Depth of Binary Tree: Given the root of a binary tree, return its maximum
depth.
2. Validate Binary Search Tree: Given the root of a binary tree, determine if it is a valid
binary search tree (BST).
3. Invert Binary Tree: Given the root of a binary tree, invert the tree, and return its root.
4. Lowest Common Ancestor of a BST: Given a binary search tree (BST), find the lowest
common ancestor (LCA) of two given nodes in the BST.
5. Binary Tree Level Order Traversal: Given the root of a binary tree, return the level order
traversal of its nodes' values.
6. Convert Sorted Array to Binary Search Tree: Given an integer array nums where the
elements are sorted in ascending order, convert it to a height-balanced binary search tree.

5.7 Graphs

Theoretical Explanation

A graph is a non-linear data structure consisting of a set of vertices (or nodes) and a set of
edges that connect pairs of vertices.72 Graphs are used to model networks and relationships.

Graph Representations:
● Adjacency Matrix: A 2D array where matrix[i][j] = 1 if there is an edge from vertex i to
vertex j. It's space-inefficient for sparse graphs but fast for checking the existence of an
edge.74
● Adjacency List: An array of lists, where adjList[i] contains a list of all vertices adjacent to
vertex i. It's space-efficient for sparse graphs.75
Graph Traversal Algorithms:
● Breadth-First Search (BFS): Explores the graph level by level from a starting vertex. It
uses a queue.
● Depth-First Search (DFS): Explores as far as possible along each branch before
backtracking. It uses a stack (or recursion).

Code Example: Graph with Adjacency List and DFS

Java

import [Link].*;

public class Graph {


private Map<Integer, List<Integer>> adjList;

public Graph() {
[Link] = new HashMap<>();
}

public void addVertex(int vertex) {


[Link](vertex, new ArrayList<>());
}

public void addEdge(int v1, int v2) {


[Link](v1).add(v2);
[Link](v2).add(v1); // For an undirected graph
}

public void dfs(int start) {


Set<Integer> visited = new HashSet<>();
dfsRecursive(start, visited);
}

private void dfsRecursive(int current, Set<Integer> visited) {


[Link](current);
[Link](current + " ");

for (int neighbor : [Link](current)) {


if (![Link](neighbor)) {
dfsRecursive(neighbor, visited);
}
}
}

public static void main(String args) {


Graph g = new Graph();
for (int i = 0; i < 5; i++) {
[Link](i);
}
[Link](0, 1);
[Link](0, 4);
[Link](1, 2);
[Link](1, 3);
[Link](1, 4);
[Link](2, 3);
[Link](3, 4);

[Link]("Depth First Traversal starting from vertex 0:");


[Link](0);
}
}

Practice Problems 76
1. Number of Islands: Given an m x n 2D binary grid which represents a map of '1's (land)
and '0's (water), return the number of islands.
2. Clone Graph: Given a reference of a node in a connected undirected graph, return a deep
copy (clone) of the graph.
3. Course Schedule: There are a total of numCourses courses you have to take, labeled
from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] =
[a_i, b_i] indicates that you must take course b_i first if you want to take course a_i. Return
true if you can finish all courses. Otherwise, return false. (This is a topological sort
problem).
4. Word Ladder: Given two words, beginWord and endWord, and a dictionary's word list,
return the length of the shortest transformation sequence from beginWord to endWord,
such that only one letter can be changed at a time and each transformed word must exist
in the word list.
5. Shortest Path in Unweighted Graph: Find the shortest path between two nodes in an
unweighted graph using BFS.
6. Detect Cycle in a Directed Graph: Given a directed graph, check whether the graph
contains a cycle or not.

Section 6: Essential Algorithms


This section covers fundamental algorithms that are cornerstones of computer science and
frequent subjects of technical interviews. For each, we provide an implementation, analyze its
complexity, and list common problems.

6.1 Searching Algorithms

Theoretical Explanation

Searching algorithms are used to find a specific element within a data structure. The choice of
algorithm depends heavily on whether the data is sorted.78
● Linear Search: This is the simplest search algorithm. It sequentially checks each element
of the list until a match is found or the whole list has been searched. It works on unsorted
data but has a time complexity of O(n).79
● Binary Search: A highly efficient algorithm that works only on sorted data. It repeatedly
divides the search interval in half. If the value of the search key is less than the item in the
middle of the interval, it narrows the interval to the lower half. Otherwise, it narrows it to the
upper half. This process continues until the value is found or the interval is empty. Its time
complexity is O(log n).79

Code Example 1: Linear Search

Java

public class LinearSearch {


public static int search(int arr, int target) {
for (int i = 0; i < [Link]; i++) {
if (arr[i] == target) {
return i; // Target found at index i
}
}
return -1; // Target not found
}

public static void main(String args) {


int numbers = {22, 35, 12, 4, 58, 9};
int target = 58;
int index = search(numbers, target);
if (index!= -1) {
[Link]("Element " + target + " found at index " + index);
} else {
[Link]("Element " + target + " not found.");
}
}
}

Code Example 2: Binary Search (Iterative)

Java

public class BinarySearch {


public static int search(int sortedArr, int target) {
int left = 0;
int right = [Link] - 1;

while (left <= right) {


int mid = left + (right - left) / 2; // Avoids potential overflow

if (sortedArr[mid] == target) {
return mid; // Target found
} else if (sortedArr[mid] < target) {
left = mid + 1; // Search in the right half
} else {
right = mid - 1; // Search in the left half
}
}
return -1; // Target not found
}

public static void main(String args) {


int sortedNumbers = {4, 9, 12, 22, 35, 58};
int target = 12;
int index = search(sortedNumbers, target);
if (index!= -1) {
[Link]("Element " + target + " found at index " + index);
} else {
[Link]("Element " + target + " not found.");
}
}
}
Practice Problems 79
1. Search in Rotated Sorted Array: There is an integer array nums sorted in ascending
order (with distinct values) that has been rotated at an unknown pivot. Given the array
nums after the rotation and an integer target, return the index of target if it is in nums, or -1
if it is not. You must write an algorithm with O(log n) runtime complexity.
2. Find First and Last Position of Element in Sorted Array: Given an array of integers
nums sorted in non-decreasing order, find the starting and ending position of a given target
value. If target is not found, return [-1, -1]. You must write an algorithm with O(log n)
runtime complexity.
3. Square Root (Integer): Given a non-negative integer x, compute and return the square
root of x. Since the return type is an integer, the decimal digits are truncated, and only the
integer part of the result is returned. (Hint: Use binary search on the range of possible
answers).
4. Find Peak Element:

A peak element is an element that is strictly greater than its neighbors. Given an integer array

6.2 Sorting Algorithms

Theoretical Explanation

Sorting algorithms arrange elements of a list in a certain order (e.g., numerical or


lexicographical). Understanding their mechanisms and trade-offs is a staple of technical
interviews.82
● Bubble Sort: A simple algorithm that repeatedly steps through the list, compares adjacent
elements, and swaps them if they are in the wrong order. The pass through the list is
repeated until the list is sorted. Time Complexity: O(n²). Space Complexity: O(1).
● Insertion Sort: Builds the final sorted array one item at a time. It iterates through the input
elements and inserts each element into its correct position in the sorted part of the array.
Efficient for small or nearly sorted datasets. Time Complexity: O(n²). Space Complexity:
O(1).
● Merge Sort: A divide-and-conquer algorithm. It divides the array into two halves,
recursively sorts them, and then merges the two sorted halves. It guarantees O(n log n)
performance but requires O(n) extra space.83
● Quick Sort: Another divide-and-conquer algorithm. It picks a 'pivot' element and partitions
the other elements into two sub-arrays, according to whether they are less than or greater
than the pivot. The sub-arrays are then sorted recursively. Average Time Complexity: O(n
log n). Worst-Case Time Complexity: O(n²). Space Complexity: O(log n) on average for the
recursion stack.

Code Examples

Java

import [Link];

public class SortingAlgorithms {

// Bubble Sort
public static void bubbleSort(int arr) {
int n = [Link];
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// Merge Sort
public static void mergeSort(int arr, int n) {
if (n < 2) {
return;
}
int mid = n / 2;
int l = new int[mid];
int r = new int[n - mid];

for (int i = 0; i < mid; i++) {


l[i] = arr[i];
}
for (int i = mid; i < n; i++) {
r[i - mid] = arr[i];
}
mergeSort(l, mid);
mergeSort(r, n - mid);
merge(arr, l, r, mid, n - mid);
}

private static void merge(int a, int l, int r, int left, int right) {
int i = 0, j = 0, k = 0;
while (i < left && j < right) {
if (l[i] <= r[j]) {
a[k++] = l[i++];
} else {
a[k++] = r[j++];
}
}
while (i < left) {
a[k++] = l[i++];
}
while (j < right) {
a[k++] = r[j++];
}
}

public static void main(String args) {


int array1 = {64, 34, 25, 12, 22, 11, 90};
bubbleSort(array1);
[Link]("Bubble Sorted array: " + [Link](array1));

int array2 = {12, 11, 13, 5, 6, 7};


mergeSort(array2, [Link]);
[Link]("Merge Sorted array: " + [Link](array2));
}
}

Practice Problems
1. Sort Colors: Given an array nums with n objects colored red, white, or blue, sort them in-
place so that objects of the same color are adjacent, with the order red, white, and blue.
Use integers 0, 1, and 2 to represent the color red, white, and blue, respectively. (Dutch
National Flag problem).
2. Kth Largest Element in an Array: Given an integer array nums and an integer k, return
the k-th largest element in the array. (Hint: A modified Quick Sort partition method is very
efficient).
3. Top K Frequent Elements: Given an integer array nums and an integer k, return the k
most frequent elements. You may return the answer in any order.
4. Meeting Rooms II: Given an array of meeting time intervals intervals where intervals[i] =
[start_i, end_i], return the minimum number of conference rooms required. (Hint: Sorting is
the first step).
5. Merge K Sorted Lists: You are given an array of k linked-lists, where each linked-list is
sorted in ascending order. Merge all the linked-lists into one sorted linked-list and return it.

6.3 Recursion

Theoretical Explanation

Recursion is a programming technique where a method calls itself to solve a problem. A


recursive method must have two parts 85:
1. Base Case: A condition that stops the recursion. Without a base case, the recursion would
continue indefinitely, leading to a StackOverflowError.
2. Recursive Step: The part of the method that calls itself, typically with a modified argument
that moves it closer to the base case.

Recursion can lead to elegant and simple solutions for problems that can be broken down into
smaller, self-similar subproblems, such as tree traversals or factorial calculations.

Code Example: Factorial Calculation

Java

public class RecursionDemo {

// Recursive method to calculate factorial


public static long factorial(int n) {
// Base case: factorial of 0 or 1 is 1
if (n <= 1) {
return 1;
}
// Recursive step: n * factorial of (n-1)
else {
return n * factorial(n - 1);
}
}

public static void main(String args) {


int number = 5;
long result = factorial(number);
[Link]("The factorial of " + number + " is " + result); // Output: 120
}
}

Practice Problems 85
1. Fibonacci Number: Write a recursive function to calculate the nth Fibonacci number.
2. Reverse a String: Write a recursive function that takes a string as input and returns the
string reversed.
3. Power Function: Implement pow(x, n), which calculates x raised to the power n (i.e., xn),
using recursion.
4. Subsets (Power Set): Given an integer array nums of unique elements, return all possible
subsets (the power set). The solution set must not contain duplicate subsets.
5. Merge Sort: Implement the Merge Sort algorithm using recursion. The core logic of
dividing the array is recursive.

6.4 Dynamic Programming (Introductory)

Theoretical Explanation

Dynamic Programming (DP) is an optimization technique for solving complex problems by


breaking them down into simpler, overlapping subproblems. It is an enhancement over plain
recursion.86 The key idea is to solve each subproblem only once and store its result, avoiding
redundant computations.87

A problem is suitable for DP if it has two properties 87:


1. Overlapping Subproblems: The recursive solution involves solving the same
subproblems multiple times.
2. Optimal Substructure: The optimal solution to the overall problem can be constructed
from the optimal solutions of its subproblems.

There are two main approaches to DP:


● Memoization (Top-Down): The recursive solution is kept, but results of subproblems are
stored in a cache (e.g., a map or array). Before computing a subproblem, the cache is
checked.
● Tabulation (Bottom-Up): The solution is built iteratively, starting from the smallest
subproblems and building up to the final solution.

Code Example: Fibonacci with Memoization

Java
import [Link];
import [Link];

public class DynamicProgrammingDemo {

// Using a map for memoization


private static Map<Integer, Long> memo = new HashMap<>();

public static long fibonacci(int n) {


if (n <= 1) {
return n;
}
// Check if the result is already in the cache
if ([Link](n)) {
return [Link](n);
}
// Compute and store the result
long result = fibonacci(n - 1) + fibonacci(n - 2);
[Link](n, result);
return result;
}

public static void main(String args) {


int n = 50;
[Link]("Fibonacci(" + n + ") = " + fibonacci(n));
// A simple recursive solution would be extremely slow for n=50.
}
}

Practice Problems 88
1. Climbing Stairs: You are climbing a staircase. It takes n steps to reach the top. Each time
you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
2. 0/1 Knapsack Problem: You are given weights and values of n items. You need to put
these items in a knapsack of capacity W to get the maximum total value. You cannot break
an item.
3. Longest Increasing Subsequence: Given an integer array nums, return the length of the
longest strictly increasing subsequence.
4. Coin Change: You are given an integer array coins representing coins of different
denominations and an integer amount representing a total amount of money. Return the
fewest number of coins that you need to make up that amount. If that amount of money
cannot be made up by any combination of the coins, return -1.
5. Word Break Problem: Given a string s and a dictionary of strings wordDict, return true if s
can be segmented into a space-separated sequence of one or more dictionary words.

Part IV: The Professional Developer's Toolkit

This final part provides a glimpse into the tools and practices of a professional Java developer.
Mastering these tools is essential for working effectively in a team, building robust applications,
and moving beyond academic exercises into real-world software engineering.

Section 7: Building and Testing Your Code

7.1 Build Automation: Maven vs. Gradle

Theoretical Explanation

Build automation tools are essential for managing a project's lifecycle, including compiling
source code, managing dependencies, running tests, and packaging the application. The two
dominant build tools in the Java ecosystem are Maven and Gradle.89
● Apache Maven: A long-standing and widely used tool that uses a declarative XML file,
[Link], for configuration. It follows a principle of "convention over configuration,"
providing a rigid

Works cited

1. Java Tutorial - GeeksforGeeks, accessed August 23, 2025,


[Link]
2. How JVM Works - JVM Architecture - GeeksforGeeks, accessed August 23, 2025,
[Link]
3. JVM Tutorial - Java Virtual Machine Architecture Explained for Beginners -
freeCodeCamp, accessed August 23, 2025,
[Link]
explained-for-beginners/
4. Java Coding Practice | Improve Your Skills with Free Java Exercises - CodeGym,
accessed August 23, 2025, [Link]
5. Java Language Basics - [Link], accessed August 23, 2025,
[Link]
6. Creating Variables and Naming Them - [Link], accessed August 23, 2025,
[Link]
7. Core Java Tutorial - Learn Java - Studytonight, accessed August 23, 2025,
[Link]
8. Control Flow Statements - [Link], accessed August 23, 2025,
[Link]
9. Arrays - Learning the Java Language, accessed August 23, 2025,
[Link]
10. Java Array (With Examples) - Programiz, accessed August 23, 2025,
[Link]
11. An Overview of Arrays as Data Structures in Java - Pieces for Developers,
accessed August 23, 2025, [Link]
structures-in-java
12. Object Oriented Programming (OOP) in Java Course - YouTube, accessed
August 23, 2025, [Link]
list=PL9gnSGHSqcno1G3XjUbwzXHL8_EttOuKk
13. Mastering OOP Concepts in Java with Examples - Great Learning, accessed
August 23, 2025, [Link]
14. Understanding Object-Oriented Programming (OOP) Concepts with Java
Examples, accessed August 23, 2025,
[Link]
concepts-with-java-examples-9e4627abf1be
15. Using OOP concepts to write high-performance Java code (2023) · Raygun Blog,
accessed August 23, 2025, [Link]
16. Encapsulation in Java (with Example) - Geekster, accessed August 23, 2025,
[Link]
17. Master Encapsulation in Java with Simple Examples - [Link], accessed August
23, 2025, [Link]
18. OOP Principles For Dummies - DEV Community, accessed August 23, 2025,
[Link]
19. Java Encapsulation - Exercises, Practices, Solutions - w3resource, accessed
August 23, 2025,
[Link]
20. Java Inheritance (With Examples) - Programiz, accessed August 23, 2025,
[Link]
21. Inheritance in Java With Examples - BeginnersBook, accessed August 23, 2025,
[Link]
22. Java Inheritance (with Example) - Geekster, accessed August 23, 2025,
[Link]
23. Inheritance in Java - GeeksforGeeks, accessed August 23, 2025,
[Link]
24. Learn Java: Inheritance and Polymorphism Cheatsheet | Codecademy, accessed
August 23, 2025, [Link]
java-inheritance-and-polymorphism/cheatsheet
25. Java Inheritance: Exercises, Practice, Solution - Best Training IT Courses in
Dehradun, accessed August 23, 2025, [Link]
exercises-practice-solution/
26. Java Inheritance: Exercises, Practice, Solution - w3resource, accessed August 23,
2025, [Link]
27. Java Inheritance I | HackerRank, accessed August 23, 2025,
[Link]
28. Polymorphism in Java (with Example) - Geekster, accessed August 23, 2025,
[Link]
29. Polymorphism In Java With Examples | by Ujjawal Rohra - Medium, accessed
August 23, 2025, [Link]
examples-1b4747ee1445
30. Polymorphism in Java with example - BeginnersBook, accessed August 23, 2025,
[Link]
31. Polymorphism in Java - GeeksforGeeks, accessed August 23, 2025,
[Link]
32. Java Polymorphism: Exercises, Practice and Solutions - w3resource, accessed
August 23, 2025, [Link]
[Link]
33. Abstraction in Java - GeeksforGeeks, accessed August 23, 2025,
[Link]
34. Java Abstract Class and Method (With Example) - Programiz, accessed August
23, 2025, [Link]
methods
35. What is Abstraction in Java with Examples & Its Uses - ScholarHat, accessed
August 23, 2025, [Link]
36. Java Abstract Classes: Exercises, Practices, Solutions - w3resource, accessed
August 23, 2025, [Link]
37. Java - Collections Framework - Tutorialspoint, accessed August 23, 2025,
[Link]
38. Trail: Collections (The Java™ Tutorials) - Oracle Help Center, accessed August
23, 2025, [Link]
39. Java Collection Tutorial - GeeksforGeeks, accessed August 23, 2025,
[Link]
40. Generic Types (The Java™ Tutorials > Learning the Java Language ..., accessed
August 23, 2025, [Link]
41. Java Generics (With Examples) - Programiz, accessed August 23, 2025,
[Link]
42. Generics in Java - GeeksforGeeks, accessed August 23, 2025,
[Link]
43. Data Structures in Java (with code examples) - CodeChef, accessed August 23,
2025, [Link]
44. LinkedList in Java - GeeksforGeeks, accessed August 23, 2025,
[Link]
45. A Comprehensive Guide to LinkedList in Java | by Yodgorbek Komilov - Medium,
accessed August 23, 2025, [Link]
comprehensive-guide-to-linkedlist-in-java-a64a4584a3dd
46. Java JDBC - [Link], accessed August 23, 2025,
[Link]
47. Establishing JDBC Connection in Java - GeeksforGeeks, accessed August 23,
2025, [Link]
48. JDBC - Sample, Example Code - Tutorials Point, accessed August 23, 2025,
[Link]
49. A Deep Dive into JVM Start-up - [Link], accessed August 23, 2025,
[Link]
50. Data structures and algorithms study cheatsheets for coding interviews, accessed
August 23, 2025, [Link]
cheatsheet/
51. DSA using Java - Arrays - Tutorials Point, accessed August 23, 2025,
[Link]
52. Array Data Structure - GeeksforGeeks, accessed August 23, 2025,
[Link]
53. Top 20+ Array Interview Questions in Java (2025) - Hirist, accessed August 23,
2025, [Link]
54. Top 10 Array Interview Questions: A Comprehensive Guide | by Kirti Arora -
Medium, accessed August 23, 2025, [Link]
array-interview-questions-a-comprehensive-guide-40557456a262
55. Linked List in Java: All You Need to Know About it - [Link], accessed
August 23, 2025, [Link]
java
56. Data structures 101: How to use linked lists in Java - [Link], accessed
August 23, 2025, [Link]
tutorial
57. Top 10 Linked List Interview Questions | by Kirti Arora - Medium, accessed August
23, 2025, [Link]
bd155dad51f1
58. Top 50 Problems on Linked List Data Structure asked in SDE Interviews -
GeeksforGeeks, accessed August 23, 2025,
[Link]
59. Java Stacks & Queues Beginners Guide | Medium, accessed August 23, 2025,
[Link]
structures-stack-and-queue-54b6f7259a08
60. Stack Data Structure - GeeksforGeeks, accessed August 23, 2025,
[Link]
61. Java Stack Class - Programiz, accessed August 23, 2025,
[Link]
62. Implementing Hash Tables from Scratch in Java - Hackajob, accessed August 23,
2025, [Link]
java
63. Hash Table Data Structure - GeeksforGeeks, accessed August 23, 2025,
[Link]
64. DSA using Java - Hash Table - Tutorialspoint, accessed August 23, 2025,
[Link]
65. 39 Must-Know Hash Table Data Structure Interview Questions in 2025 - GitHub,
accessed August 23, 2025, [Link]
structure-interview-questions
66. Top HashMap Interview Questions and Answers (2025) - InterviewBit, accessed
August 23, 2025, [Link]
67. Introduction to Tree Data Structure - GeeksforGeeks, accessed August 23, 2025,
[Link]
68. A Comprehensive Guide to Tree Data Structures in Java: From Beginner to
Advanced | by Yodgorbek Komilov | Medium, accessed August 23, 2025,
[Link]
structures-in-java-from-beginner-to-advanced-2232d0902208
69. Top 50+ Data Structure Interview Questions and Answers (2025) - InterviewBit,
accessed August 23, 2025, [Link]
questions/
70. 50+ tree questions and solutions (easy, medium, hard) - IGotAnOffer, accessed
August 23, 2025, [Link]
71. Tree cheatsheet for coding interviews - Tech Interview Handbook, accessed
August 23, 2025, [Link]
72. Java Graph Tutorial – How To Implement Graph Data Structure - Software Testing
Help, accessed August 23, 2025, [Link]
graph-tutorial/
73. Graph Data Structure - GeeksforGeeks, accessed August 23, 2025,
[Link]
74. Graphs in Java | Baeldung, accessed August 23, 2025,
[Link]
75. Graph Algorithm Tutorial with Java Examples | by Yodgorbek Komilov - Medium,
accessed August 23, 2025, [Link]
algorithm-tutorial-with-java-examples-23abe60179cb
76. 50 Essential Graph Data Structure Interview Questions in 2025 - GitHub,
accessed August 23, 2025, [Link]
structure-interview-questions
77. Commonly Asked Data Structure Interview Questions on Graph - GeeksforGeeks,
accessed August 23, 2025, [Link]
data-structure-interview-questions-on-graph/
78. Searching Algorithms - GeeksforGeeks, accessed August 23, 2025,
[Link]
79. Searching Algorithms in Java - GeeksforGeeks, accessed August 23, 2025,
[Link]
80. Java Search Algorithms: Beginner's Guide | LatinXinAI - Medium, accessed
August 23, 2025, [Link]
algorithms-in-java-a-beginners-guide-fd6332380f1f
81. Java: Algorithms: Searching and Sorting Cheatsheet | Codecademy, accessed
August 23, 2025,
[Link]
sorting/cheatsheet
82. Sorting Algorithms - GeeksforGeeks, accessed August 23, 2025,
[Link]
83. Sorting Algorithms | GeeksforGeeks - LAYAR, accessed August 23, 2025,
[Link]
84. Merge Sort - GeeksforGeeks | Videos, accessed August 23, 2025,
[Link]
85. Five examples of recursion in Java - TheServerSide, accessed August 23, 2025,
[Link]
Opinions/examples-Java-recursion-recursive-methods
86. Dynamic Programming or DP - GeeksforGeeks, accessed August 23, 2025,
[Link]
87. Dynamic Programming (DP) Introduction - GeeksforGeeks, accessed August 23,
2025, [Link]
data-structures-and-algorithm-tutorials/
88. Dynamic Programming or DP - GeeksforGeeks, accessed August 23, 2025,
[Link]
89. Gradle and Maven Comparison - Gradle, accessed August 23, 2025,
[Link]

You might also like