Last Updated : August 2023
A HashMap in Java stores elements as key-value pairs using a hash table. Internally, it uses an array of Node objects (often referred to as buckets), where each Node is a linked list (or a balanced tree in Java 8+ for high collision scenarios). When you call put(K key, V value), the HashMap calculates the hash code of the key, applies a secondary hash function to mitigate poor hash distributions, and uses the modulo operator with the array capacity to determine the target bucket index.
Collisions occur when two distinct keys hash to the same bucket index. Prior to Java 8, collisions were handled strictly via Separate Chaining, where elements mapped to the same bucket were appended to a linked list. From Java 8 onwards, if a bucket's linked list size exceeds a certain threshold (TREEIFY_THRESHOLD, default 8), the linked list is transformed into a Red-Black Tree. This optimization significantly improves performance during high collision scenarios.
In the ideal scenario, where the hash function distributes keys uniformly, the time complexity for both get() and put() operations is O(1). However, in the worst-case scenario (e.g., a poor hash function where all keys map to the same bucket), the time complexity degrades. With traditional linked lists, this degrades to O(n). With the Java 8 Red-Black tree optimization, the worst-case degrades to O(log n), providing a substantial performance safeguard for large datasets.
Both synchronized blocks and ReentrantLock are used in Java to ensure thread safety by providing mutual exclusion. A synchronized block is an intrinsic lock tied to an object's monitor. It is syntactically simpler and implicitly releases the lock when the block is exited, even if an exception is thrown. ReentrantLock, introduced in java.util.concurrent.locks, is an explicit lock implementing the Lock interface. It requires explicit calls to lock() and unlock(), typically placing the unlock() in a finally block to ensure release.
ReentrantLock provides advanced features not available with intrinsic locks. These include the ability to interrupt a thread waiting for a lock (lockInterruptibly()), a non-blocking attempt to acquire a lock (tryLock()), and fairness policies (ensuring the longest-waiting thread acquires the lock first). Conversely, synchronized blocks offer no such flexibility and can lead to thread starvation or unresolvable deadlocks if not carefully managed.
A scenario where ReentrantLock is advantageous is when implementing a timed lock acquisition. If a thread needs to acquire two locks to perform a transaction but wants to avoid a deadlock, it can use tryLock(long timeout, TimeUnit unit). If the second lock isn't available within the timeframe, the thread can release the first lock, wait, and try again, effectively breaking the deadlock condition—a pattern impossible to implement with standard synchronized blocks.
The Model-View-Controller (MVC) design pattern divides an application into three interconnected components. The Model manages the data, logic, and rules of the application. The View is responsible for rendering the UI and displaying data to the user. The Controller acts as an intermediary, accepting user input from the View, manipulating the Model accordingly, and updating the View. This separation of concerns ensures that business logic is decoupled from presentation, allowing multiple developers to work simultaneously and making the codebase easier to test and maintain.
By decoupling these layers, MVC ensures that changes to the user interface do not necessitate changes to the underlying business rules. For example, replacing a web-based View with a mobile app View is straightforward because the Model and Controller logic can largely be reused. Furthermore, this modularity heavily facilitates automated unit testing, as the Model can be tested entirely independently of the UI framework.
However, as applications scale into highly interactive Single Page Applications (SPAs), traditional MVC often hits limitations. In complex SPAs, the data flow between Views and Controllers can become bidirectional and tangled, leading to cascading updates (often termed 'callback hell' or the 'spaghetti code' problem). This complexity led to the rise of unidirectional data flow architectures, such as Flux or Redux, which enforce strict, predictable state mutations, solving the unpredictable state management issues that plague large-scale MVC implementations.
Relational Database Management Systems (RDBMS) adhere strictly to ACID properties: Atomicity, Consistency, Isolation, and Durability. Atomicity ensures transactions are 'all or nothing'. Consistency guarantees the database transitions from one valid state to another. Isolation ensures concurrent transactions do not interfere with each other, and Durability guarantees committed data is permanently saved. This strict adherence makes ACID databases ideal for systems requiring absolute precision and reliability, such as financial ledger transactions or inventory management.
Conversely, many NoSQL databases prioritize BASE properties: Basically Available, Soft state, and Eventual consistency. Rooted in the CAP theorem, BASE sacrifices immediate consistency to achieve high availability and partition tolerance in distributed systems. 'Basically Available' means the system guarantees a response, even if it's stale data or a failure. 'Eventual consistency' means that, given a period without updates, all nodes will eventually reflect the same data state.
A system should prioritize BASE over ACID when horizontal scalability, high availability, and rapid read/write performance are more critical than immediate, absolute data accuracy. For example, a global social media platform generating millions of 'likes' per second does not need a strict ACID transaction for every click. If a user's like count is temporarily out of sync across different global servers (eventual consistency), the user experience is minimally impacted, but the system gains massive performance and uptime benefits that an ACID-compliant RDBMS could not sustain at that scale.
Representational State Transfer (REST) is an architectural style for designing networked applications. The core principles of RESTful API design include a client-server architecture, uniform interfaces (using standard HTTP methods like GET, POST, PUT, DELETE), resource-based URIs, cacheability, layered systems, and crucially, statelessness. In a RESTful system, resources are treated as nouns (e.g., /users/123), and state is passed back and forth via representations, most commonly in JSON format.
An API is defined as 'stateless' when the server does not store any client context or session data between requests. Every single HTTP request from the client must contain all the necessary information (including authentication tokens and parameters) for the server to understand and fulfill it. The server treats every request as completely independent; it does not remember that the client made a previous request five seconds ago.
This characteristic of statelessness is the primary driver of horizontal scalability in modern web architectures. Because no session state is bound to a specific server, a load balancer can route a client's request to any available server in a cluster without breaking the user experience. If traffic spikes, the infrastructure team can simply spin up additional server instances without worrying about replicating session memory across the fleet. This eliminates a massive bottleneck, making cloud-native, infinitely scalable applications possible.
Advanced Programming & Data Structures
Improve your academic writing instantly. Get advanced grammar and style suggestions.
Try it for free