<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Sayantan Banerjee on Medium]]></title>
        <description><![CDATA[Stories by Sayantan Banerjee on Medium]]></description>
        <link>https://medium.com/@sayantanbanerjee_91303?source=rss-7ebcf9c6e96e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*HlvNdcSEWQYmRXyW2iDi2A.jpeg</url>
            <title>Stories by Sayantan Banerjee on Medium</title>
            <link>https://medium.com/@sayantanbanerjee_91303?source=rss-7ebcf9c6e96e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 02:20:01 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@sayantanbanerjee_91303/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[System Design Part III — Design a multiple elevator problem]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/system-design-part-iii-design-a-multiple-elevator-problem-4f95ffb21527?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4f95ffb21527</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Sat, 31 Oct 2026 17:56:49 GMT</pubDate>
            <atom:updated>2026-10-31T17:56:49.762Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><em>“If you’re lucky enough to do well, it’s your responsibility to send the elevator back down.”</em> — <strong>Kevin Spacey</strong></blockquote><h3>PROLOGUE</h3><p>Elevators look simple: press a button, wait, ride. But the moment a building has more than one car, we are solving a <strong>scheduling problem</strong> with real people waiting. Which car answers which call? What do idle cars do? What happens at 9 AM when everyone arrives at once? It’s also a great LLD question, because it forces us to model state, choose algorithms, and keep concurrency under control.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5uBYYVmV2VKSTbyBcUgfAA.jpeg" /></figure><blockquote>I had a lot of fun playing this game in my childhood, anyone remembers?</blockquote><h3>1. What are we building?</h3><p>A system that controls <strong>M elevators</strong> serving <strong>N floors</strong>, accepts passenger requests, and decides how cars move.</p><h4>Functional requirements</h4><ol><li>Passengers press <strong>UP/DOWN</strong> at a floor (a <em>hall call</em>)</li><li>Passengers press a <strong>destination floor</strong> inside the car (a <em>car call</em>)</li><li>Multiple elevators operate together.</li><li>Doors open and close, and the car stops at requested floors.</li><li>Support emergency stop, maintenance mode, and overload detection.</li></ol><h4>Non-functional requirements</h4><ol><li><strong>Minimize average wait time</strong> (call to pickup)</li><li><strong>Minimize worst-case wait</strong>, so nobody starves</li><li><strong>Safe by default</strong>: safety logic always beats scheduling logic</li><li><strong>Fault tolerant</strong>: one broken car must not stop the building</li><li><strong>Extensible</strong>: swap scheduling algorithms without rewriting the system</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dCS-15Of7C4jMDvRPoN7Dw.png" /></figure><p>This distinction drives the whole design. A car call is already assigned to a car. A hall call must be <strong>assigned by the system</strong>, and that assignment is the interesting problem.</p><h3>2. Core entities</h3><pre>Building<br> ├── List&lt;Floor&gt;<br> ├── List&lt;ElevatorCar&gt;<br> └── ElevatorDispatcher ── uses ──► DispatchStrategy<br><br>ElevatorCar<br> ├── id, currentFloor, direction, state<br> ├── capacity / currentLoad<br> ├── upStops, downStops   (pending stops)<br> └── Door<br><br>Requests<br> ├── HallCall (floor, direction, timestamp)<br> └── CarCall  (carId, destinationFloor)</pre><p><strong>Separate the car from the dispatcher.</strong> The car knows how to <em>move and serve stops</em>. The dispatcher knows <em>which car should get which call</em>. Mixing the two is the most common LLD mistake here.</p><h3>3. The elevator state machine</h3><p>Every car is always in exactly one state:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xUEKJQvgqFFzTNNaLew_Gg.png" /></figure><p>Modeling this explicitly (State pattern or an enum with a switch) makes the safety rules easy to enforce. For example, <strong>never move while </strong><strong>DOORS_OPEN</strong> becomes a one-line guard instead of scattered if statements.</p><h3>4. First steps — Single elevator: How should a car move?</h3><h4>Option 1: FCFS (First Come, First Served)</h4><p>Serve requests in arrival order.</p><ul><li><strong>Pros:</strong> trivial, perfectly fair in ordering</li><li><strong>Cons:</strong> wildly inefficient. A car at floor 10 told to go to 2, then 9, then 3 zigzags needlessly.</li></ul><h4>Option 2: SSTF (Shortest Seek Time First)</h4><p>Always go to the nearest pending stop.</p><ul><li><strong>Pros:</strong> short average trips</li><li><strong>Cons:</strong> <strong>starvation.</strong> A request at floor 40 can wait forever while floors 1 to 5 keep generating calls.</li></ul><h4>Option 3: SCAN</h4><p>Keep moving in one direction, serving every stop on the way, until we reach the end. Then reverse.</p><ul><li><strong>Pros:</strong> efficient and starvation-free (every floor is visited each sweep)</li><li><strong>Cons:</strong> wastes time traveling to the building’s end when nobody’s there</li></ul><h4>Option 4: LOOK (what is recommended and used)</h4><p>SCAN, but <strong>reverse at the last pending stop</strong> instead of the last floor. Real elevators use LOOK-style logic because it matches how people think: a car going up picks up everyone going up, then turns around.</p><h4>Implementing LOOK in JAVA</h4><p>Each car keeps <strong>two sorted sets</strong>: stops to serve while going up and stops to serve while going down. The set a stop goes into depends on <strong>which direction the passenger wants to travel</strong>, not just where the stop is.</p><pre>public enum Direction { UP, DOWN }<br>public enum ElevatorState { IDLE, MOVING, DOORS_OPEN, OUT_OF_SERVICE }<br><br>public class ElevatorCar {<br>    private static final int DOOR_OPEN_TICKS = 3;<br><br>    private final int id;<br>    private final TreeSet&lt;Integer&gt; up = new TreeSet&lt;&gt;();    // serve while moving UP<br>    private final TreeSet&lt;Integer&gt; down = new TreeSet&lt;&gt;();  // serve while moving DOWN<br><br>    private int floor = 0;<br>    private Direction direction = Direction.UP;<br>    private ElevatorState state = ElevatorState.IDLE;<br>    private int doorTicks;<br><br>    public ElevatorCar(int id) { this.id = id; }<br><br>    /** Hall call: stop here, to pick up someone heading in `dir`. */<br>    public synchronized void assignHallCall(int targetFloor, Direction dir) {<br>        (dir == Direction.UP ? up : down).add(targetFloor);<br>    }<br><br>    /** Car call: passenger inside pressed a floor button. */<br>    public synchronized void pressFloor(int targetFloor) {<br>        (targetFloor &gt;= floor ? up : down).add(targetFloor);<br>    }<br><br>    /** One simulation step (e.g. 1 second). */<br>    public synchronized void tick() {<br>        if (state == ElevatorState.OUT_OF_SERVICE) return;<br><br>        if (state == ElevatorState.DOORS_OPEN) {<br>            if (--doorTicks &gt; 0) return;<br>            state = ElevatorState.IDLE;                 // doors closed, decide next move<br>        }<br><br>        OptionalInt target = nextTarget();<br>        if (target.isEmpty()) { state = ElevatorState.IDLE; return; }<br><br>        if (target.getAsInt() == floor) { arrive(); return; }<br><br>        state = ElevatorState.MOVING;<br>        floor += target.getAsInt() &gt; floor ? 1 : -1;<br>    }<br><br>    /** LOOK: keep sweeping in the current direction; reverse only when nothing is ahead. */<br>    private OptionalInt nextTarget() {<br>        for (int attempt = 0; attempt &lt; 2; attempt++) {<br>            if (direction == Direction.UP) {<br>                Integer t = up.ceiling(floor);                          // next stop ahead<br>                if (t == null &amp;&amp; !down.isEmpty() &amp;&amp; down.last() &gt; floor)<br>                    t = down.last();                                    // turnaround point<br>                if (t != null) return OptionalInt.of(t);<br>                direction = Direction.DOWN;                             // nothing ahead: reverse<br>            } else {<br>                Integer t = down.floor(floor);<br>                if (t == null &amp;&amp; !up.isEmpty() &amp;&amp; up.first() &lt; floor)<br>                    t = up.first();<br>                if (t != null) return OptionalInt.of(t);<br>                direction = Direction.UP;<br>            }<br>        }<br>        return OptionalInt.empty();                                     // nothing pending<br>    }<br><br>    private void arrive() {<br>        boolean served = (direction == Direction.UP ? up : down).remove(floor);<br>        if (!served) (direction == Direction.UP ? down : up).remove(floor); // turnaround stop<br>        state = ElevatorState.DOORS_OPEN;<br>        doorTicks = DOOR_OPEN_TICKS;<br>        // notify listeners: onArrived(this, floor, direction)<br>    }<br><br>    // getters used by the dispatcher<br>    public synchronized int floor()              { return floor; }<br>    public synchronized Direction direction()    { return direction; }<br>    public synchronized boolean isIdle()         { return state == ElevatorState.IDLE &amp;&amp; up.isEmpty() &amp;&amp; down.isEmpty(); }<br>    public synchronized boolean isOutOfService() { return state == ElevatorState.OUT_OF_SERVICE; }<br>    public synchronized int pendingStops()       { return up.size() + down.size(); }<br>    public synchronized int id()                 { return id; }<br>}</pre><p>Note the <strong>tick-based design.</strong> Instead of real threads sleeping between floors, the car advances one step per tick. This makes the system deterministic and unit-testable, because it can run 10,000 ticks in a test in milliseconds. This is simplified; production code also handles overload, door obstruction, and maintenance transitions.</p><h3>5. Now the main problem -&gt; Multiple elevators: the dispatching problem</h3><p>Now the real question. A hall call arrives at floor 7 (UP). Which of the M cars should handle it? Here are some brute force strategies, and reasons why they fail!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-CJQeLVkX1dVZ58GPrRw2Q.png" /></figure><p><strong>Nearest by distance is wrong because distance is not time.</strong> What matters is the <strong>estimated time of arrival (ETA)</strong>, which depends on direction, existing stops, and load.</p><h4>The cost-based approach (the standard answer)</h4><p>For each car, compute a <strong>cost</strong> representing how long the passenger would wait. Assign the call to the cheapest car.</p><pre>public interface DispatchStrategy {<br>    ElevatorCar select(List&lt;ElevatorCar&gt; cars, HallCall call);<br>}<br><br>public class CostBasedStrategy implements DispatchStrategy {<br>    private static final int STOP_PENALTY = 3;       // ~ticks lost per extra stop (doors + decel)<br>    private static final int INF = Integer.MAX_VALUE;<br><br>    @Override<br>    public ElevatorCar select(List&lt;ElevatorCar&gt; cars, HallCall call) {<br>        return cars.stream()<br>                .filter(c -&gt; !c.isOutOfService())<br>                .min(Comparator.comparingInt((ElevatorCar c) -&gt; cost(c, call))<br>                        .thenComparingInt(ElevatorCar::pendingStops))   // tie-break: least busy<br>                .orElseThrow(() -&gt; new IllegalStateException(&quot;No elevator available&quot;));<br>    }<br><br>    private int cost(ElevatorCar car, HallCall call) {<br>        int dist = Math.abs(car.floor() - call.floor());<br><br>        // Case 1: idle car, so just the travel distance<br>        if (car.isIdle()) return dist;<br><br>        // Case 2: car is heading toward the caller in the same direction, so it&#39;s on the way<br>        boolean sameDir = car.direction() == call.direction();<br>        boolean ahead = call.direction() == Direction.UP<br>                ? call.floor() &gt;= car.floor()<br>                : call.floor() &lt;= car.floor();<br>        if (sameDir &amp;&amp; ahead) {<br>            return dist + STOP_PENALTY * car.pendingStops();<br>        }<br><br>        // Case 3: car must finish its current sweep, turn around, then come back<br>        // (simplified: charge a full sweep plus the return distance)<br>        int sweep = car.pendingStops() * STOP_PENALTY;<br>        return dist + sweep + 2 * Math.abs(car.floor() - call.floor()); // pessimistic estimate<br>    }<br>}</pre><p>The three cases capture the intuition:</p><ol><li><strong>Idle car:</strong> cost is just the distance</li><li><strong>Moving toward you, same direction:</strong> cost is the distance plus the delay from stops along the way</li><li><strong>Moving away or the wrong way:</strong> it has to finish, reverse, and return, so it’s heavily penalized</li></ol><p>The constants (STOP_PENALTY, etc.) are <strong>tunable parameters.</strong> In real systems they’re calibrated from measured door times and car speeds. A more accurate variant is to <strong>dry-run simulate</strong> a copy of each car with the call added and measure ticks until pickup. It costs more compute but reuses the exact movement logic, and with 8 cars and 40 floors the cost is negligible.</p><h4>The dispatcher</h4><blockquote>Note here :- Because the strategy is injected, switching from cost-based to zoned to destination-dispatch (below) is a <strong>one-line change</strong>, which is the point of the Strategy pattern.</blockquote><pre>public class ElevatorDispatcher {<br>    private final List&lt;ElevatorCar&gt; cars;<br>    private volatile DispatchStrategy strategy;<br>    private final Set&lt;HallCallKey&gt; pending = ConcurrentHashMap.newKeySet();<br><br>    public ElevatorDispatcher(List&lt;ElevatorCar&gt; cars, DispatchStrategy strategy) {<br>        this.cars = cars;<br>        this.strategy = strategy;<br>    }<br><br>    public void onHallCall(int floor, Direction dir) {<br>        // Passengers mash buttons; ignore duplicates for an already-pending call<br>        if (!pending.add(new HallCallKey(floor, dir))) return;<br><br>        HallCall call = new HallCall(floor, dir, Instant.now());<br>        ElevatorCar chosen = strategy.select(cars, call);<br>        chosen.assignHallCall(floor, dir);<br>    }<br><br>    // Called by the car&#39;s &quot;arrived&quot; event so the button can be pressed again<br>    public void onArrived(int floor, Direction dir) {<br>        pending.remove(new HallCallKey(floor, dir));<br>    }<br><br>    public void setStrategy(DispatchStrategy s) { this.strategy = s; }   // swap at runtime<br>}</pre><h4>All the design patterns at a glance</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*20NS4EMM4Wl6zJC1FDbhAQ.png" /></figure><h3>6. Concurrency</h3><p>Passengers press buttons at any time, while cars are moving. Three sensible models, from simplest to most scalable:</p><ol><li><strong>Single-threaded event loop (recommended to start):</strong> all requests go onto a queue. One scheduler thread drains the queue and calls tick() on every car. No locks needed, and fully deterministic.</li><li><strong>Thread per car:</strong> each car runs its own loop, and synchronized methods protect its stop sets (as in the code above). The dispatcher reads car state while cars mutate it, so we need snapshots or locking.</li><li><strong>Actor model :</strong> each car is an actor that owns its state and communicates by message. This is the natural fit once the system is distributed.</li></ol><p><strong>Key rule:</strong> the dispatcher reads a <em>snapshot</em> of each car’s state to compute cost. A slightly stale snapshot just means a marginally worse assignment, but it must never produce a corrupted one. Choosing slightly stale reads over heavy locking keeps the call path fast.</p><h3>7. Edge cases that separate good designs from great ones</h3><ul><li><strong>Call for the current floor:</strong> if a car is idle at floor 7 and someone presses UP at floor 7, open the doors immediately.</li><li><strong>Duplicate presses:</strong> deduplicate by (floor, direction), as shown above.</li><li><strong>Overload:</strong> if currentLoad &gt; capacity, hold the doors, sound a buzzer, and <strong>ignore new hall-call stops</strong> until load drops. A full car should skip hall calls and let other cars take them.</li><li><strong>Door obstruction:</strong> a sensor re-opens the doors and resets the timer. Cap the retries, then close slowly with an alarm.</li><li><strong>Starvation:</strong> with LOOK, no floor waits more than about two sweeps, but also track <em>max wait time</em> and boost the priority of calls older than a threshold.</li><li><strong>Car fault mid-assignment:</strong> all hall calls assigned to a failed car must be <strong>reassigned</strong> to other cars immediately. Car calls already inside the car are the passengers’ problem, so open doors at the nearest floor.</li><li><strong>Fire/emergency mode:</strong> override everything. Recall all cars to the ground floor, open doors, and ignore calls. Safety logic must sit <strong>above</strong> the dispatcher in the hierarchy.</li><li><strong>Maintenance mode:</strong> a car in maintenance accepts no hall calls and is excluded by the dispatcher filter.</li><li><strong>Invalid floors:</strong> validate requests against [0, N-1]</li></ul><h3>8. Scaling the architecture</h3><p>For a single building, everything above runs in one process. In a real deployment (or a smart-building platform managing many buildings), the architecture looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_m_SvynqO4q-VUvoPdAxpQ.png" /></figure><p>Design principles worth stating explicitly:</p><ul><li><strong>Two-tier control.</strong> The group controller makes <em>optimization</em> decisions. Each car controller owns <em>safety and motion</em>. If the group controller dies, cars <strong>degrade gracefully to autonomous mode</strong> (serve their own car calls, answer the nearest hall calls) rather than stopping.</li><li><strong>Safety is never distributed.</strong> Door interlocks, overspeed, and emergency braking live in dedicated hardware or firmware on the car, not in software that depends on a network.</li><li><strong>Heartbeats and watchdogs</strong> between the group controller and cars. A missed heartbeat marks a car unavailable and triggers hall-call reassignment.</li><li><strong>Event-driven telemetry.</strong> Every arrival, door cycle, fault, and call is published to a stream. This feeds dashboards, wait-time metrics, and <strong>predictive maintenance</strong> (door motors that take progressively longer to close are about to fail).</li><li><strong>Persistence.</strong> Persist pending hall calls so a controller restart doesn’t silently drop waiting passengers. Car state can be rebuilt from the cars themselves on reconnect.</li></ul><h3>9. Testing and simulation</h3><p>Elevator logic is nearly impossible to validate by hand, so build a <strong>simulator</strong> early:</p><ol><li><strong>Deterministic tick clock</strong> (as in our tick() design), so tests are fast and reproducible.</li><li><strong>Traffic generators:</strong> replay realistic scenarios such as morning up-peak, lunch, random uniform, and a “burst of 50 people at floor 0”</li><li><strong>Metrics harness:</strong> record average wait, max wait, average ride time, number of stops per trip, and total distance traveled.</li><li><strong>Strategy comparison:</strong> run the <em>same</em> traffic against FCFS, nearest-car, cost-based, and destination dispatch, then compare numbers instead of arguing opinions.</li><li><strong>Invariant tests:</strong> a car never moves with doors open, a car never exceeds its floor range, and every hall call is eventually served (no starvation.)</li><li><strong>Fault injection:</strong> kill a car mid-run and verify its hall calls are reassigned within N ticks</li></ol><p>If we can say in an interview, “I’d compare dispatch strategies in a simulator using wait-time percentiles,” we are signaling real engineering maturity.</p><h3>10. Advanced: smarter dispatching</h3><h4>10.1 Reassignment (dynamic dispatch)</h4><p>The assignment made at call time is only the best guess <em>at that moment</em>. Conditions change: a car gets a long chain of car calls, or a closer car frees up. Advanced systems <strong>re-evaluate pending hall calls every few seconds</strong> and move them to a better car if the ETA improves meaningfully (with hysteresis so calls don’t flip-flop).</p><pre>void reevaluate(HallCall call) {<br>    ElevatorCar current = call.assignedCar();<br>    ElevatorCar best    = strategy.select(cars, call);<br><br>    int currentCost = cost(current, call);   // recomputed with latest state<br>    int bestCost    = cost(best, call);<br><br>    boolean worthMoving = best != current<br>            &amp;&amp; bestCost + SWITCH_THRESHOLD &lt; currentCost;   // e.g. save at least 5 ticks<br><br>    if (worthMoving) {<br>        current.unassignHallCall(call);   // remove from A&#39;s stops<br>        best.assignHallCall(call);        // add to B&#39;s stops<br>    }<br>}</pre><h4>10.2 Destination dispatch</h4><p>Instead of UP/DOWN buttons, passengers enter their <strong>destination in the lobby</strong> (keypad or app), and the system tells them <em>which car</em> to take (“Go to car C”).</p><p>Why this is powerful: the system knows every passenger’s destination before boarding, so it can <strong>group passengers going to nearby floors into the same car.</strong> The result is fewer stops per trip and noticeably higher throughput, which is why modern skyscrapers use it.</p><p>The trade-offs:</p><ul><li>A passenger can’t change their mind inside the car, and the car has no “destination buttons” to fix mistakes.</li><li>Requires a lobby UI, and groups arriving together may be split across cars.</li><li>Harder to model: a <em>request</em> becomes (origin, destination) rather than (floor, direction), which changes the cost function to optimize <strong>group trip time.</strong></li></ul><h4>10.3 Traffic-aware modes and Predictive and learning-based control</h4><p>Building traffic has strong daily patterns, and good controllers detect and adapt to them:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vZ7HybJIhJ8nB-nStOkUvA.png" /></figure><p><strong>Idle-car parking</strong> is an underrated feature. Where idle cars wait matters. Spreading them evenly (or concentrating at the lobby during up-peak) can cut average wait time significantly without moving a single passenger faster.</p><p>With historical call data (time, floor, direction), a model can predict <em>where demand will appear next</em> and pre-position cars. <strong><em>Reinforcement-learning</em></strong> dispatchers have shown wait-time reductions over hand-tuned heuristics in research. The practical path is to <strong>train and evaluate offline in a simulator</strong>, deploy behind a feature flag, and fall back to the cost-based strategy on any anomaly.</p><h4>10.4 Zoning, express cars, and sky lobbies</h4><ul><li><strong>Zoned groups:</strong> in tall buildings, split elevators into banks (low-rise 1–20, high-rise 21–40) so each car makes fewer stops</li><li><strong>Express cars:</strong> skip the lower floors entirely for high-rise destinations</li><li><strong>Sky lobbies:</strong> passengers transfer at a mid-building lobby. This is a <em>building-level</em> design decision, but the software must model cars with limited floor ranges, so make “floors served” a property of each car.</li></ul><h4>10.5 Optimization objectives</h4><p>“Minimize wait time” hides a choice. Common objectives:</p><ul><li><strong>Average waiting time:</strong> efficient but can neglect outliers</li><li><strong>Maximum waiting time:</strong> fairness-focused</li><li><strong>Sum of squared waiting times:</strong> punishes long waits disproportionately, a popular compromise</li><li><strong>Energy use:</strong> fewer starts and stops, regenerative braking, parking cars when idle</li></ul><p>Production systems often use a <strong>weighted cost function</strong> combining these, tuned per building.</p><h3>EPILOGUE</h3><p>The multi-elevator problem teaches a clean progression:</p><ol><li><strong>Model the domain</strong> by separating hall calls from car calls, and cars from the dispatcher.</li><li><strong>Make state explicit</strong> with a state machine, so safety rules become guards.</li><li><strong>Pick the right single-car algorithm</strong>: LOOK beats FCFS and SSTF.</li><li><strong>Solve assignment with cost, not distance</strong>, since direction, stops, and load all matter.</li><li><strong>Keep it pluggable</strong> with the Strategy pattern, so algorithms are swappable and testable.</li><li><strong>Design for failure</strong> with reassignment, graceful degradation, and safety above scheduling.</li><li><strong>Upgrade deliberately</strong>: reassignment, traffic modes, destination dispatch, and prediction each earn their place only when a measured wait-time problem justifies them.</li><li><strong>Prove it with a simulator</strong>, because intuition about elevators is famously wrong.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*737UKEwrYbiuU13g8plKFg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4f95ffb21527" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[System Design Part II — Designing a Rate Limiter]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/system-design-part-ii-designing-a-rate-limiter-855306280ab3?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/855306280ab3</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Sat, 31 Oct 2026 13:39:00 GMT</pubDate>
            <atom:updated>2026-10-31T13:39:00.614Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><strong>“The quality of a system is defined by our ability to change it.”</strong> — <em>Dave Farley</em></blockquote><h3>PROLOGUE</h3><blockquote>Every public API eventually meets the same three visitors: the well-behaved client, the buggy client stuck in a retry loop, and the malicious one.</blockquote><p>A rate limiter is how we treat all three fairly without letting any of them take the system down. It’s also one of the best system design questions because the naive version takes ten lines and the production version touches algorithms, distributed state, atomicity, and failure modes.</p><h3>1. What are we building?</h3><p>A component that decides, for each incoming request, whether to <strong>allow</strong> it or <strong>reject</strong> it (typically with HTTP 429 Too Many Requests) based on how many requests the caller has recently made.</p><h4>Why do we need one?</h4><ol><li><strong>Protect availability</strong>: stop one noisy client from starving everyone else</li><li><strong>Control cost</strong>: cap usage of expensive downstream calls (DB, third-party APIs, ML inference)</li><li><strong>Security</strong>: slow down brute-force logins, credential stuffing, scraping</li><li><strong>Fairness and monetization</strong>: enforce plan tiers (free: 100/min, paid: 10,000/min)</li></ol><h4>Functional requirements</h4><ol><li>Limit requests by a key: user ID, API key, IP address, or a combination</li><li>Support different limits per endpoint and per plan</li><li>Return clear feedback to the client (remaining quota, when to retry)</li></ol><h4>Non-functional requirements</h4><ol><li><strong>Low latency</strong>: the check sits on every request, so it should add only a millisecond or two</li><li><strong>Highly accurate</strong> (within reason): not letting through 2x the limit</li><li><strong>Distributed</strong>: works across many app servers</li><li><strong>Fault tolerant</strong>: if the limiter breaks, it must not take the whole API down with it</li></ol><p>That last point shapes a lot of later decisions. The rate limiter is a <em>protective</em> component, so it must never become the thing that causes the outage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VxfpBM3maLBvTh-hxrpG5w.jpeg" /></figure><h3>2. Where should it live?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cFjAhVNMm3mJjdC1AQHjPQ.png" /></figure><p>A practical pattern is <strong>layered limiting</strong>: coarse per-IP limits at the edge (cheap protection against floods), plus fine-grained per-user and per-endpoint limits closer to the application, where plan and business context are known.</p><p>In a Java backend this might be a servlet Filter or Spring HandlerInterceptor that calls a shared limiter library or service.</p><h3>3. The algorithms</h3><p>This is the core of the problem. There are five classic approaches, each with different trade-offs.</p><h4><strong>Algorithm A: Fixed Window Counter</strong></h4><p>Divide time into fixed windows (say, one minute). Keep a counter per key per window. Increment on each request. Reject if the counter exceeds the limit.</p><pre>Window 12:00:00 – 12:00:59 → counter for user42<br>Window 12:01:00 – 12:01:59 → new counter</pre><p><strong>Pros:</strong> trivial to implement, tiny memory (one integer per key), O(1).</p><p><strong>Cons: the boundary burst problem.</strong> With a limit of 100/min, a client sends 100 requests at 12:00:59 and another 100 at 12:01:00. That’s 200 requests in two seconds, yet both windows are individually within the limit. The system sees up to <strong>2x the intended rate</strong> around the boundary.</p><h4><strong>Algorithm B: Sliding Window Log</strong></h4><p>Store the <strong>timestamp of every request</strong> per key (typically in a sorted structure). On each request:</p><ol><li>Remove timestamps older than now - windowSize</li><li>If the remaining count is below the limit, allow and add the new timestamp</li><li>Otherwise reject</li></ol><p><strong>Pros:</strong> perfectly accurate, with no boundary problem.</p><p><strong>Cons:</strong> Memory-hungry. For a limit of 10,000/min, we store up to 10,000 timestamps per user. At millions of users, this gets expensive fast. Rejected requests may or may not be stored, depending on whether we want to penalize hammering.</p><h4>Algorithm C: Sliding Window Counter</h4><p>Take algorithm 1, fixed window lengths, but now keep counters for the current and previous fixed windows, then estimate the rolling count with a weighted sum:</p><pre>estimated = currentWindowCount + previousWindowCount × overlapRatio</pre><p>If we’re 30% into the current minute, the previous window’s overlap with our rolling window is 70%:</p><pre>estimated = current + previous × 0.70</pre><p><strong>Pros:</strong> nearly the accuracy of the log approach at the memory cost of the fixed window (two integers per key). It smooths the boundary problem.</p><p><strong>Cons:</strong> it’s an approximation, assuming requests in the previous window were evenly distributed. In practice, studies of real traffic show the error is very small, and this is why many large systems use it.</p><h4>Algorithm D: Token Bucket</h4><p>Picture a bucket that holds up to capacity tokens. Tokens are added at a steady refillRate (say, 10 per second). Each request consumes one token. No token means reject.</p><pre>capacity = 100, refillRate = 10 tokens/sec<br><br>// Idle for a while → bucket fills to 100<br>// Burst of 100 requests → all allowed instantly<br>// Then sustained rate limited to 10/sec</pre><p><strong>Pros:</strong></p><ol><li><strong>Allows controlled bursts</strong>, which matches real client behavior (page loads fire many calls at once)</li><li>Only two values per key: tokens and lastRefillTime</li><li>Used by Amazon and Stripe in their public APIs</li></ol><p><strong>Cons:</strong> two parameters (capacity and refill rate) to tune, and they interact, so reasoning about “what limit did we actually set?” is slightly less intuitive.</p><p><strong>The lazy refill trick:</strong> we don’t need a background thread adding tokens. Compute them on demand:</p><pre>public final class TokenBucket {<br>    private final long capacity;<br>    private final double refillPerSecond;<br>    private double tokens;<br>    private long lastRefillNanos;<br><br>    public TokenBucket(long capacity, double refillPerSecond) {<br>        this.capacity = capacity;<br>        this.refillPerSecond = refillPerSecond;<br>        this.tokens = capacity;<br>        this.lastRefillNanos = System.nanoTime();<br>    }<br><br>    public synchronized boolean tryAcquire() {<br>        long now = System.nanoTime();<br>        double elapsedSeconds = (now - lastRefillNanos) / 1_000_000_000.0;<br>        tokens = Math.min(capacity, tokens + elapsedSeconds * refillPerSecond);<br>        lastRefillNanos = now;<br><br>        if (tokens &gt;= 1) {<br>            tokens -= 1;<br>            return true;<br>        }<br>        return false;<br>    }<br>}</pre><h4><strong>Algorithm E: Leaky Bucket</strong></h4><p>Requests enter a queue (the bucket) and are processed at a <strong>constant outflow rate</strong>. If the queue is full, new requests are dropped.</p><p><strong>Pros:</strong> produces a perfectly smooth output rate, which is ideal when protecting a downstream system that can’t tolerate bursts.</p><p><strong>Cons:</strong> bursts fill the queue and add latency to legitimate requests. Old requests can starve new ones. It’s more of a traffic <em>shaper</em> than a pure limiter.</p><h4>Comparison between all 5 Algorithms</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kytywCGLjdVThf8IhsjzsA.png" /></figure><blockquote><strong>A reasonable default is Token Bucket or Sliding Window Counter.</strong></blockquote><h3>4. The basic design: single server, in-memory</h3><p>Start simple.</p><p>One app server, with a ConcurrentHashMap&lt;String, TokenBucket&gt; keyed by client ID.</p><p>Request ─► Filter ─► map.computeIfAbsent(clientId) ─► bucket.tryAcquire()<br> ├─ true → continue<br> └─ false → 429</p><p>This works fine for a single instance. Two problems appear immediately:</p><ol><li><strong>Memory leak:</strong> the map grows forever as new client IDs arrive. We need eviction of idle entries (Caffeine cache with expireAfterAccess is a good fit).</li><li><strong>It doesn’t survive scale-out.</strong> Run three app servers behind a load balancer, and each has its own counters. A client with a limit of 100/min can effectively make 300/min, depending on how the load balancer routes it.</li></ol><p>Which brings us to the real problem.</p><h3>5. Going distributed: shared state in Redis</h3><p>The standard answer is to move counters to a <strong>shared, fast store</strong>. Redis is the usual choice: in-memory, sub-millisecond, atomic operations, and built-in key expiry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yhBy19bvm7DCnFTEwqxLKQ.png" /></figure><h4>The naive implementation and its race condition</h4><p>A first attempt at a fixed window in Redis:</p><pre>count = GET user42:12:00 // this notation mean user42 came at 12:00 time block<br>if count &lt; limit:<br>    INCR user42:12:00<br>    allow<br>else:<br>    reject</pre><p>This has a classic <strong>check-then-act race</strong>. Two servers both GET a count of 99, both see it’s below 100, and both INCR, landing at 101. Under high concurrency the limit leaks.</p><h4>Fix 1: Use atomic operations (Only for fixed window counter — Algorithm 3A)</h4><p>For a fixed window counter, INCR is atomic and returns the new value. Increment first, then compare:</p><p>Here user42:12:00 is the Redis key: user ID plus the minute-window. Each new minute gets a fresh key.</p><pre>count = INCR user42:12:00 // this notation mean user42 came at 12:00 time block<br><br>// If user came for the first time, that is count 1, <br>// expire that time block after 60 seconds<br>if count == 1: EXPIRE user42:12:00 60<br><br>// then comparison logic<br>if count &gt; limit: <br>    reject <br>else <br>    allow</pre><p>Putting it together with an example</p><blockquote>Limit: 3 requests per minute for an user</blockquote><blockquote>12:00:05 request 1 → INCR user42:12:00 → 1 → first one, so EXPIRE 60 → allow<br>12:00:20 request 2 → INCR user42:12:00 → 2 → allow<br>12:00:40 request 3 → INCR user42:12:00 → 3 → allow<br>12:00:50 request 4 → INCR user42:12:00 → 4 → 4 &gt; 3 → reject (429)<br>12:01:05 (key already auto-deleted by Redis)<br>12:01:05 request 5 → INCR user42:12:01 → 1 → new window, new key → allow</blockquote><p>(Even INCR followed by EXPIRE can leave a key without a TTL if the process dies between them, so prefer a Lua script or SET key 0 EX 60 NX first.)</p><h4>Fix 2: Lua scripts for multi-step logic (For sliding window and token counter — Algorithm 3B-3E)</h4><p>For token bucket or sliding window, the logic needs a read, a computation, and a write. Wrap it in a <strong>Lua script</strong>, which Redis executes atomically on the server with no interleaving from other clients.</p><pre>-- KEYS[1] = bucket key<br>-- ARGV[1] = capacity, ARGV[2] = refill/sec, ARGV[3] = now (ms), ARGV[4] = cost<br>local data = redis.call(&#39;HMGET&#39;, KEYS[1], &#39;tokens&#39;, &#39;ts&#39;)<br>local tokens = tonumber(data[1]) or tonumber(ARGV[1])<br>local ts = tonumber(data[2]) or tonumber(ARGV[3])<br><br>local elapsed = math.max(0, tonumber(ARGV[3]) - ts) / 1000<br>tokens = math.min(tonumber(ARGV[1]), tokens + elapsed * tonumber(ARGV[2]))<br><br>local allowed = 0<br>if tokens &gt;= tonumber(ARGV[4]) then<br>  tokens = tokens - tonumber(ARGV[4])<br>  allowed = 1<br>end<br><br>redis.call(&#39;HMSET&#39;, KEYS[1], &#39;tokens&#39;, tokens, &#39;ts&#39;, ARGV[3])<br>redis.call(&#39;PEXPIRE&#39;, KEYS[1], math.ceil(tonumber(ARGV[1]) / tonumber(ARGV[2]) * 1000) * 2)<br>return { allowed, math.floor(tokens) }</pre><p>From Java, load it once with SCRIPT LOAD and call it via EVALSHA (Spring Data Redis’s RedisScript, or Redisson’s built-in RRateLimiter, handle this).</p><p><strong>Clock caveat:</strong> pass now from a consistent source. If each app server uses its own clock, skew between servers corrupts refill math. Either use Redis TIME inside the script or keep servers NTP-synced.</p><blockquote><strong>Side-note On Lua :-</strong> Lua is a lightweight, high-level, multi-paradigm scripting language designed primarily for embedded use in applications. Lua is heavily used in Redis as its primary embedded server-side scripting language.</blockquote><h3>6. Rule configuration</h3><p>Hardcoded limits don’t survive contract with product requirements. Model the rules as data:</p><pre>rules:<br>  - id: login-attempts<br>    match: { endpoint: &quot;POST /auth/login&quot; }<br>    key: ip<br>    limit: 5<br>    window: 60s<br>    algorithm: sliding_window_counter<br><br>  - id: api-free-tier<br>    match: { plan: free }<br>    key: apiKey<br>    capacity: 100<br>    refillPerSecond: 1.67<br>    algorithm: token_bucket<br><br>  - id: api-paid-tier<br>    match: { plan: paid }<br>    key: apiKey<br>    capacity: 5000<br>    refillPerSecond: 83<br>    algorithm: token_bucket</pre><p><strong>Design points:</strong></p><ol><li><strong>Store rules in a config service or DB</strong>, cached locally on each app server and refreshed periodically (or pushed on change). Limits then change without a deploy.</li><li><strong>Rule resolution order:</strong> most specific match wins (endpoint + plan beats plan alone).</li><li><strong>Composite keys:</strong> rate:{ruleId}:{keyType}:{keyValue}, for example rate:api-free-tier:apiKey:ak_9f2.</li><li><strong>Multiple limits at once:</strong> a request can be checked against several rules (per-second burst limit <em>and</em> per-day quota). Reject if any fails.</li></ol><h3><strong>7. Telling clients what happened</strong></h3><p>A good rate limiter is communicative. Return 429 with headers so well-behaved clients can self-regulate:</p><pre>HTTP/1.1 429 Too Many Requests<br>Retry-After: 12<br>RateLimit-Limit: 100<br>RateLimit-Remaining: 0<br>RateLimit-Reset: 12<br>Content-Type: application/json<br><br>{ &quot;error&quot;: &quot;rate_limited&quot;, &quot;message&quot;: &quot;Retry after 12 seconds.&quot; }</pre><p>Also send RateLimit-Remaining on <strong>successful</strong> responses, so clients can slow down <em>before</em> hitting the wall. On the client side, the matching behavior is <strong>exponential backoff with jitter</strong>, so a thousand throttled clients don’t all retry at the same instant and recreate the spike.</p><h3>8. Scaling the limiter itself</h3><p>At large scale, the Redis layer becomes the next bottleneck.</p><h4>Shard the counters</h4><p>Partition keys across a Redis Cluster using hash slots. Because every request for one client touches only that client’s key, <strong>there are no cross-key operations</strong> and sharding is straightforward. Keep each Lua script operating on a single key so it stays within one slot.</p><h4>Reduce the round trip</h4><p>The check adds one network hop per request. Ways to trim it:</p><ul><li><strong>Co-locate</strong> Redis close to app servers (same region and AZ)</li><li><strong>Connection pooling and pipelining</strong> to avoid connection setup cost</li><li><strong>Local pre-filtering:</strong> a tiny in-process check rejects obviously abusive clients (already blocked) without touching Redis</li></ul><h4>Approximate counting with local batching</h4><p>For extremely high-volume systems, each app server keeps a <strong>local counter</strong> and syncs to Redis every N requests or every few milliseconds, borrowing a chunk of quota at a time (similar to the range-allocation idea in ID generators).</p><ul><li><strong>Pros:</strong> drastically fewer Redis calls</li><li><strong>Cons:</strong> the limit becomes approximate. With 50 servers each holding a local allowance, a client can briefly exceed the global limit. This is a deliberate trade of <strong>accuracy for latency and throughput</strong>, and it’s acceptable when the limit exists for protection rather than billing.</li></ul><h3>9. Failure modes: fail open or fail closed?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*koJpgySmgejwIcFkRD3PKA.png" /></figure><p>Most systems <strong>fail open by default</strong> with alerting, since a broken limiter shouldn’t become an outage. But apply fail-closed selectively for security-sensitive endpoints. Mixed policies per rule are completely reasonable.</p><p>Some of the supporting measures are :</p><ol><li><strong>Tight timeouts</strong> (a few milliseconds) on the Redis call, so a slow limiter doesn’t add latency to every request.</li><li><strong>Circuit breaker</strong> (Resilience4j in Java) to stop calling Redis during an outage and degrade to a <strong>local in-memory limiter</strong> as a fallback. That fallback is imprecise but still gives some protection.</li><li><strong>Redis replication with failover</strong> (Sentinel or Cluster). Remember that async replication means a failover can lose a few recent counter updates, which is usually acceptable.</li></ol><h3>10. Multi-region and global limits</h3><p>If we run in several regions, there’s a question: is the limit <strong>per region</strong> or <strong>global</strong>? Pick based on how strict the guarantee must be.</p><ul><li><strong>Per-region limits:</strong> simplest. Each region has its own Redis, with no cross-region latency. A client can use up to limit × regions in total, which is often fine, especially if users are geo-routed to one region anyway.</li><li><strong>Global limits:</strong> need cross-region synchronization (expensive and slow) or a <strong>split-quota</strong> approach: divide the global limit among regions (say 40/40/20 based on traffic share) and rebalance periodically, with each region enforcing its share locally.</li><li><strong>Eventually consistent sync:</strong> regions asynchronously exchange usage counts and adjust. Accurate enough for most needs without blocking the request path.</li></ul><h3>11. Advanced concerns</h3><h4>Identifying the client</h4><ul><li><strong>IP address:</strong> easy but flawed. Many users share an IP (corporate NAT, mobile carriers), and attackers rotate IPs. Behind proxies, read the real client IP from X-Forwarded-For <strong>only from trusted proxies</strong>, since the header is spoofable.</li><li><strong>API key / user ID:</strong> accurate for authenticated traffic. Apply <em>after</em> authentication.</li><li><strong>Layered approach:</strong> IP-based limit pre-auth (to protect the login endpoint itself), user-based limit post-auth.</li></ul><h4>Request cost weighting</h4><p>Not all requests are equal. A search query may cost 10x a simple GET. Let rules <strong>consume variable tokens</strong> (the cost argument in the Lua script above) so limits track actual resource use.</p><h4>Concurrency limits</h4><p>Rate (requests per second) differs from concurrency (requests in flight). A slow endpoint can be overwhelmed by few requests that each hold resources for a long time. Add a <strong>concurrent request limiter</strong> (a semaphore with a TTL) alongside the rate limit.</p><h4>Load shedding and priority</h4><p>Under extreme overload, rate limiting per client isn’t enough. <strong>Load shedding</strong> drops lowest-priority traffic first (for example, shed batch/background traffic before interactive traffic) to keep critical paths alive.</p><h4>Dynamic and adaptive limits</h4><p>Limits can respond to system health. If downstream latency climbs or CPU saturates, <strong>tighten limits automatically</strong>, then relax as the system recovers (similar to TCP congestion control, or Netflix’s adaptive concurrency limits).</p><h4>Allowlists and blocklists</h4><p>Trusted internal services bypass limits. Confirmed abusers get blocked outright, ideally at the edge or WAF, so they never consume our limiter’s resources.</p><h3>12. Observability</h3><p>A rate limiter we can’t see into is a source of mystery support tickets. Track:</p><ul><li><strong>Allowed vs rejected counts</strong>, per rule and per endpoint</li><li><strong>Top throttled keys</strong>, to spot abusers and also to find legitimate customers who need a higher plan</li><li><strong>Limiter latency</strong> (p50/p95/p99) and <strong>Redis error rate</strong></li><li><strong>Fail-open events</strong>, which should page someone</li><li><strong>Rule hit distribution</strong>, to find rules that never trigger (too loose) or trigger constantly (too tight)</li></ul><p>A useful rollout technique is <strong>shadow mode (dry run)</strong>: evaluate a new rule and log what <em>would</em> have been rejected, without actually rejecting. Review the data, tune the thresholds, then enforce. This avoids the classic launch-day incident of accidentally throttling real customers.</p><h3><strong>13. The Full Architecture Diagram</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nBUrAILHzsk8ljI9o0-IxA.png" /></figure><h3>EPILOGUE</h3><p>The rate limiter shows how a small idea expands under real constraints:</p><ol><li><strong>Pick the right algorithm</strong> for the traffic: token bucket for bursty public APIs, sliding window counter for smooth general limits.</li><li><strong>Make the check atomic</strong>, because race conditions silently destroy accuracy.</li><li><strong>Share state</strong> through Redis, and shard it when volume demands.</li><li><strong>Treat failure as a design input</strong>: decide fail-open or fail-closed per rule, deliberately.</li><li><strong>Keep it off the hot path’s critical risk</strong>: tight timeouts, circuit breakers, local fallbacks.</li><li><strong>Make it observable and tuneable</strong>, with shadow mode and per-rule metrics.</li><li><strong>Communicate with clients</strong> through 429, Retry-After, and quota headers.</li></ol><p>As with most system design, the winning answer isn’t the most elaborate one. A single Redis with token-bucket Lua scripts and a fail-open policy handles an enormous amount of real traffic.</p><p>Here is an trade-off summary before concluding!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WsdVpKrO9PhPtGpyqsB88A.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=855306280ab3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[System Design Part 1 — Designing a URL Shortener]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/system-design-part-1-designing-a-url-shortener-2b4d08dbe281?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/2b4d08dbe281</guid>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Sat, 31 Oct 2026 11:03:44 GMT</pubDate>
            <atom:updated>2026-10-31T11:05:19.086Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><strong>“Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.”</strong> — <em>Antoine de Saint-Exupéry</em></blockquote><h3>PROLOGUE</h3><p>Every system design interview has one question that looks trivial and turns out to be deep: <em>“Design a URL shortener like bit.ly.”</em> It starts as a hashmap and ends up touching ID generation, caching, sharding, analytics, and abuse prevention.</p><h3>1. What are we building?</h3><p>A service that does two things:</p><ol><li><strong>Shorten</strong>: take https://example.com/products/very/long/path?utm=abc and return <a href="https://sho.rt/aZ3kP9x">https://sho.rt/aZ3kP9x</a></li><li><strong>Redirect</strong>: when someone opens https://sho.rt/aZ3kP9x, send them to the original URL</li></ol><h4>Functional requirements</h4><ol><li>Generate a short, unique link for a long URL</li><li>Redirect short link to original URL</li><li>Optional custom alias (sho.rt/my-sale)</li><li>Optional expiry time</li><li>Basic click analytics</li></ol><h4>Non-functional requirements</h4><ol><li><strong>Highly available</strong>: a dead redirect means a broken link on someone’s printed poster</li><li><strong>Low latency</strong>: redirects should feel instant (p99 under ~50 ms)</li><li><strong>Read-heavy</strong>: far more redirects than creations</li><li><strong>Non-guessable</strong> (nice to have): people shouldn’t be able to enumerate all links</li><li><strong>Durable</strong>: a link created today should work years from now</li></ol><blockquote>Stating these up front matters. Almost every later decision (cache, DB, ID scheme) traces back to “this is a read-heavy, availability-first system.”</blockquote><h3>2. Before we develop — some GUESSTIMATES</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mbmKxb1NRXtb-qR2rMTteA.png" /></figure><p><strong>So guesstimate summarization:</strong></p><ol><li>Write load is tiny, so ID generation doesn’t need to be exotic at first.</li><li>Read load is moderate but spiky, so caching is the main lever.</li><li>3 TB fits on one big machine, but the availability requirement pushes us to replicate and shard anyway.</li></ol><p><strong>How long should the short code be?</strong></p><p>Using Base62 (a-z A-Z 0-9):</p><ul><li>6 chars: 6²⁶ ≈ <strong>56 billion</strong></li><li>7 chars: 6²⁷ ≈ <strong>3.5 trillion</strong></li></ul><p>For 6 Billion URLs, 7 characters gives plenty of headroom, and 6 would also work. We’ll go with <strong>7</strong>.</p><h3>3. Lets dive into Basic Design [LEVEL 1]</h3><p>Let’s start simple with :-</p><blockquote>Client ──► App Server ──► Database</blockquote><pre>POST /api/v1/urls<br>{<br>  &quot;longUrl&quot;: &quot;https://example.com/a/very/long/path&quot;,<br>  &quot;customAlias&quot;: &quot;my-sale&quot;,        // optional<br>  &quot;expiresAt&quot;: &quot;2027-01-01T00:00:00Z&quot;  // optional<br>}<br>→ HTTP status code 201, with body<br>{ &quot;shortUrl&quot;: &quot;https://sho.rt/aZ3kP9x&quot; }<br><br><br><br>GET /{shortCode}<br>→ HTTP status code 302 , with body <br>{ &quot;location&quot;: &quot;https://example.com/a/very/long/path&quot; }</pre><p>The database also lets keep very straightforward , because the access pattern is almost purely <strong>key to value lookup by </strong><strong>short_code</strong>. There are no joins and no complex queries!</p><pre>CREATE TABLE url_mapping (<br>  short_code   VARCHAR(10)  PRIMARY KEY,<br>  long_url     TEXT         NOT NULL,<br>  user_id      BIGINT       NULL,<br>  created_at   TIMESTAMP    NOT NULL,<br>  expires_at   TIMESTAMP    NULL<br>);</pre><p>Revisiting the access pattern, billions of rows, key-value lookups, minimal relations, and heavy reads.</p><ul><li><strong>NoSQL (Cassandra, DynamoDB)</strong> is a natural fit. It scales horizontally and handles simple key lookups at huge scale. Partition key: short_code.</li><li><strong>SQL (MySQL/PostgreSQL)</strong> works fine at this scale with sharding and read replicas.</li></ul><p>For custom aliases and uniqueness guarantees, a conditional write (INSERT ... IF NOT EXISTS in Cassandra, or a unique constraint in SQL) lets the database enforce uniqueness for us.</p><h3>4. The heart of the problem: generating the short code</h3><p><strong>Approach A :-</strong> Hash the long URL , simple enough , do a SHA-256 and take first 7 chars. Cons — Collision!</p><p><strong>Approach B :-</strong> What if we retry on collision? Fair enough, but at 6 billion estimated URLs, we might retry a lot! Also it costs a DB round-trip (both lookup + store)</p><p><strong>Approach C :-</strong> <strong><em>Counter + Base62 (recommended)</em></strong></p><p>Take a <strong>globally unique incrementing integer ID</strong> and encode it in Base62. Uniqueness is guaranteed by construction, so collisions are impossible.</p><pre>public final class Base62 {<br>    private static final String ALPHABET =<br>        &quot;0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;;<br><br>    public static String encode(long id) {<br>        if (id == 0) return &quot;0&quot;;<br>        StringBuilder sb = new StringBuilder();<br>        while (id &gt; 0) {<br>            sb.append(ALPHABET.charAt((int) (id % 62)));<br>            id /= 62;<br>        }<br>        return sb.reverse().toString();<br>    }<br>}</pre><p>encode(125) gives &quot;21&quot;, and encode(3_500_000_000_000L) is around 7 characters.</p><p><strong>The catch:</strong> sequential IDs give sequential codes (aaaaab, aaaaac, ...), which are guessable. Fix this by scrambling the ID with a reversible bijection (e.g. multiply by a large odd number modulo 62⁷, or use a Feistel cipher / format-preserving encryption) before encoding. We keep uniqueness and gain obscurity.</p><h3>5. Making ID generation scale [LEVEL 2]</h3><p>A single DB auto-increment is a single point of failure and a bottleneck. Here are the options, from simple to robust.</p><h4>Option 1: Range allocation (Key Range Service)</h4><p>A small coordination service (backed by ZooKeeper, etcd, or a DB row with optimistic locking) hands out <strong>ranges</strong> of IDs to app servers.</p><pre>Server 1 → gets range 1,000,000 – 1,999,999<br>Server 2 → gets range 2,000,000 – 2,999,999</pre><p>Each server then generates IDs locally from its range with no network call per request. When a range is exhausted, it asks for a new one.</p><ul><li><strong>Pros:</strong> very fast and simple to reason about</li><li><strong>Cons:</strong> if a server crashes, the unused part of its range is lost. That’s fine, since we have trillions of IDs to spare.</li></ul><h4>Option 2: Pre-generated Key Generation Service (KGS)</h4><p>A separate service pre-generates millions of unique random 7-char keys into a unused_keys table and moves them to used_keys when handed out. App servers fetch keys in batches and cache them in memory.</p><ul><li><strong>Pros:</strong> zero collision handling at request time, and keys are naturally random</li><li><strong>Cons:</strong> an extra component to run, and we need to handle crashed servers holding unused keys</li></ul><h4>Option 3: Snowflake-style IDs</h4><p>A 64-bit ID composed of timestamp | machine ID | sequence. No coordination is needed at runtime.</p><ul><li><strong>Pros:</strong> fully decentralized</li><li><strong>Cons:</strong> the IDs are large, so the Base62 code comes out at 10–11 characters rather than 7</li></ul><h3>6. API Redirect status code, which to use, 301 or 302?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ft5gbvZlbqUCvqUHWPOOgA.png" /></figure><p><strong>For most cases, we will use 302, also if analytics needed, we will use 302.</strong> If we want raw speed and the links never change, we will use 301. Most commercial shorteners use 302 (or 307) for this reason.</p><h3>7. Adding a cache [LEVEL 3]</h3><p>Since traffic follows a power law (a small number of links get most clicks), caching is hugely effective.</p><blockquote>Client ─► Load Balancer ─► App Servers ─► Redis Cache ─► (if missed) ─►Database</blockquote><p><strong>Read path (cache-aside):</strong></p><ol><li>Check Redis for short_code</li><li><strong>Hit:</strong> return the redirect immediately</li><li><strong>Miss:</strong> read from DB, populate Redis with a TTL (asynchronously), return the redirect</li></ol><p><strong>Details that matter:</strong></p><ul><li><strong>Eviction:</strong> LRU works well for this skewed traffic.</li><li><strong>TTL:</strong> set it no longer than the link’s own expires_at.</li><li><strong><em>Negative caching:</em></strong><em> cache “not found” results briefly. Otherwise a bot hammering nonexistent codes bypasses the cache and hits our DB every time. (important point in real life system)</em></li><li><strong>Hot keys:</strong> a viral link can overload a single Redis node. Mitigate with a small in-process local cache (e.g. Caffeine in Java) in front of Redis, or replicate hot keys.</li></ul><p>With a ~90%+ hit rate, our database sees only a fraction of the 4,000+ reads/sec.</p><h3><strong>8. Time for Overall Architecture</strong></h3><p><strong>Why split Write and Redirect services?</strong> Their load profiles are completely different (40 req/s versus 4,000+ req/s). Separating them lets us scale, deploy, and fail them independently. A bug in the creation flow should never break redirects.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c1Xntoot-Nzwx-YGOJnmWQ.png" /></figure><h3>9. Scaling the database: Sharding</h3><p>When one DB can’t hold or serve all the data, partition it.</p><ul><li><strong>Hash-based sharding on </strong><strong>short_code:</strong> even distribution, and lookups know exactly which shard to hit. This is the standard choice.</li><li><strong>Consistent hashing:</strong> adding or removing shards moves only a fraction of the keys, avoiding a massive reshuffle.</li><li><strong>Range-based sharding:</strong> simple, but sequential codes create hot shards, so avoid it unless codes are scrambled.</li></ul><p>Pair sharding with <strong>replication</strong> (typically 3 replicas per shard) for availability and read scaling.</p><h3>10. Analytics (Advanced)</h3><p>A redirect should never wait on analytics writes. Do it <strong>asynchronously</strong>:</p><ol><li>Redirect Service responds with the 302 immediately</li><li>It publishes a click event (short_code, timestamp, IP-derived geo, user-agent, referrer) to <strong>Kafka</strong></li><li>A consumer aggregates the events into an OLAP store (ClickHouse, Druid, or a data warehouse)</li><li>A dashboard queries pre-aggregated counts (clicks per day, per country, per device)</li></ol><p>If the analytics pipeline goes down, redirects keep working. Events queue in Kafka and catch up later. This is the principle of <strong>never putting a non-critical dependency on the critical path</strong>.</p><h3>11. Expiry and cleanup</h3><ul><li><strong>Lazy deletion:</strong> on read, if expires_at has passed, return 404/410 and optionally delete the row.</li><li><strong>Background job:</strong> a periodic sweeper removes expired rows in batches (avoid giant deletes that lock tables).</li><li><strong>Cassandra TTL / DynamoDB TTL</strong> can handle expiry natively.</li><li>Consider <strong>recycling</strong> expired codes only if we are running low. With a 7-char space, we usually don’t need to.</li></ul><h3>12. Security and abuse prevention</h3><p>A public URL shortener is a favourite tool for phishers, so treat this as a first-class concern.</p><ul><li><strong>Rate limiting</strong> per IP and per API key (token bucket in Redis) on the creation endpoint</li><li><strong>Malicious URL checks</strong> against Google Safe Browsing or similar, at creation time and periodically afterward</li><li><strong>Input validation:</strong> only allow http/https, enforce a max length, and block internal or private IP ranges to prevent <strong>SSRF</strong> if we ever fetch the URL for previews</li><li><strong>Non-enumerable codes</strong> via scrambling, so attackers can’t crawl all links</li><li><strong>Reserved words:</strong> block aliases like admin, api, login</li><li><strong>Interstitial warning page</strong> for flagged links instead of silently redirecting</li><li><strong>Authentication and authorization</strong> for link management (edit, delete, view stats)</li></ul><h3>13. Reliability and multi-region (Advanced)</h3><ul><li><strong>Multi-region deployment</strong> with geo-DNS so users hit the nearest region</li><li><strong>Redirect path is read-only</strong>, so it replicates easily across regions. Writes can go to a primary region, or each region can own its own ID range to avoid cross-region coordination.</li><li><strong>CDN caching</strong> of redirects (when using 301, or 302 with a short Cache-Control) offloads huge traffic for viral links</li><li><strong>Graceful degradation:</strong> if the DB is unreachable but the cache is alive, keep serving cached redirects</li><li><strong>Observability:</strong> track redirect latency (p50/p95/p99), cache hit ratio, 4xx/5xx rate, DB replication lag, and ID range exhaustion. Alert on cache hit ratio dropping, because it’s an early warning of trouble.</li></ul><h3>EPILOGUE</h3><p>A URL shortener teaches the full arc of system design in miniature:</p><ol><li><strong>Clarify requirements</strong> and estimate scale before drawing boxes.</li><li><strong>Start simple</strong>: one server, one table, one lookup.</li><li><strong>Solve the unique problem</strong> (ID generation) properly.</li><li><strong>Optimize for the dominant workload</strong> (reads) with caching.</li><li><strong>Scale out</strong> with sharding, replication, and service separation.</li><li><strong>Protect the critical path</strong> by moving analytics off it.</li><li><strong>Harden</strong> against abuse and failure.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*23V2mudVBeNiIWyc2xVndw.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2b4d08dbe281" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring Boot based complete Java REST API + CRUD Application]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/spring-boot-based-complete-java-rest-api-crud-application-21105e058bc1?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/21105e058bc1</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Sat, 31 Jan 2026 04:53:36 GMT</pubDate>
            <atom:updated>2026-01-31T04:53:36.695Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><strong>“Convention over Configuration”</strong>: This is a core philosophy that guides Spring Boot, significantly reducing the need for manual configuration (like XML), allowing developers to get started quickly.</blockquote><blockquote><strong>“Production-ready apps in a snap”</strong>: Highlights the speed and ease with which it allows developers to build robust, deployable applications.</blockquote><blockquote><strong>“Just Run”</strong>: Refers to how simple it is to run a Spring Boot application due to the embedded server and minimal setup requirements.</blockquote><p>We will use <strong>Spring Data JPA</strong> for database interaction, <strong>Spring Validation</strong> for validation handling, <strong>H2 Database</strong>, and integrate Maven for dependency management.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2c2iWIjONHjcWTm2TMLQyw.png" /></figure><h3>Step 1: Create a new Spring Boot application</h3><p>Generate a <strong>Spring Boot application</strong> using Spring Initializr:</p><blockquote><strong>Visit</strong>: <a href="https://start.spring.io/">https://start.spring.io/</a><strong><br>Project</strong>: Maven <br><strong>Spring Boot Version</strong>: 3.1.X (use latest compatible version)<br><strong>Dependencies</strong>: Spring Web, Spring Data JPA, H2 Database, Validation</blockquote><p>Download the project and extract it to IntelliJ.</p><h3>Step 2: Configure Maven Dependencies (pom.xml)</h3><p>Open the pom.xml file and verify that the relevant dependencies exist. Run mvn clean install to download all the dependencies.</p><pre>&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;<br>    xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;<br>    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;<br>    &lt;groupId&gt;com.example&lt;/groupId&gt;<br>    &lt;artifactId&gt;customer-management&lt;/artifactId&gt;<br>    &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;<br>    &lt;packaging&gt;jar&lt;/packaging&gt;<br>    &lt;name&gt;customer-management&lt;/name&gt;<br>    &lt;description&gt;Spring Boot application for customer management&lt;/description&gt;<br>    &lt;properties&gt;<br>        &lt;java.version&gt;21&lt;/java.version&gt;<br>    &lt;/properties&gt;<br><br>    &lt;dependencies&gt;<br>        &lt;!-- Spring boot starter for REST API --&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;<br>        &lt;/dependency&gt;<br>        <br>        &lt;!-- Spring Boot Starter JPA for database access --&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>            &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;<br>        &lt;/dependency&gt;<br>        <br>        &lt;!-- H2 Database --&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;com.h2database&lt;/groupId&gt;<br>            &lt;artifactId&gt;h2&lt;/artifactId&gt;<br>            &lt;scope&gt;runtime&lt;/scope&gt;<br>        &lt;/dependency&gt;<br>        <br>        &lt;!-- Validation --&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>            &lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt;<br>        &lt;/dependency&gt;<br><br>        &lt;!-- Lombok (for getter/setter annotations) --&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;<br>            &lt;artifactId&gt;lombok&lt;/artifactId&gt;<br>            &lt;optional&gt;true&lt;/optional&gt;<br>        &lt;/dependency&gt;<br><br>        &lt;!-- Spring Boot DevTools for development --&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>            &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;<br>            &lt;scope&gt;runtime&lt;/scope&gt;<br>        &lt;/dependency&gt;<br><br>        &lt;!-- Spring Boot Test --&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;<br>            &lt;scope&gt;test&lt;/scope&gt;<br>        &lt;/dependency&gt;<br>    &lt;/dependencies&gt;</pre><h3>Step 3: Configure application.properties</h3><p>Inside the src/main/resources folder, modify or create the application.properties file to specify database configuration.</p><pre># H2 Database configuration<br>spring.datasource.url=jdbc:h2:mem:testdb<br>spring.datasource.driverClassName=org.h2.Driver<br>spring.datasource.username=sa<br>spring.datasource.password=password<br>spring.jpa.database-platform=org.hibernate.dialect.H2Dialect<br>spring.h2.console.enabled=true<br>spring.h2.console.path=/h2-console<br><br># Show Hibernate SQL<br>spring.jpa.show-sql=true</pre><h3>Step 4: Create the Entity Layer (Customer.java)</h3><p>The entity represents a database table. Create a class Customer with necessary annotations.</p><pre>package com.example.customermanagement.entity;<br><br>import jakarta.persistence.*;<br>import jakarta.validation.constraints.Email;<br>import jakarta.validation.constraints.NotBlank;<br>import jakarta.validation.constraints.Size;<br>import lombok.AllArgsConstructor;<br>import lombok.Data;<br>import lombok.NoArgsConstructor;<br><br>@Entity<br>@Table(name = &quot;customers&quot;)<br>@Data<br>@NoArgsConstructor<br>@AllArgsConstructor<br>public class Customer {<br>    @Id<br>    @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-generate primary key<br>    private Long id;<br><br>    @NotBlank(message = &quot;Name is mandatory&quot;)<br>    @Size(min = 2, max = 50, message = &quot;Name must be between 2 and 50 characters&quot;)<br>    private String name;<br><br>    @NotBlank(message = &quot;Email is mandatory&quot;)<br>    @Email(message = &quot;Email should be valid&quot;)<br>    private String email;<br>}</pre><h3>Step 5: Create the Repository Layer (CustomerRepository.java)</h3><p>This layer interacts with the database. Create an interface that extends JpaRepository</p><p>The JpaRepository&lt;Customer, Long&gt; is a fundamental interface in Spring Data JPA that provides sophisticated CRUD (Create, Read, Update, Delete) functionality for the Customer entity, with Long representing the type of the entity&#39;s primary key. By extending this interface, we inherit methods for basic data operations as well as pagination and sorting capabilities without needing to implement them.</p><pre>package com.example.customermanagement.repository;<br><br>import com.example.customermanagement.entity.Customer;<br>import org.springframework.data.jpa.repository.JpaRepository;<br>import org.springframework.stereotype.Repository;<br><br>@Repository<br>public interface CustomerRepository extends JpaRepository&lt;Customer, Long&gt; {<br>}</pre><h3>Step 6: Create the Service Layer (CustomerService.java)</h3><pre>package com.example.customermanagement.service;<br><br>import com.example.customermanagement.entity.Customer;<br>import com.example.customermanagement.exception.CustomerNotFoundException;<br>import com.example.customermanagement.repository.CustomerRepository;<br>import org.springframework.beans.factory.annotation.Autowired;<br>import org.springframework.stereotype.Service;<br><br>import java.util.List;<br><br>@Service<br>public class CustomerService {<br><br>    @Autowired<br>    private CustomerRepository customerRepository;<br><br>    public List&lt;Customer&gt; getAllCustomers() {<br>        return customerRepository.findAll();<br>    }<br><br>    public Customer getCustomerById(Long id) {<br>        return customerRepository.findById(id)<br>                .orElseThrow(() -&gt; new CustomerNotFoundException(&quot;Customer not found with ID &quot; + id));<br>    }<br><br>    public Customer createCustomer(Customer customer) {<br>        return customerRepository.save(customer);<br>    }<br><br>    public Customer updateCustomer(Long id, Customer customerDetails) {<br>        Customer customer = getCustomerById(id);<br>        customer.setName(customerDetails.getName());<br>        customer.setEmail(customerDetails.getEmail());<br>        return customerRepository.save(customer);<br>    }<br><br>    public void deleteCustomer(Long id) {<br>        Customer customer = getCustomerById(id);<br>        customerRepository.delete(customer);<br>    }<br>}</pre><h3>Step 7: Create Custom Exception Handling (CustomerNotFoundException.java and GlobalExceptionHandler.java)</h3><p>@ControllerAdvice is a Spring annotation used for global exception handling, data binding, and model enhancement across multiple controllers. It acts as an interceptor for exceptions thrown by @RequestMapping methods, centralizing error handling logic to avoid repetitive code in individual controllers.</p><blockquote>It is commonly used for global @ExceptionHandler methods.</blockquote><p>In programming, particularly Java, super(message); is a constructor call used in a subclass to invoke a specific constructor of its immediate parent class, typically passing a string message to initialise the parent with error details or context.</p><pre>// CustomerNotFoundException.java<br>package com.example.customermanagement.exception;<br>public class CustomerNotFoundException extends RuntimeException {<br>    public CustomerNotFoundException(String message) {<br>        super(message);<br>    }<br>}<br><br><br><br>// GlobalExceptionHandler.java<br>package com.example.customermanagement.exception;<br><br>import org.springframework.http.HttpStatus;<br>import org.springframework.http.ResponseEntity;<br>import org.springframework.web.bind.annotation.ControllerAdvice;<br>import org.springframework.web.bind.annotation.ExceptionHandler;<br><br>@ControllerAdvice<br>public class GlobalExceptionHandler {<br><br>    @ExceptionHandler(CustomerNotFoundException.class)<br>    public ResponseEntity&lt;String&gt; handleCustomerNotFoundException(CustomerNotFoundException ex) {<br>        return new ResponseEntity&lt;&gt;(ex.getMessage(), HttpStatus.NOT_FOUND);<br>    }<br><br>    @ExceptionHandler(Exception.class)<br>    public ResponseEntity&lt;String&gt; handleGlobalException(Exception ex) {<br>        return new ResponseEntity&lt;&gt;(&quot;Internal server error: &quot; + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);<br>    }<br>}</pre><h3>Step 8: Create the Controller Layer (CustomerController.java)</h3><pre>package com.example.customermanagement.controller;<br><br>import com.example.customermanagement.entity.Customer;<br>import com.example.customermanagement.service.CustomerService;<br>import jakarta.validation.Valid;<br>import org.springframework.beans.factory.annotation.Autowired;<br>import org.springframework.http.ResponseEntity;<br>import org.springframework.web.bind.annotation.*;<br><br>import java.util.List;<br><br>@RestController<br>@RequestMapping(&quot;/api/customers&quot;)<br>public class CustomerController {<br><br>    @Autowired<br>    private CustomerService customerService;<br><br>    @GetMapping<br>    public List&lt;Customer&gt; getAllCustomers() {<br>        return customerService.getAllCustomers();<br>    }<br><br>    @GetMapping(&quot;/{id}&quot;)<br>    public Customer getCustomerById(@PathVariable Long id) {<br>        return customerService.getCustomerById(id);<br>    }<br><br>    @PostMapping<br>    public Customer createCustomer(@Valid @RequestBody Customer customer) {<br>        return customerService.createCustomer(customer);<br>    }<br><br>    @PutMapping(&quot;/{id}&quot;)<br>    public Customer updateCustomer(@PathVariable Long id, @Valid @RequestBody Customer customerDetails) {<br>        return customerService.updateCustomer(id, customerDetails);<br>    }<br><br>    @DeleteMapping(&quot;/{id}&quot;)<br>    public ResponseEntity&lt;String&gt; deleteCustomer(@PathVariable Long id) {<br>        customerService.deleteCustomer(id);<br>        return ResponseEntity.ok(&quot;Customer with ID &quot; + id + &quot; deleted successfully.&quot;);<br>    }<br>}</pre><h3>Step 9: Run the Application</h3><pre>mvn spring-boot:run</pre><p>Access the endpoints with tools like Postman</p><ul><li><strong>GET /api/customers</strong></li><li><strong>GET /api/customers/{id}</strong></li><li><strong>POST /api/customers</strong></li><li><strong>PUT /api/customers/{id}</strong></li><li><strong>DELETE /api/customers/{id}</strong></li></ul><p>That’s it! We’ve built a complete Spring Boot project with CRUD APIs, validation, and exception handling.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=21105e058bc1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sprint Boot Annotations]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/sprint-boot-annotations-cb46e5cecde3?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/cb46e5cecde3</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Mon, 26 Jan 2026 09:49:19 GMT</pubDate>
            <atom:updated>2026-01-26T09:49:19.756Z</atom:updated>
            <content:encoded><![CDATA[<h3>PROLOGUE</h3><p>Annotations in Spring Boot indeed revolutionize application development by providing a concise and elegant way to configure and manage applications, eliminating the need for lengthy XML-based configurations.</p><h3>Benefits of Annotations in Spring Boot</h3><ol><li><strong>Reduce XML-Based Configuration — </strong>By using annotations, developers can avoid creating and maintaining verbose applicationContext.xml or other configuration files. For example, annotations such as @SpringBootApplication, @Component, and @Bean allow Spring to perform dependency injection and configuration directly in Java code.</li><li><strong>Improve Code Readability and Maintainability — </strong>Annotations make the code more self-explanatory because configuration logic is tied directly to the relevant Java class or method. For example, @RestController clearly indicates that the annotated class will handle REST API requests.\</li><li><strong>Enable Auto-Configuration — </strong>The @SpringBootApplication annotation simplifies the setup process by leveraging intelligent defaults and auto-configuring components based on the dependencies in the class path. This reduces manual configuration effort significantly.</li><li><strong>Simplify Dependency Injection — </strong>Annotations like @Autowired or @Qualifier allow the framework to wire beans automatically, removing the need for manual wiring. This makes the development process faster and less error-prone.</li><li><strong>Support Stereotype Annotations — </strong>Annotations like @Component, @Service, @Repository, and @Controller clearly define the role of a particular class in the application. This helps to organize the code and align it with standard design patterns.</li><li><strong>Boost Development Speed — </strong>Developers can focus on writing business logic rather than spending time defining bean configurations and dependencies inside XML files.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I2jUzK8vHwtzA4oBnRaq9A.jpeg" /></figure><h3><strong>Core</strong> Spring Boot Annotations</h3><h4><strong>1. @ SpringBootApplication</strong></h4><p>This annotation is used to mark the <strong>main class of a Spring Boot application.</strong> It encapsulates @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan and @Configuration annotations with their default attributes.</p><blockquote>@SpringBootConfiguration : Indicates that a class provides configuration for a Spring Boot application.</blockquote><blockquote><em>@EnableAutoConfiguration</em> : Enables Spring Boot’s auto-configuration mechanism, automatically configuring beans based on classpath dependencies and application properties.</blockquote><blockquote>@ComponentScan : A spring framework annotation that tells the Spring container to automatically find and register beans (like @Component, @Service, @Repository, @Controller) in specified packages, streamlining configuration by eliminating manual XML bean definitions and promoting convention over configuration in modern Spring apps. It scans base packages and sub-packages, automatically managing eligible classes as beans, often used with @Configuration</blockquote><blockquote>@Configuration: Indicates that the class provides Spring configuration and bean definitions. Acts as a replacement for XML-based configuration.</blockquote><pre>@SpringBootApplication<br>public class SampleApplication {<br>    public static void main(String[] args)<br>    {<br>        SpringApplication.run(SampleApplication.class, args);<br>    }<br>}</pre><h4>2. @ Component</h4><p>Marks a class as a generic Spring-managed component. Spring automatically detects and registers it during component scanning.</p><pre>@Component<br>public class EmailService {<br>  // ...<br>}</pre><h4>3. @ Service</h4><p>Used in the service layer to define business logic and improve code readability. Service annotation is a specialized @Component that indicates the class contains <strong>business logic</strong>.</p><pre>@Service<br>public class UserService {<br>  // ...<br>}</pre><h4>4. @ Repository and @ Transactional — Data access related Annotations</h4><p>Used in the DAO layer to interact with the database. Automatically translates database-related exceptions into Spring exceptions.</p><pre>@Repository<br>public class UserRepository {<br>  // ...<br>}</pre><p>The @Transactional annotation is used to manage <strong>transactions</strong> in Spring, ensuring atomicity (all-or-nothing) and consistency during database operations.</p><ol><li>Ensures a method runs within a <strong>transaction</strong> (commit on success, rollback on failure).</li><li>Commonly used for methods that perform <strong>CRUD</strong> operations on the database.</li><li>Can be applied at <strong>class</strong> or <strong>method</strong> level.</li></ol><pre>@Service<br>public class UserService {<br>    @Transactional<br>    public void registerUser(User user) {<br>        userRepository.save(user);<br>        emailService.sendWelcomeEmail(user);  // Both operations run in a single transaction.<br>    }<br>}</pre><p>In case of exceptions, changes in userRepository.save() are rolled back automatically.</p><h4>5. @ Bean</h4><p>The @Bean annotation is used to <strong>define Spring-managed beans</strong> inside a @Configuration class. It tells the Spring container to manage the method&#39;s return object as a bean, providing it for dependency injection.</p><ol><li><strong>Explicit Bean Definition</strong>: @Bean is used when you manually want to configure a bean and provide it to the Spring Context. It is used in cases where automatic component scanning via annotations (e.g., @Component) is not applicable.</li><li><strong>Declarative Bean Creation</strong>: The annotated method contains the logic to instantiate, configure, and initialize the bean.</li><li><strong>Custom Configurations</strong>: Allows setting <strong>dependencies</strong>, <strong>properties</strong>, and <strong>custom logic</strong> during bean creation.</li></ol><pre>@Configuration<br>public class AppConfig {<br>    @Bean<br>    public MyService myService() {<br>        return new MyServiceImpl();  // Manually created and registered service bean<br>    }<br><br>    @Bean<br>    public MyRepository myRepository() {<br>        return new MyRepositoryImpl();<br>    }<br>}<br><br>// IMPORTANT -&gt; myService and myRepository become Spring-managed beans <br>// and can be injected into other components using @Autowired.<br></pre><p><strong>Benefits of </strong><strong>@Bean:</strong></p><ul><li>Greater control over bean creation (e.g., for third-party libraries that don’t use @Component).</li><li>Ideal for defining beans with <strong>custom instantiation logic</strong> or when automatic scanning isn’t feasible.</li></ul><h3>Dependency Injection Spring Boot Annotations</h3><p>Dependency Injection (DI) allows Spring to manage the lifecycle and dependencies of objects (beans) in the application. It ensures loose coupling between components, making code more modular and maintainable. Spring Boot provides several annotations to facilitate DI, including @Autowired, @Qualifier, and @Value.</p><h4>1. @ Autowired</h4><p>This annotation is used to <strong>inject a bean automatically</strong> into a field, constructor, or method. Spring checks the application context and resolves the dependency by type.</p><p><strong>@Autowired </strong>automatically finds a <strong><em>matching bean by type</em></strong> in the Spring container. It can be used on <strong>fields</strong>, <strong>setters</strong>, or <strong>constructors</strong>.</p><pre>@Component<br>public class MyService {<br>    public void performTask() {<br>        System.out.println(&quot;Task performed!&quot;);<br>    }<br>}<br><br>// FIELD AUTOWIRED INJECTION<br>@Component<br>public class MyController {<br>    @Autowired   // Automatically injects MyService bean<br>    private MyService myService;<br><br>    public void handleRequest() {<br>        myService.performTask();   // MyService is now available to use<br>    }<br>}</pre><pre>// CONSTRUCTOR AUTOWIRED INJECTION<br>@Component<br>public class MyController {<br>    private final MyService myService;<br><br>    @Autowired<br>    public MyController(MyService myService) {  // Constructor injection<br>        this.myService = myService;<br>    }<br><br>    public void handleRequest() {<br>        myService.performTask();  // MyService is now available to use<br>    }<br>}</pre><h4><strong>2. @ Qualifier</strong></h4><p>When there are <strong>multiple beans of the same type</strong>, Spring cannot decide which bean to inject. In such cases, you can use the @Qualifier annotation to specify the <strong>exact bean name</strong> to inject.</p><pre>@Component(&quot;myServiceImpl1&quot;)<br>public class MyServiceImpl1 implements MyService {<br>    @Override<br>    public void performTask() {<br>        System.out.println(&quot;Task performed by MyServiceImpl1!&quot;);<br>    }<br>}<br><br>@Component(&quot;myServiceImpl2&quot;)<br>public class MyServiceImpl2 implements MyService {<br>    @Override<br>    public void performTask() {<br>        System.out.println(&quot;Task performed by MyServiceImpl2!&quot;);<br>    }<br>}<br><br>@Component<br>public class MyController {<br>    // Injecting a specific bean using @Qualifier<br>    @Autowired<br>    @Qualifier(&quot;myServiceImpl2&quot;)<br>    private MyService myService;<br><br>    public void handleRequest() {<br>        myService.performTask();  // This will use MyServiceImpl2<br>    }<br>}</pre><h3>Configuration and Properties Spring Boot Annotations</h3><h4>1. @ Value</h4><p>This annotation is used to <strong>inject externalized configuration values</strong> from properties files, environment variables, or system properties into fields, constructors, or methods. Useful for injecting configurations like application settings, URLs, port numbers, etc. Values are fetched from the application.properties or application.yml file, or via environment variables.</p><pre>// application.properties file<br>app.name=MySpringBootApp<br>app.version=1.0.0<br>app.port=8080<br><br><br>// while usage in a java class<br>@Component<br>public class AppConfig {<br>    @Value(&quot;${app.name}&quot;) // Inject value from application.properties<br>    private String appName;<br><br>    @Value(&quot;${app.version}&quot;) // Inject value from application.properties<br>    private String appVersion;<br><br>    @Value(&quot;${app.port}&quot;) // Inject value from application.properties<br>    private int appPort;<br>}</pre><h4>2. @ ConfigurationProperties</h4><p>The @ConfigurationProperties annotation is used to <strong>bind a group of related configuration properties</strong> from sources like application.properties or application.yml to a <strong>POJO (Plain Old Java Object)</strong> in a <strong>type-safe manner</strong>.</p><h4>Key Features:</h4><ul><li>Binds multiple related configuration properties under a specific <strong>prefix</strong>.</li><li>Supports externalized configuration retrieval in a clean, structured way.</li><li>Useful for structured or hierarchical configuration.</li><li>Promotes maintainability and readability for configuration properties.</li></ul><pre>// application.properties file<br>app.name=MySpringBootApp<br>app.version=1.0.0<br>app.port=8080<br><br><br><br>// JAVA POJO<br>import org.springframework.boot.context.properties.ConfigurationProperties;<br>import org.springframework.stereotype.Component;<br>@Component<br>@ConfigurationProperties(prefix = &quot;app&quot;)<br>public class AppConfig {<br>    private String name;<br>    private String version;<br>    private int port;<br><br>    // Getters and setters<br>}<br><br><br><br>// Usage in another Java class<br>@Component<br>public class MyService {<br>    @Autowired<br>    private AppConfig appConfig;<br><br>    public void printConfig() {<br>        System.out.println(appConfig.getName());<br>        System.out.println(appConfig.getVersion());<br>        System.out.println(appConfig.getPort());<br>    }<br>}</pre><h4>3. Validation Annotations</h4><p>Ensure input correctness using annotations.</p><ol><li>@NotNull: Field must not be null.</li><li>@NotBlank: String must contain at least one non-whitespace character.</li><li>@Email: Validates email format.</li><li>@Size: Restricts length/size of the field.</li></ol><pre>// Example of the above validations are<br><br>public class UserDto {<br>    @NotNull<br>    private String username;<br><br>    @NotBlank<br>    private String fullName;<br><br>    @Email<br>    private String email;<br><br>    @Size(min = 6, max = 15)<br>    private String password;<br><br>    // Getters and setters<br>    // .......<br>}<br><br><br>// Now for the below when UserDto is used as request<br>// When validation fails, <br>// Spring automatically returns a 400 Bad Request with validation errors<br>@RestController<br>@RequestMapping(&quot;/users&quot;)<br>public class UserController {<br>    @PostMapping(&quot;/register&quot;)<br>    public String register(@RequestBody @Valid UserDto userDto) {<br>        return &quot;User registered!&quot;;<br>    }<br>}</pre><h3>Web and REST API Spring Boot Annotations</h3><h4>1. @ RestController</h4><p>Used to create RESTful web services and automatically returns data in JSON or XML format. Combines @Controller and @ResponseBody for handling REST APIs.</p><pre>@RestController<br>public class HelloController {<br>  // ...<br>}</pre><h4>2. @ RequestMapping</h4><p>Maps HTTP requests to controller classes or methods. Defines the base URL path for request handling.</p><pre>@RequestMapping(&quot;/api&quot;)<br>public class ApiController {<br>  // ...<br>}</pre><h4><strong>3. HTTP Method Annotations</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LaaYnotHNTLhw5PzPMB14A.png" /></figure><pre>@GetMapping(&quot;/users&quot;)<br>public List&lt;User&gt; getUsers() {<br>    return userService.getAllUsers();<br>}</pre><h4><strong>4. @ PathVariable</strong></h4><p>Extracts values from the URI path and binds them to method parameters.</p><pre>@GetMapping(&quot;/users/{id}&quot;)<br>public User getUser(@PathVariable int id) {<br>    return userService.getUser(id);<br>}</pre><h4>5. @ RequestParam</h4><p>Reads query parameters from the request URL. Used for optional or filtering inputs.</p><pre>@GetMapping(&quot;/search&quot;)<br>public String search(@RequestParam String keyword) {<br>    return keyword;<br>}</pre><h4>6. @ RequestBody</h4><p>Binds the HTTP request body to a Java object. Commonly used with POST and PUT requests.</p><pre>@PostMapping(&quot;/users&quot;)<br>public User saveUser(@RequestBody User user) {<br>    return userService.save(user);<br>}</pre><h3>EPILOGUE</h3><p><strong>Core Spring Boot Annotations</strong>: Provide foundational functionality like application setup (@SpringBootApplication) and bean management (@Bean, @Component).</p><p><strong>Dependency Injection Spring Boot Annotations</strong>: Manage object dependencies via @Autowired, resolve conflicts using @Qualifier, and inject external configuration values with @Value.</p><p><strong>Configuration and Properties Spring Boot Annotations</strong>: Enable type-safe binding of configuration properties to POJOs with @ConfigurationProperties and declare configuration classes using @Configuration.</p><p><strong>Web and REST API Spring Boot Annotations</strong>: Facilitate development of REST APIs and web controllers with @RestController, @RequestMapping, @GetMapping, and other HTTP method mappings.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cb46e5cecde3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python Library Basics — Introduction to NumPy and Pandas]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/python-library-basics-introduction-to-numpy-and-pandas-ce7ebe70c00b?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/ce7ebe70c00b</guid>
            <category><![CDATA[numpy]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[pandas]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Thu, 15 Jan 2026 09:10:07 GMT</pubDate>
            <atom:updated>2026-01-15T09:10:07.266Z</atom:updated>
            <content:encoded><![CDATA[<h3>Python Library Basics — Introduction to NumPy and Pandas</h3><h3>PROLOGUE</h3><blockquote><strong>“The goal is to turn data into information, and information into insight</strong>.” — Carly Fiorina</blockquote><p>NumPy, short for Numerical Python, stands as one of the most widely-used Python libraries for numerical computing. Open-source and incredibly versatile, it serves as the backbone for scientific and computational workflows, enabling efficient handling of numerical data. NumPy is a cornerstone tool for performing numerical operations. Its simplicity and performance make complex calculations straightforward, even for beginners.</p><p>On the other hand, <strong>Pandas</strong> is a powerful, open-source Python library designed for data manipulation and analysis. It is built on top of NumPy and provides highly efficient data structures such as <strong>DataFrames</strong> (2D) and <strong>Series</strong> (1D) for organizing and analyzing structured data.</p><h3>Core Features of NumPy</h3><ol><li><strong>N-dimensional Array: </strong>At the heart of NumPy lies its powerful <strong>N-dimensional array (ndarray)</strong>. This data structure can hold multi-dimensional numerical data in an efficient and easy-to-access format. It forms the basis of numerical operations, making large-scale manipulation of data seamless.</li><li><strong>Broadcasting: </strong>NumPy simplifies arithmetic operations through <strong>broadcasting</strong>, allowing calculations between arrays of different shapes without excessive use of loops. This ensures faster computations and cleaner code.</li><li><strong>Linear Algebra Capabilities: </strong>For anyone working with matrices, NumPy offers robust tools for core linear algebra operations, such as dot products, matrix inversions, eigenvalues, and more. Whether it’s basic matrix manipulation or advanced transformations, NumPy has your back.</li><li><strong>Seamless Integration with Libraries: </strong>NumPy works hand-in-hand with advanced libraries like TensorFlow, PyTorch, and pandas, forming the foundation for machine learning, deep learning, and data manipulation workflows</li></ol><h3>Why Use NumPy?</h3><ol><li>Efficient storage and manipulation of large datasets.</li><li>Optimized for performance (implemented in C under the hood).</li><li>More memory-efficient.</li><li>Easy integration for matrix math and vector operations.</li><li>Faster computation compared to traditional Python lists.</li></ol><h3>Numpy Statements</h3><h4><strong>I. Shape and Dimensions:</strong></h4><p>The .shape attribute provides the size of each dimension, while .ndim gives the total number of dimensions, and type() gives the type of the object.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/780/1*CvbJldIbbct-MJ3dMMgGqg.png" /></figure><h4><strong>II. Array Slicing:</strong></h4><p>Slicing is a powerful feature in NumPy that allows for efficient access and manipulation of specific portions of an array. It works similarly to slicing in Python lists but is far more flexible when dealing with multi-dimensional arrays.</p><p><strong>Syntax:</strong><br>array[start:stop:step]</p><ul><li><strong>start:</strong> The starting index (inclusive). Defaults to 0 if omitted.</li><li><strong>stop:</strong> The ending index (exclusive).</li><li><strong>step:</strong> The step size (optional). Defaults to 1.</li></ul><p>For multidimensional arrays, slicing can be done along multiple axes using commas:<br>array[row_start:row_stop, col_start:col_stop]</p><p><strong>Uses of Array Slicing :-</strong></p><ol><li><strong>Access Subsets:</strong> Retrieve specific portions of an array for analysis without changing the original dataset. Example: arr[2:5] retrieves elements from index 2 to 4.</li><li><strong>Modify Data:</strong> Directly alter portions of an array for updates or corrections. Example: arr[2:5] = [10, 11, 12] modifies elements in place.</li><li><strong>Filter Values:</strong> Use conditional slicing to extract values that meet logical conditions. Example: arr2D &gt; 4 generates a boolean mask identifying values greater than 4.</li><li><strong>Broadcasting:</strong> Combine slicing with broadcasting to perform arithmetic operations on specific sections. Example: arr2D[arr2D &gt; 4] += 1 increments all values greater than 4.</li><li><strong>Negative Indexing:</strong> Use negative indices to slice from the end of the array. Example: arr[-3:] gets the last 3 elements.</li><li><strong>Default Values:</strong> Omit start, stop, or step for cleaner slicing. Example: arr[:3] retrieves the first 3 elements.</li><li><strong>Boolean Masking:</strong> Use conditions to filter arrays dynamically. Example: Selecting values greater than 4 with arr2D[arr2D &gt; 4].</li><li><strong>Copy vs View:</strong> Slices return views of the original array. Changes to the slice affect the original array unless explicitly copied using .copy(). Example: arr_slice = arr[2:5].copy() prevents modifications to the original.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/786/1*B6IxXghgmMiA2S-N2G96JA.png" /></figure><h4>III. Array Manipulation Functions :</h4><ol><li><strong>Reshaping Arrays (</strong>reshape<strong>) -&gt; </strong>The total number of elements must match before and after reshaping. <br><strong>TIP:</strong> Use arr.reshape(-1, x) to automatically compute one dimension based on the size of the array. Like from 1D of <strong>6</strong> <strong>elements</strong> to 2D, if we do arr.reshape(-1, 3), then -1 tells NumPy to automatically calculate the number of rows based on the total number of elements. In this case, it calculates 6 / 3 = 2, creating a 2x3 array.</li><li><strong>Flattening Arrays (</strong>flatten<strong>) -&gt; </strong>Converts a multi-dimensional array into a one-dimensional array. Flattened arrays are useful for simplifying data processing tasks.<br><strong>TIP:</strong> Unlike .ravel(), flatten() always produces a copy of the original array</li><li><strong>Concatenation (</strong>concatenate<strong>) -&gt; </strong>Combines two or more arrays along a specified axis (rows for axis=0, columns for axis=1)<br><strong>TIP:</strong> Use np.vstack() and np.hstack() for vertical/axis=0 or horizontal/axis=1 stacking for a simpler interface.</li><li><strong>Sorting (</strong>np.sort<strong>) -&gt; </strong>Sorts array elements in ascending order. Works on both 1D and multi-dimensional arrays.<br><strong>TIP: </strong>For multi-dimensional arrays, use np.sort(arr, axis=...) to specify the axis to sort along</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/986/1*sWBnjDRErDnat81x1MsMeA.png" /></figure><h4>IV. Data Creation :</h4><ol><li><strong>Precision Handling:</strong> Use dtype=np.longdouble for higher precision computations, but double-check compatibility with other libraries.</li><li><strong>Controlled Spacing:</strong> Use np.linspace with endpoint=False if you want to exclude the stop value.</li><li><strong>Reproducibility:</strong> Set np.random.seed(seed_value) before using np.random to ensure consistent results across runs (useful for debugging and testing).</li><li><strong>Random Shapes:</strong> Use tuple shapes like (m, n) in np.random or np.full to generate arrays efficiently for various dimensions.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ClTqdHgSBdhV4jm767bQFA.png" /></figure><h4>V. Mathematics :</h4><ol><li><strong>Broadcasting Rules:</strong> Dimensions must be either the same or equal to 1. Use np.newaxis to reshape arrays for broadcasting.</li><li><strong>Universal Functions:</strong> Use NumPy’s built-in math functions (np.sin, np.exp, np.sqrt) for element-wise calculations across arrays.</li><li><strong>Performance Boost:</strong> Vectorize operations for faster computations instead of using Python loops.</li><li><strong>Matrix Multiplication Shortcut:</strong> Use the @ operator for clean syntax: result = A @ B.</li><li><strong>Aggregations:</strong> Use functions like np.sum, np.max, np.mean with axis for row-wise or column-wise calculations.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/978/1*zO8V0x8Hmj4lLP7CSUznOA.png" /></figure><h3>Core Features of Pandas</h3><p><strong>Pandas</strong> is a powerful and widely-used open-source Python library designed for data analysis and manipulation. Built on top of <strong>NumPy</strong>, it simplifies handling and processing structured data, making it a staple tool for data scientists, analysts, and developers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*byIFThDWnSOWIrKecyYBXg.png" /></figure><ol><li><strong>Homogeneity v/s Heterogeneity </strong>: All elements within a Series must be of the same data type, but DataFrames can contain columns with different data types. Each column, however, is a Series and thus homogeneous within itself.</li><li><strong>Size Immutability v/s Mutability</strong>: Once created, the size of a Series cannot be changed. Elements can be modified, but the number of elements remains constant. DataFrames are size-mutable, meaning rows and columns can be added or dropped.</li><li><strong>Analogy</strong>: Series are similar to a NumPy 1D array with an added index for labeling, whereas DataFrames are similar to a spreadsheet or a SQL table.</li><li><strong>Vectorized Operations:</strong> Both Series and DataFrames excel when performing vectorized operations (applying an operation to an entire Series or column) rather than iterating element-by-element.</li></ol><h3>Pandas Series</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NAaAj7_v5WsHiF8EIAIu4Q.png" /></figure><ol><li><strong>Create Series:</strong> Use pd.Series() with custom indices for labeled 1D data handling.<br>Example: pd.Series([10, 20, 30], index=[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]).</li><li><strong>Access Data:</strong> Use .loc for label-based indexing and .iloc for position-based indexing.<br>Example: s.loc[&#39;b&#39;] or s.iloc[1].</li><li><strong>Vectorized Operations:</strong> Perform operations like s * 2 directly or use .apply() for custom functions.<br>Example: s.apply(lambda x: x * 2).</li><li><strong>Handling Missing Data:</strong> Detect (.isna()), drop (.dropna()), or fill missing values (.fillna()).<br>Example: s.fillna(0).</li><li><strong>Filter with Conditions:</strong> Use logical conditions to filter data.<br>Example: s[s &gt; 20].</li><li><strong>Statistical Methods:</strong> Use .mean(), .sum(), .max() for quick stats or .describe() for a summary.<br>Example: s.mean().</li><li><strong>Change Index:</strong> Rename indices or reset them for better organization.<br>Example: s.index = [&#39;x&#39;, &#39;y&#39;, &#39;z&#39;] or s.reset_index().</li><li><strong>Sort Series:</strong> Sort by values (.sort_values()) or indices (.sort_index()).<br>Example: s.sort_values(ascending=False).</li><li><strong>String Handling:</strong> Perform string operations using .str.<br>Example: s.str.upper() or s.str.len().</li><li><strong>Unique &amp; Frequency:</strong> Identify unique values (.unique()) and count frequencies (.value_counts()).<br>Example: s.value_counts().</li><li><strong>Combine Series:</strong> Add, subtract, or concatenate Series easily.<br>Example: s1 + s2 or pd.concat([s1, s2]).</li></ol><h3>Pandas Dataframe</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/554/1*UpnSMV1jJ1qdLdrM1hE_Ag.png" /></figure><ol><li><strong>Create DataFrame:</strong> Use pd.DataFrame() with dictionaries, lists, or NumPy arrays.<br>Example: pd.DataFrame({&#39;Name&#39;: [&#39;Alice&#39;, &#39;Bob&#39;], &#39;Age&#39;: [25, 30]}).</li><li><strong>Access Data:</strong> Use .loc (label-based), .iloc (position-based), or column selection (df[&#39;column_name&#39;]).<br>Example: df.loc[0], df.iloc[1, :], or df[&#39;Age&#39;]</li><li><strong>Add or Remove Columns:</strong> Add new columns (df[&#39;New&#39;] = values) or remove columns (df.drop(&#39;column&#39;, axis=1)).<br>Example: df[&#39;Salary&#39;] = [5000, 6000].</li><li><strong>Filter Rows:</strong> Use conditions to filter rows.<br>Example: df[df[&#39;Age&#39;] &gt; 25]</li><li><strong>Groupby and Aggregations:</strong> Group data using .groupby() and apply aggregation functions like .sum() or .mean().<br>Example: df.groupby(&#39;Category&#39;).sum()</li><li><strong>Handle Missing Data:</strong> Detect (df.isna()), drop (df.dropna()), or fill (df.fillna()) missing values.<br>Example: df.fillna(0)</li><li><strong>Sorting:</strong> Sort rows by column values (.sort_values()) or by index (.sort_index()).<br>Example: df.sort_values(by=&#39;Age&#39;, ascending=False).</li><li><strong>Merge and Join:</strong> Merge or join multiple DataFrames using pd.merge() or df.join().<br>Example: pd.merge(df1, df2, on=&#39;key&#39;).</li><li><strong>Reshape Data:</strong> Pivot tables with .pivot(), .pivot_table(), or melt data using .melt().<br>Example: df.pivot(index=&#39;ID&#39;, columns=&#39;Category&#39;, values=&#39;Values&#39;).</li><li><strong>Apply Functions:</strong> Use .apply() or .applymap() for custom row, column, or element-wise functions.<br>Example: df[&#39;Age&#39;] = df[&#39;Age&#39;].apply(lambda x: x * 2)</li><li><strong>Statistical Methods:</strong> Use .mean(), .sum(), .max(), .min() across rows or columns (axis).<br>Example: df.mean(axis=0)</li><li><strong>Export Data:</strong> Save DataFrame to a file like CSV or Excel.<br>Example: df.to_csv(&#39;output.csv&#39;), df.to_excel(&#39;output.xlsx&#39;)</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ce7ebe70c00b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Java Programming — Multithreading]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/java-programming-multithreading-150f3ab6c1be?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/150f3ab6c1be</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Mon, 01 Sep 2025 09:58:53 GMT</pubDate>
            <atom:updated>2025-09-01T09:58:53.837Z</atom:updated>
            <content:encoded><![CDATA[<h3>Java Programming — Multithreading</h3><blockquote>“Juggling is an Illusion… In reality, the balls are being independently caught and thrown in rapid succession… It is actually task switching.” — Gary Keller</blockquote><h3>PROLOGUE</h3><p>Multithreading is essential for improving the <strong>performance (parallelism)</strong> and <strong>responsiveness</strong> <strong>(concurrency) </strong>of applications.</p><p>By allowing multiple threads to run concurrently, it enables efficient utilization of CPU resources. Performance is enhanced as separate threads can handle tasks like computation, I/O operations, or data processing simultaneously, reducing overall execution time.</p><p>For responsiveness, multithreading ensures that time-consuming tasks (e.g., network calls) run in the background, keeping the user interface responsive and smooth. This is critical in modern applications where lag / delay can result in poor user experience.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*vGdk3afaSP80sPc_UUiwOA.png" /></figure><blockquote>We don’t need multiple cores to achieve concurrency, even by using a single core and multiple threads, we can acheive concurrency.</blockquote><p>==============================================================</p><h3>Operating System, Process and Threads</h3><p>When we turn ON our computer, operating system (OS) is loaded from disk/hard drive to Memory. OS is essential system software that manages a computer’s hardware and software resources, acting as an intermediary between the user and the computer’s physical components.</p><p>All of our application/files reside inside the disk/hard drive. When we run an application, OS takes the program / application and creates an instance of the program in the memory. That instance is called process / context of the application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*76NTW6_2fSNd88CgFaNJTg.png" /></figure><ol><li><strong>Process</strong>: A process is an independent program in execution, containing its own memory space, resources, and system context. It can have one or more threads.</li><li><strong>Threads within a Process</strong>: A thread is like a “lightweight process” that shares the same memory and resources of its parent process but has its own execution context (e.g., program counter, stack, and register states). Multiple threads can exist in a single process and execute different parts of the code simultaneously. Threads share global variables but maintain separate stacks.</li></ol><blockquote><strong>Stack</strong> is a region in memory, where local variables are stored, and passed into function. <br><strong>Instruction Pointer </strong>is address of the next instruction to execute.<br><strong>Stack + Instruction Pointer</strong> is the current state of the thread.</blockquote><p><strong>Stack stores Local primitive types, local references.<br>Heap stores Objects, Class Members, Static variables.</strong></p><p><strong>Context Switch — Key Takeaways</strong></p><ol><li>Context switching is the process where a CPU stops executing one task (a process or thread), saves its current state (its “context”), and loads the saved state of another task to resume its execution, allowing multiple processes to share a single CPU and creating the illusion of multitasking. <strong>Context switching is not cheap, and is the price of multitasking (concurrency.)</strong></li><li>Too many threads — Thrashing, spending more time in management than real productive work.</li><li>Threads consume less resources than processes.</li><li>Context switching between <strong>threads from the same process is cheaper than context switch between different processes.</strong></li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GnGIRxZ1FMq4pHVotNgcFw.png" /></figure><p>==============================================================</p><h3>Thread Scheduling — Dynamic Priority with Epochs</h3><p><strong>Dynamic priority</strong> is a method of assigning priorities to threads in a way that can change over time based on various factors. It is used in scheduling algorithms to ensure fairness and improve responsiveness by adapting to the system’s state and workload.</p><p><strong>Epochs</strong> are a scheduling concept often associated with certain operating systems (e.g., older UNIX-like systems). In some scheduling algorithms, time is divided into discrete <em>epochs</em> during which threads are managed according to their priorities.</p><p>These two concepts can be combined to create a scheduling system that is both adaptive and organized:</p><ol><li>Priorities of threads are adjusted dynamically throughout an epoch.</li><li>At the end of each epoch, the system recalculates thread priorities based on their recent behavior (e.g., CPU usage, I/O activity).</li><li>A fair allotment of time slices ensures all threads get a chance to execute during each epoch.</li></ol><p>This approach balances responsiveness and fairness in scheduling, particularly for systems that manage a wide range of workloads, such as desktop environments or server applications.</p><p>==============================================================</p><h3>Different ways of Thread Implementation in JAVA</h3><h4>1. Extend Thread Class (encapsulates all thread related functions)</h4><pre>class MyThread extends Thread {<br>    @Override<br>    public void run() {<br>        // Code that executes on a new Thread<br>        System.out.println(&quot;Thread is running &quot; + this.getName());<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        MyThread thread = new MyThread();<br>        thread.start();  // Starts the new thread<br>    }<br>}</pre><h4>2. Implement Runnable Interface</h4><pre>class MyRunnable implements Runnable {<br>    @Override<br>    public void run() {<br>        System.out.println(&quot;Thread is running&quot;);<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        MyRunnable myRunnable = new MyRunnable();<br>        Thread thread = new Thread(myRunnable);<br>        thread.start();  // Starts the new thread<br>    }<br>}</pre><h4>3. Using a Lambda Expression (With Runnable)</h4><p>The following code is end-to-end which prints thread-name, thread-priority, does some task (sleep), and also handles exception.</p><pre>public class Main {<br>    public static void main(String[] args) {<br><br>        Thread thread = new Thread(() -&gt; {<br>            try {<br><br>                System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName());<br>                System.out.println(&quot;Thread Priority: &quot; + Thread.currentThread().getPriority());<br><br>                // Simulate some task (e.g., sleeping for 10 second)<br>                Thread.sleep(10000);<br><br>                System.out.println(&quot;Task completed successfully!&quot;);<br><br>            } catch (InterruptedException e) {<br>                // Handle InterruptedException<br>                System.out.println(&quot;Thread was interrupted: &quot; + e.getMessage());<br>            }<br>        });<br><br>        // Set thread name and priority<br>        thread.setName(&quot;MyCustomThread&quot;);<br>        thread.setPriority(Thread.NORM_PRIORITY + 1); // Slightly higher than normal priority<br><br>        // Start the thread<br>        thread.start();<br>    }<br>}</pre><p>==============================================================</p><h3>Properties of Threads in JAVA</h3><h4>1. Thread States</h4><p>Java provides a set of predefined states that describe the lifecycle of a thread. These states are defined in the Thread.State enum, and we can retrieve the thread&#39;s current state using thread.getState()</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XccfH5bX_IUK-dvbdyAcWw.png" /></figure><h4>2. Thread Priority</h4><p>Threads in Java are assigned a <strong>priority</strong> to suggest their relative importance to the JVM scheduler. The priority is an integer between Thread.MIN_PRIORITY (1) and Thread.MAX_PRIORITY (10), with the default being Thread.NORM_PRIORITY (5)</p><blockquote>getPriority()<strong> / </strong>setPriority(int): Gets or sets the thread&#39;s priority.</blockquote><h4>3. Daemon Threads</h4><p>By default, threads are non-daemon (i.e., daemon = false). Non-daemon threads (or also referred to as User threads) keep the JVM process alive as long as at least one of them is running. Unless explicitly stopped, if there are unfinished non-daemon threads, the application will keep running indefinitely, even if all other threads (or the main thread) complete.</p><blockquote>The Java Virtual Machine (JVM) will not terminate the application as long as there is at least one thread running.</blockquote><p>To ensures that the thread runs in the background and terminates when the main thread finishes, set a thread as a daemon using the setDaemon(true) method <strong>before starting the thread</strong>. Use isDaemon() to check if a thread is a daemon.</p><h4>4. Thread Lifecycle Functions</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4cum8nYYBVEqT6K1q0S14g.png" /></figure><h4><strong>5. Join Threads —</strong></h4><p>The join() method ensures that the <strong>main thread</strong> (or caller thread) waits until the specified thread completes its execution. This helps in synchronizing multiple threads when the final result depends on the output of predecessor threads.</p><ol><li>The Thread.join() method is declared to throw an InterruptedException.</li><li>Since InterruptedException is a checked exception in Java, the compiler will enforce that it is either caught or declared to be thrown by the enclosing method.</li><li>Therefore, omitting a try-catch block around thread.join() and also not declaring throws in Java will result in a compile-time error.</li></ol><pre><br>// Calculate Sum of Powers of Big Integers<br><br>import java.math.BigInteger;<br><br>public class ComplexCalculation {<br>    public BigInteger calculateResult(BigInteger base1, BigInteger power1, BigInteger base2, BigInteger power2) {<br>        BigInteger result;<br>        /*<br>            Calculate result = ( base1 ^ power1 ) + (base2 ^ power2).<br>            Where each calculation in (..) is calculated on a different thread<br>        */<br>        <br>        PowerCalculatingThread thread1 = new PowerCalculatingThread(base1, power1);<br>        <br>        PowerCalculatingThread thread2 = new PowerCalculatingThread(base2, power2);<br>        <br>        thread1.start();<br>        thread2.start();<br>        <br>        try {<br>            thread1.join();<br>            thread2.join();<br>        } catch (InterruptedException e) {<br>            System.err.println(&quot;Thread interrupted during join: &quot; + e.getMessage());<br>        }<br>        <br>        return thread1.getResult().add(thread2.getResult());<br>        <br>    }<br><br>    private static class PowerCalculatingThread extends Thread {<br>        private BigInteger result = BigInteger.ONE;<br>        private BigInteger base;<br>        private BigInteger power;<br>    <br>        public PowerCalculatingThread(BigInteger base, BigInteger power) {<br>            this.base = base;<br>            this.power = power;<br>        }<br>    <br>        @Override<br>        public void run() {<br>           /*<br>           Implement the calculation of result = base ^ power<br>           */<br>          for (BigInteger i = BigInteger.ZERO ; i.compareTo(power) &lt; 0 ; ) {<br>              result = result.multiply(base); <br>              i = i.add(BigInteger.ONE);<br>          }<br>            <br>        }<br>    <br>        public BigInteger getResult() { return result; }<br>    }<br>}</pre><p><strong>Thread.join(timeout) —</strong></p><p>Threads may take unreasonably long time. The method call thread.join(2000) instructs the current thread to wait for the completion of the thread object&#39;s execution. The 2000 argument specifies a timeout in milliseconds.</p><blockquote><strong>What does setDaemon(true) with join(timeout) do?</strong></blockquote><blockquote>When a daemon thread is joined with a timeout, the calling thread will wait for the daemon thread to complete, but only for the specified duration.</blockquote><blockquote>If the daemon thread finishes within the timeout, the join() call unblocks.</blockquote><blockquote>If the daemon thread does not finish within the timeout, the join() call unblocks, and the daemon thread continues to run in the background. However, because it is a daemon thread, it will not prevent the program from exiting if all other non-daemon threads have finished. This means that even if the daemon thread is still running after the join(timeout) unblocks, it will be automatically terminated when the main program flow (or other non-daemon threads) completes.</blockquote><h4><strong>6. Thread.interrupt()</strong></h4><p>The Thread.interrupt() method in Java is used to <strong>interrupt a thread</strong> and signal that it should stop or take some corrective action, typically when it&#39;s performing a blocking operation (e.g., sleeping, waiting). However, interrupt() itself does not stop the thread — it is up to the thread to respond to the interruption gracefully.</p><p>==============================================================</p><h3>Performance in Multithreading — Latency and Throughput</h3><p><strong>Latency — </strong>Generally <strong>not reduced</strong> by adding more cores since each task’s individual execution time doesn’t change. Exception: If a single task can be <strong>split across cores (parallelism), latency decreases.</strong></p><p><strong>Throughput — </strong>Throughput defines the amount of material or items passing through a system or process. <strong>Improves significantly</strong> as more cores process more tasks in parallel.</p><p><strong>Formulae —</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C8l2obrehenp6o8oEzDMLw.png" /></figure><p>where Eff = efficiency factor (0 &lt; Eff ≤ 1).</p><p><strong>Efficiency</strong> comes in picture while dividing tasks into threads, this <em>(below pic)</em> is the overhead cost we should always keep in mind. So for small tasks, it’s actually a disadvantage to create threads and running them in parallel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Uz8X2QvOmwgHUAaYD7vgAw.png" /></figure><blockquote>Let’s say we have N cores (including physical + virtual), if we break a task into threads more than N, it might increase significant latency due to increased overhead, thus no performance benefit. So stick to breaking of threads to max N.</blockquote><p><strong>All above discussions are related to Non-blocking calls.</strong></p><p>==============================================================</p><h3>Threadpool in JAVA</h3><p>A <strong>ThreadPool</strong> is a collection of pre-initialized threads that manage and execute tasks efficiently. Instead of creating a new thread for every task (expensive), tasks are assigned to reusable threads in the pool.</p><ol><li><strong>Pre-Allocated Threads</strong>: A thread pool consists of a fixed or dynamically adjustable number of threads (N threads).</li><li><strong>Task Queue</strong>: Tasks are submitted to the pool via a task queue.</li><li><strong>Thread Assignment</strong>: Threads pick up tasks from the queue and execute them.</li><li><strong>Reusable Threads</strong>: Once a thread completes a task, it is reused for the next task instead of being destroyed.</li></ol><h4><strong>Types of Threadpool in Java</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4MaAXSie6GlH7zphYt_wfA.png" /></figure><p><strong>1. Fixed Thread Pool</strong></p><p>Creates a thread pool with a fixed number of threads. Once all threads are busy, subsequent tasks are queued until a thread becomes available.</p><pre>import java.util.concurrent.*;<br><br>public class FixedThreadPoolExample {<br>    public static void main(String[] args) {<br>        ExecutorService executor = Executors.newFixedThreadPool(4); // 4 threads in the pool<br>        for (int i = 0; i &lt; 10; i++) {<br>            executor.execute(() -&gt; System.out.println(&quot;Task executed by thread: &quot; + Thread.currentThread().getName()));<br>        }<br>        executor.shutdown();<br>    }<br>}</pre><p><strong>2. Cached Thread Pool</strong></p><p>Creates a thread pool with dynamically growing threads. Threads that are idle for 60 seconds are terminated. If all threads are busy, new threads are created.</p><pre>ExecutorService executor = Executors.newCachedThreadPool();<br>// rest program as of above</pre><p><strong>3. Single Thread Executor</strong></p><p>Creates a thread pool with only a single thread. Tasks are executed sequentially (one-by-one) in the order they are submitted.</p><pre>ExecutorService executor = Executors.newSingleThreadExecutor();</pre><p><strong>4. Scheduled Thread Pool (Introduced in Java 8)</strong></p><p>Creates a thread pool that can schedule tasks to execute <strong>after a delay</strong> or <strong>periodically</strong> (repeated execution). Use case -&gt; CRON jobs.</p><pre>import java.util.concurrent.*;<br><br>public class ScheduledThreadPoolExample {<br>    public static void main(String[] args) {<br>        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); // 2 threads<br><br>        scheduler.schedule(() -&gt; System.out.println(&quot;Task executed after 3 seconds&quot;), 3, TimeUnit.SECONDS);<br><br>        scheduler.scheduleAtFixedRate(() -&gt; System.out.println(&quot;Repeating task every 2 seconds&quot;), <br>                                       1, 2, TimeUnit.SECONDS);<br><br>        scheduler.scheduleWithFixedDelay(() -&gt; System.out.println(&quot;Task executed with a fixed delay of 1 second&quot;), <br>                                         1, 1, TimeUnit.SECONDS);<br>    }<br>}</pre><p><strong>5. Work-Stealing Pool (Introduced in Java 8)</strong></p><p>Creates a pool of threads using the <strong>ForkJoinPool</strong> behind the scenes. (even virtual threads uses <strong>ForkJoinPool </strong>behind the scenes.) Exploits multiple processor cores by dynamically redistributing tasks from busy threads to idle threads (work stealing).</p><pre>ExecutorService executor = Executors.newWorkStealingPool(); <br>// Parallelism based on available cores.</pre><p><strong>6. Custom ThreadPool (ThreadPoolExecutor)</strong></p><p>We can create a customizable thread pool using ThreadPoolExecutor</p><pre>import java.util.concurrent.*;<br><br>public class CustomThreadPoolExample {<br>    public static void main(String[] args) {<br>        ThreadPoolExecutor executor = new ThreadPoolExecutor(<br>            2,           // Core pool size - Minimum threads kept alive<br>            4,           // Maximum pool size - Max threads allowed<br>            30,          // Keep-alive time - How long idle threads are kept alive<br>            TimeUnit.SECONDS, <br>            new LinkedBlockingQueue&lt;&gt;(2) // Task queue with capacity of 2<br>        );<br><br>        for (int i = 0; i &lt; 10; i++) {<br>            executor.execute(() -&gt; System.out.println(&quot;Task executed by thread: &quot; + Thread.currentThread().getName()));<br>        }<br><br>        executor.shutdown();<br>    }<br>}</pre><p>==============================================================</p><h3>Concurrency Challenges and ways to deal with it</h3><h4><strong>Critical Section and Synchronization</strong></h4><p>A <strong>critical section</strong> is a part of the code that accesses shared resources (e.g., variables, files) that should not be concurrently modified by more than one thread. <strong>Synchronization</strong> ensures that only one thread can access a shared resource (or critical section) at a time to prevent data inconsistency or race conditions. It’s a way to control thread interaction.</p><h4>Atomic Operations in Java</h4><ol><li><strong>Reading and Writing Primitive Types</strong> (Except long and double): Operations like reading and writing int, boolean, char, byte, etc., are atomic. Example: int x = 5; or x = y;</li><li><strong>Reading and Writing References</strong>: Reading/writing object references is atomic. Example: Object obj = myObject; Thus all <strong>getters</strong> and <strong>setters</strong> of a class is <strong>atomic</strong>.</li><li><strong>Atomic Classes</strong> in java.util.concurrent.atomic: Classes like AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference provide atomic methods for increment, decrement, update, etc., on variables.</li><li><strong>Reading and Writing </strong>long<strong> and </strong>double: These are <strong>not atomic</strong> by default because they are 64-bit and can be read/written in two separate 32-bit operations. But If declared <strong>volatile</strong>, then the operations on long and double become atomic. When a variable is declared volatile, all threads will read its latest value directly from the <strong>main memory</strong> (instead of thread-local cache). <strong>Even if we are reading long and double from getters of object references, it will still be non-atomic, and thus we need volatile.</strong></li></ol><blockquote>A variable with the volatile keyword in programming ensures that any change to it is immediately visible to all other threads, but it doesn&#39;t guarantee atomicity because operations like count++ involve multiple steps (read, modify, write), and a thread could be interrupted between these steps, leading to inconsistent results. In essence, volatile ensures that threads see the most up-to-date value of the variable, but it doesn&#39;t ensure that a series of operations on that variable occur as a single, indivisible &quot;atomic&quot; unit.</blockquote><h4>Synchronized Keyword</h4><p>A <strong>data race</strong> occurs when two or more threads access the same shared resource (e.g., variable, object) <strong>concurrently</strong>, and at least one thread modifies the resource without proper synchronization. This results in <strong>undefined or inconsistent behavior</strong> because the threads can overwrite each other’s operations, leading to unpredictable outcomes. To overcome data race in JAVA, we use <strong>Synchronized Keyword </strong>:-</p><pre>// PART A -&gt; use keyword &quot;synchronized&quot; -&gt; for whole of method<br>public synchronized void wholeMethod() {<br>    // critical section code<br>}<br><br>// PART B-&gt; use block &quot;synchronized (this) { ... }&quot; -&gt; for part of method<br>public void partOfMethod() {<br>    // non - critial section <br>    // ...<br>    // ...<br>    <br>    // entering critical section<br>    // Synchronized block for thread safety<br>    synchronized (this) {<br>        // critical section code<br>    }<br>    // exit critical section<br><br>    // non - critial section <br>    // ...<br>    // ...<br>}<br><br>// PART C-&gt; use keyword &quot;synchronized&quot; with &quot;static&quot; <br>// to block class level whole of method<br>public static synchronized void wholeMethod() {<br>    // critical section code<br>}</pre><p>But note with above, if we are having multiple methods, all with the synchronized keyword as method definition, <strong><em>then if a thread is executing one of the methods, then all the other methods are deprived from using by other threads!</em></strong></p><p>So how to work around above limitation,</p><p>That is by using<strong> multiple locks</strong>, i.e, locks per method. Because Synchronization happens on object level.</p><pre>class MyClass {<br><br>    private final Object lock1 = new Object(); // Lock for method1<br>    private final Object lock2 = new Object(); // Lock for method2<br><br>    public void method1() {<br>        synchronized (lock1) { // Locking with lock1<br>            // some critical section code of method1<br>        }<br>    }<br><br>    public void method2() {<br>        synchronized (lock2) { // Locking with lock2<br>            // some critical section code of method2<br>        }<br>    }<br><br>}</pre><p>Now for above if ThreadA blocks method1(), then ThreadB would still be blocked for method1(), but ThreadB can access method2().</p><p>But as we use multiple locks in our code, a bigger problem awaits for us.</p><p>==============================================================</p><h3>Issue with multiple locks — Deadlock</h3><p>A <strong>deadlock</strong> in Java occurs when two or more threads are <strong>blocked forever</strong> because each thread is waiting for a resource (lock) held by another thread in a circular fashion. Since no thread can proceed, the execution gets stuck, and the program hangs.</p><h4>How Does Deadlock Occur — All of these below conditions met</h4><ol><li><strong>Mutual Exclusion</strong>: At least one shared resource is non-shareable. Only one thread can access it at a time.</li><li><strong>Hold and Wait</strong>: A thread holds one resource and waits to acquire another resource held by another thread.</li><li><strong>No Preemption</strong>: A resource held by a thread cannot be forcibly taken away; it can only be released voluntarily.</li><li><strong>Circular Wait</strong>: A set of threads form a circular chain where each thread waits for a resource held by the next thread in the chain.</li></ol><h4>Getting rid of Deadlock</h4><p>So as we see, if all of the four above holds true, deadlock arises. To get rid of deadlock, a simple solution is to get <strong>avoid circular wait. </strong>Strictly speaking in terms of acquiring locks and releasing them, <strong>always hold locks in the same order.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h5ZjSVhbTZuRo20HD_Sz3g.png" /></figure><pre>class Shared_Resource_With_Deadlock {<br>    void method1() {<br>        synchronized (String.class) {<br>            // ...<br>            synchronized (Integer.class) {<br>                // ...<br>            }<br>        }<br>    }<br><br>    void method2() {<br>        synchronized (Integer.class) {<br>            // ...<br>            synchronized (String.class) {<br>                // ...<br>            }<br>        }<br>    }<br>}</pre><p>The <strong>(above)</strong> is particular situation of Deadlock. To solve that <strong>(below)</strong> is the correct order to aquire locks. <strong>Notice that the order of releasing locks doesn’t matter.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pCgCmEzzhoCuz9h_3Sv5tg.png" /></figure><pre>class Shared_Resource_With_NO_Deadlock {<br>    void method1() {<br>        synchronized (String.class) {<br>            // ...<br>            synchronized (Integer.class) {<br>                // ...<br>            }<br>        }<br>    }<br><br>    void method2() {<br>        synchronized (String.class) {<br>            // ...<br>            synchronized (Integer.class) {<br>                // ...<br>            }<br>        }<br>    }<br>}</pre><p>==============================================================</p><h3>Reentrant Lock</h3><p>The ReentrantLock class in java.util.concurrent.locks provides more advanced locking mechanisms compared to the synchronized keyword. It provides better flexibility and offers features like:</p><ul><li>Ability to lock/unlock explicitly.</li><li>Try to acquire a lock with timeout.</li><li>Support for interruptible locking.</li></ul><pre>class SharedResource {<br>    private final ReentrantLock lock = new ReentrantLock(); // Lock Object<br><br>    public void increment() {<br>        lock.lock(); // Acquire the lock<br>        try {<br>            // critical section code<br>        } finally {<br>            lock.unlock(); <br>            // Always release the lock in `finally` block<br>            // Because if the code throws some exceptions, <br>            // we still need to unlock the lock.<br>        }<br>    }<br>}</pre><h4>1. tryLock(timeout)</h4><p>tryLock(timeout) allows the thread to wait for the lock for a specified amount of time before giving up.</p><pre>import java.util.concurrent.locks.ReentrantLock;<br>import java.util.concurrent.TimeUnit;<br><br>class LockWithTimeoutExample {<br>    private final ReentrantLock lock = new ReentrantLock();<br><br>    public void performTask() {<br>        try {<br>            // Waits for 2 seconds to acquire the lock<br>            if (lock.tryLock(2, TimeUnit.SECONDS)) { <br>                try {<br>                    // Critical section code<br>                } finally {<br>                    lock.unlock(); // Release the lock<br>                }<br>            } else {<br>                System.out.println(Thread.currentThread().getName() + &quot; could not acquire the lock.&quot;);<br>            }<br>        } catch (InterruptedException e) {<br>            e.printStackTrace();<br>        }<br>    }<br>}</pre><h4>2. Interruptible Locking -&gt; lockInterruptibly()</h4><p>lockInterruptibly() allows us to interrupt a thread while it&#39;s waiting for the lock. Very useful for implementing the watchDog, i.e, deadlock detection, because by this merhod</p><pre>import java.util.concurrent.locks.ReentrantLock;<br><br>class InterruptibleLockExample {<br>    private final ReentrantLock lock = new ReentrantLock();<br><br>    public void performTask() {<br>        try {<br>            lock.lockInterruptibly(); // Allows interruption while waiting for the lock<br>            try {<br>                System.out.println(Thread.currentThread().getName() + &quot; got the lock!&quot;);<br>                Thread.sleep(2000); // Simulate some work<br>            } finally {<br>                lock.unlock(); // Release the lock<br>            }<br>        } catch (InterruptedException e) {<br>            System.out.println(Thread.currentThread().getName() + &quot; was interrupted while waiting for the lock.&quot;);<br>        }<br>    }<br>}<br><br>public class InterruptibleLockTest {<br>    public static void main(String[] args) {<br>        InterruptibleLockExample example = new InterruptibleLockExample();<br><br>        Thread t1 = new Thread(example::performTask, &quot;Thread-1&quot;);<br>        Thread t2 = new Thread(() -&gt; {<br>            try {<br>                Thread.sleep(500); // Allow Thread-1 to acquire the lock<br>                t1.interrupt(); // Interrupt Thread-1 while waiting for the lock<br>            } catch (InterruptedException e) {<br>                e.printStackTrace();<br>            }<br>        }, &quot;Thread-2&quot;);<br><br>        t1.start();<br>        t2.start();<br>    }<br>}</pre><h4>3. Methods like these given by Reentrant Lock can be very handy for testing</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UVxXQuO_Slf8rQF2zy-tqA.png" /></figure><h4>4. ReentrantReadWriteLock in JAVA</h4><p>ReentrantReadWriteLock is a <strong>read-write lock</strong> that allows a resource to be accessed by multiple threads for reading or a single thread for writing, but not both at the same time. It improves performance in scenarios where reads are more frequent than writes, as it allows <strong>concurrent reading</strong> by multiple threads while maintaining <strong>exclusive access</strong> for writes.</p><p><strong>Read Lock (</strong>readLock<strong>)</strong>:</p><ul><li>Multiple threads can acquire the readLock simultaneously if no thread holds the writeLock.</li><li>Ensures better performance when there are frequent read operations.</li><li>A counter is hold internally which holds how many threads currently holds readLock</li></ul><p><strong>Write Lock (</strong>writeLock<strong>)</strong>:</p><ul><li>Allows only one thread to acquire the lock at a time.</li><li>Prevents read and write operations from executing concurrently.</li><li>Also no thread can aquire writeLock even there is a single thread currently holding readLock</li></ul><pre>class SharedResource {<br>    private int counter = 0;<br><br>    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();<br><br>    public void increment() {<br>        lock.writeLock().lock(); // Acquire write lock<br>        try {<br>            counter++;<br>            System.out.println(Thread.currentThread().getName() + &quot; incremented counter to: &quot; + counter);<br>        } finally {<br>            lock.writeLock().unlock(); // Release write lock<br>        }<br>    }<br><br>    public int getCounter() {<br>        lock.readLock().lock(); // Acquire read lock<br>        try {<br>            System.out.println(Thread.currentThread().getName() + &quot; read counter: &quot; + counter);<br>            return counter;<br>        } finally {<br>            lock.readLock().unlock(); // Release read lock<br>        }<br>    }<br>}</pre><p>==============================================================</p><h3>Semaphores</h3><p>A <strong>Semaphore</strong> is a synchronization mechanism in Java that controls access to <strong>a shared resource with a fixed number of permits</strong>. It is part of the java.util.concurrent package and helps manage concurrent thread access to limited resources (e.g., database connections, thread pools, files).</p><p>A semaphore regulates access using a set number of permits. Threads acquire a permit before accessing the resource and release the permit when done. If no permits are available, the thread is <strong>blocked</strong> until a permit is released.</p><ul><li><strong>Counting Semaphore — </strong>Maintains a count of available permits.</li><li><strong>Binary Semaphore — </strong>Acts like a <strong>lock</strong> with only one permit.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zSj8YZW_xDOIF3jSZ5EOKw.png" /></figure><h4>Fair Semaphore</h4><p>By default, Semaphore is <strong>unfair</strong>, meaning threads waiting to acquire permits may not get them in the order they requested.</p><p>To enforce fairness:</p><pre>Semaphore semaphore = new Semaphore(3, true); // Create a fair semaphore<br>// This ensures that threads acquire permits in a first-come-first-served (FIFO) order.</pre><h4>Use Cases of Semaphores</h4><ol><li><strong>Database Connection Pool — </strong>Limiting concurrent database connections to a fixed number.</li><li><strong>Thread Pool — </strong>Restricting the number of threads that can run simultaneously.</li><li><strong>Resource Management — </strong>Managing access to a limited pool of resources (e.g., files, services).</li><li><strong>Concurrent Workflows — </strong>Controlling order of execution or allowing staged threads to execute periodically.</li></ol><h4>Key difference between Semaphore and ReentrantLock</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ji8JcP-A1F13dqO_KbpTWA.png" /></figure><h4>Code example of Semaphores</h4><pre>class SharedResource {<br>    private final Semaphore semaphore = new Semaphore(5); // Allow 5 permits (threads)<br><br>    public void accessResource(String threadName) {<br>        try {<br>            System.out.println(threadName + &quot; trying to acquire a permit...&quot;);<br>            semaphore.acquire(); // Acquire a permit<br>            System.out.println(threadName + &quot; got a permit!&quot;);<br><br>            // Simulate critical section<br>          <br>            System.out.println(threadName + &quot; done with the resource.&quot;);<br>        } catch (InterruptedException e) {<br>            e.printStackTrace();<br>        } finally {<br>            System.out.println(threadName + &quot; releasing the permit.&quot;);<br>            semaphore.release(); // Release the permit<br>        }<br>    }<br>}</pre><h4>FAQs on Semaphore</h4><ol><li>Can we have negative semaphore value? -&gt; NO</li><li>Can we have semaphore value as 0 and then release() -&gt; YES. It means previously no permits are currently available, but once released, it will become 1, making a permit available for a waiting process or thread.</li></ol><p>==============================================================</p><h3>Inter-thread Communication in Java</h3><h4><strong>Condition Variables</strong></h4><p>A <strong>condition variable</strong> allows a thread to <strong>wait</strong> until a specific condition is met without busy-waiting. Other threads can <strong>signal</strong> it when the condition becomes true, allowing the waiting thread to proceed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YMIEoVCEsSHg8MReyov-Qg.png" /></figure><h4>Benefits of Condition Variables</h4><ol><li><strong>Fine-grained control</strong>: Use multiple Condition objects with a single lock to manage different conditions (e.g., notFull and notEmpty).</li><li><strong>Readable and Flexible</strong>: Easier to design complex thread interactions compared to wait/notify.</li><li><strong>Avoids Unnecessary Wakeups</strong>: Only the appropriate threads are signaled.</li><li><strong>Condition Variables</strong> are especially useful in scenarios like producer-consumer problems, reader-writer locks, or threads needing access control over shared resources.</li></ol><h4>Producer-Consumer Problem Using Condition Variables</h4><p>The <strong>producer-consumer problem</strong> is a classic example of inter-thread communication where:</p><ul><li><strong>The producer thread</strong> produces items and adds them to a shared buffer.</li><li><strong>The consumer thread</strong> consumes items from the shared buffer.</li></ul><p>Condition variables are used to ensure:</p><ol><li>The producer waits if the buffer is full.</li><li>The consumer waits if the buffer is empty.</li></ol><pre>import java.util.concurrent.locks.Condition;<br>import java.util.concurrent.locks.Lock;<br>import java.util.concurrent.locks.ReentrantLock;<br>import java.util.LinkedList;<br><br>class SharedBuffer {<br>    private final LinkedList&lt;Integer&gt; buffer = new LinkedList&lt;&gt;();<br>    private final int capacity = 5;<br><br>    // Lock and condition variables<br>    private final Lock lock = new ReentrantLock();<br>    private final Condition notEmpty = lock.newCondition(); // For consumer<br>    private final Condition notFull = lock.newCondition();  // For producer<br><br>    // Producer method<br>    public void produce(int value) throws InterruptedException {<br>        lock.lock(); // Acquire the lock<br>        try {<br>            // Wait until the buffer has space<br>            while (buffer.size() == capacity) {<br>                System.out.println(&quot;Producer waiting, buffer is full...&quot;);<br>                notFull.await(); // Wait for space<br>            }<br>            buffer.add(value);<br>            System.out.println(&quot;Produced: &quot; + value);<br>            notEmpty.signal(); // Signal the consumer that the buffer is not empty<br>        } finally {<br>            lock.unlock(); // Release the lock<br>        }<br>    }<br><br>    // Consumer method<br>    public void consume() throws InterruptedException {<br>        lock.lock(); // Acquire the lock<br>        try {<br>            // Wait until there is something to consume<br>            while (buffer.isEmpty()) {<br>                System.out.println(&quot;Consumer waiting, buffer is empty...&quot;);<br>                notEmpty.await(); // Wait for items<br>            }<br>            int value = buffer.removeFirst();<br>            System.out.println(&quot;Consumed: &quot; + value);<br>            notFull.signal(); // Signal the producer that the buffer is not full<br>        } finally {<br>            lock.unlock(); // Release the lock<br>        }<br>    }<br>}<br><br>public class ProducerConsumerUsingCondition {<br>    public static void main(String[] args) {<br>        SharedBuffer buffer = new SharedBuffer();<br><br>        // Producer thread<br>        Thread producer = new Thread(() -&gt; {<br>            int value = 0;<br>            try {<br>                while (true) {<br>                    buffer.produce(++value);<br>                    Thread.sleep(500); // Simulate production time<br>                }<br>            } catch (InterruptedException e) {<br>                e.printStackTrace();<br>            }<br>        });<br><br>        // Consumer thread<br>        Thread consumer = new Thread(() -&gt; {<br>            try {<br>                while (true) {<br>                    buffer.consume();<br>                    Thread.sleep(1000); // Simulate consumption time<br>                }<br>            } catch (InterruptedException e) {<br>                e.printStackTrace();<br>            }<br>        });<br><br>        producer.start();<br>        consumer.start();<br>    }<br>}</pre><h4>Using Objects as Inter-thread Communication in Java</h4><p>Before the introduction of the java.util.concurrent package (which includes Condition), thread communication in Java was typically implemented using <strong>objects themselves as condition variables</strong>. The Object class in Java provides built-in methods for thread intercommunication:</p><ol><li>wait():Causes a thread to wait until it is awakened by another thread. Thread releases the lock on the object and enters a waiting state.</li><li>notify() — Wakes up one thread that is waiting on the object’s monitor (if any).</li><li>notifyAll() — Wakes up all threads that are waiting on the object’s monitor.</li></ol><p>Every object in Java has a <strong>monitor</strong>. When a thread wants to communicate, it can call wait(), notify(), and notifyAll() on the object <strong>while holding its monitor lock</strong> (inside a synchronized block). Threads must hold the <strong>intrinsic lock</strong> of the object before calling these methods. Otherwise, a runtime exception (IllegalMonitorStateException) will be thrown.</p><h4>Key Difference Between Using Objects and Condition Variables as Inter-thread Communication in Java</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lLd6Z1LiE36KE1BlSfKbyg.png" /></figure><p>==============================================================</p><h3>Thread per task model v/s Thread per core model with Blocking I/O</h3><p>Blocking I/O is a programming concept where an application’s execution halts and waits for an input/output (I/O) operation to complete before proceeding, effectively “blocking” the thread. In this synchronous model, the program’s flow pauses until the I/O operation (like reading a file or receiving network data) finishes and the data is ready for use.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jjzwKjOdZbfxL4rxZr8uZQ.png" /></figure><h3>Virtual Threads in Java</h3><p>Virtual threads revolutionize concurrency in Java by enabling developers to execute millions of lightweight threads without the memory overhead and scalability issues of traditional platform threads (OS threads).</p><p>They maintain the familiar thread-based API while addressing the inefficiencies of the traditional thread-per-task model.</p><ol><li><strong>Lightweight — </strong>Virtual threads are much lighter than OS threads and use far less memory.</li><li><strong>Managed by JVM — </strong>Unlike platform threads managed by the OS, virtual threads are managed by the Java runtime.</li><li><strong>Non-blocking I/O — </strong>Blocking calls (e.g., I/O operations) inside virtual threads do not block the underlying OS threads.</li><li><strong>Rich API — </strong>Virtual threads use the familiar Thread class and APIs, making it simple to adopt.</li><li><strong>Scalable Concurrency — </strong>Can scale to millions of simultaneous threads, enabling massive parallelism without excessive overhead.</li><li><strong>Task Isolation — </strong>Virtual threads provide logical isolation for tasks using the same high-level programming model as threads.</li></ol><h4>How Virtual Threads Work</h4><ul><li>Virtual threads run on a small number of <strong>carrier platform threads</strong> (provided by the OS).</li><li>The JVM scheduler dynamically assigns virtual threads to carrier threads.</li><li>When a virtual thread is suspended (e.g., during a blocking operation), the carrier thread becomes free to run other tasks.</li><li>This makes virtual threads ideal for workloads involving blocking I/O or many short-lived tasks.</li></ul><h4>Code example</h4><pre>import java.util.concurrent.ExecutorService;<br>import java.util.concurrent.Executors;<br><br>public class VirtualThreadsAPIServer {<br>    public static void main(String[] args) {<br>        <br>        // Create a virtual thread executor<br>        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();<br><br>        // Simulate handling 10,000 incoming requests<br>        for (int i = 1; i &lt;= 10_000; i++) {<br>            int requestId = i;<br><br>            // Submit each request as a task to the virtual thread executor<br>            executor.submit(() -&gt; handleRequest(requestId));<br>        }<br><br>        // Shutdown the executor gracefully after all requests are processed<br>        executor.shutdown();<br>    }<br><br>    <br>    private static void handleRequest(int requestId) {<br>        // Simulate handling an API request<br>    }<br>}</pre><h4>Virtual Threads v/s Platform Threads</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fEQdgHG2VU017zyx-VDETg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B8BHT024uy9I3PGX7WXqmQ.png" /></figure><h4>Performance Benefits using Virtual Thread</h4><ol><li>Virtual threads consume <strong>a few kilobytes per thread</strong> instead of megabytes, enabling the JVM to handle <strong>millions of concurrent threads</strong>. Their lightweight nature allows highly-concurrent systems (e.g., API servers, microservices) to scale to massive workloads without hitting resource limits.</li><li>Virtual threads do not block <strong>carrier threads</strong> (the limited pool of OS threads backing virtual threads). If a virtual thread is suspended while waiting for I/O (e.g., Thread.sleep() or database calls), the carrier thread is freed for other tasks.</li><li>Virtual threads allocate memory for their stacks dynamically, using <strong>a few kilobytes</strong> when idle and growing only when executing tasks that require larger stacks.</li><li>Virtual threads reduce the CPU overhead caused by excessive thread context-switching, improving throughput and responsiveness.</li></ol><blockquote>Note that, if the task only has CPU related calls, and NO blocking I/O, Virtual threads won’t give NO performance benefit.</blockquote><p>==============================================================</p><h3>EPILOGUE</h3><p>Thanks for your patience reading. <strong><em>Clap</em></strong> if you liked the article.Follow me <a href="https://medium.com/@sayantanbanerjee_91303">here</a>, in <a href="https://github.com/SayantanBanerjee16">GitHub</a> and <a href="https://www.linkedin.com/in/sayantan-banerjee-19040b175/">Linked-in</a>. Stay happy and safe.</p><p>Would like to end this article with this quote by Brian Goetz —</p><blockquote>“It is far easier to design a class to be thread-safe than to retrofit it for thread safety later.”</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=150f3ab6c1be" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Java Programming — Concise Summary]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/java-programming-concise-summary-5a0dcc7544cf?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/5a0dcc7544cf</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[coding]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Fri, 22 Aug 2025 16:10:27 GMT</pubDate>
            <atom:updated>2025-08-22T16:10:27.847Z</atom:updated>
            <content:encoded><![CDATA[<h3>Java Programming — Concise Summary</h3><blockquote>“The ratio of time spent reading (code) versus writing is well over 10 to 1 … (therefore) making it easy to read makes it easier to write.”― Robert C. Martin</blockquote><h3>PROLOGUE</h3><ol><li><strong>Platform Independence -&gt;</strong> once we have the <strong><em>.jar </em>(Java ARchive)</strong> file, which is a package file format used to aggregate multiple Java class files, associated metadata, and resources into a single file for distribution and deployment, we can take it and run it at any Operating Systems. A JVM, or <strong>Java Virtual Machine</strong>, is a program that enables computers to run Java code. It acts as an intermediary between the <em>Java code (bytecode) and the underlying hardware and operating system</em>, allowing Java to be platform-independent.</li><li><strong>Object Orient Language</strong> -&gt; An object is essentially a self-contained entity that bundles together data (attributes) and the operations (methods) that can be performed on that data. These objects are often created based on blueprints called classes.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W3zQY5Fxa0mZD47gdVgv4g.png" /></figure><h4>JDK v/s JVM v/s JRE — Summary</h4><p>JVM is the engine that executes Java bytecode, the <strong>JRE (Java Runtime Environment) </strong>provides the runtime environment to run Java applications by including the JVM and necessary libraries, and the <strong>JDK (Java Development Kit)</strong> is the comprehensive toolkit for developing Java applications, including the JRE and development tools.</p><blockquote>JDK contains JRE which in turn contains JVM.</blockquote><h4>C++ v/s JAVA</h4><ol><li>C++ is NOT platform independent, whereas Java is.</li><li><strong>Java doesn’t support multiple inheritance (multi parent class, one base)</strong>, where as C++ supports it.</li><li>Java automatically does <strong><em>garbage collection</em></strong>, whereas C++ programmer has to do memory management, i.e, remove object from memory.</li><li>C++ we have concepts of pointers (access to internal memory). Java doesn’t have such concept.</li></ol><h3>INDEX</h3><ol><li>JAVA — Wrapper Classes , Autoboxing, and Castings</li><li>String, StringBuffer, and StringBuilder in Java</li><li>Object Oriented Programming with deep dive on Inheritance</li><li>Interface and Abstract Class</li><li>Constructor , super() , this()</li><li>Modifiers in JAVA</li><li>Some Advance Concepts in JAVA — <br>InstanceOf operator , Coupling , Cohesion, Inner Class, Static Inner Class, Generic, Varargs, Garbage Collector, Serialization and Deserialization, Static and Members Initializer Block</li><li>Exception Handling</li></ol><h3>JAVA — Wrapper Classes , Autoboxing, and Castings</h3><p>Wrapper classes in Java are used to provide <strong>object representation</strong> for primitive types.</p><p>Reasons why we need wrapper classes :-</p><ol><li>NULL is a possible value.</li><li>Use it in a collection.</li><li>Methods that support Object creation from other types.</li></ol><pre>Integer num = new Integer(10); // Integer num wrapper over int value 10<br>Integer num = new Integer(&quot;123&quot;); // Integer wrapper over string value &quot;123&quot;</pre><p><strong>Autoboxing</strong>: Automatically converts a primitive type into its corresponding wrapper class object. And it’s reverse is called <strong>unboxing.</strong></p><pre>Integer num = 456; // Possible due to Autoboxing<br><br>Integer numWrapper = new Integer(10); <br>numWrapper++; <br>// Again possible due to Unboxing and Autoboxing.<br>// i.e, initially numWrapper converts to primitive number, <br>// does the calculation,<br>// and again Autoboxing to corresponding wrapper class.</pre><blockquote>Autoboxing helps in saving memory by re-using already created wrapper objects.</blockquote><pre>// wrapper objects created using new are NOT same object<br>Integer nineA = new Integer(9);<br>Integer nineB = new Integer(9);<br>System.out.println(nineA == nineB); // false<br>System.out.println(nineA.equals(nineB)); // true<br><br><br>// BUT , with autoboxing, it is the same object<br>Integer nineC = 9;<br>Integer nineD = 9;<br>System.out.println(nineC == nineD); // true<br>System.out.println(nineC.equals(nineD)); // true</pre><p>With primitive wrapper classes,</p><ol><li>equality operator (==) checks for <strong>reference equality</strong></li><li>.equals() method in the Integer class checks for <strong>value equality</strong></li></ol><p><strong>Implicit Casting (Widening)</strong> -&gt; <strong><em>Automatic conversion</em></strong> from a smaller data type to a larger data type (lower precision to higher precision).<br>byte → short → int → long → float → double</p><pre>int myInt = 42;<br>double myDouble = myInt;  // int to double automatically<br>System.out.println(&quot;Implicit Casting: &quot; + myDouble);  // Output: 42.0</pre><p><strong>Explicit Casting (Narrowing) -&gt; <em>Manual conversion</em></strong> from a larger data type to a smaller data type (higher precision to lower precision).</p><pre><br>double myDouble = 99.99;  // double is larger than int<br>int myInt = (int) myDouble;  // Explicit casting (double -&gt; int)<br>System.out.println(&quot;Integer value after casting: &quot; + myInt);  // Output: 99<br><br><br>double largeValue = 123.456;  <br>int truncatedValue = largeValue;  // ERROR<br>// COMPILATION ERROR , we need to manually declare explicit casting        </pre><h3>String, StringBuffer, and StringBuilder in Java</h3><blockquote>Once String wrapper object is created, it cannot be modified.</blockquote><p><strong>That is, String objects are immutable. </strong>Modifications like concatenation or replacement create a new String object instead of modifying the existing one.</p><pre>String str = &quot;value1&quot;;<br>String str2 = str.concat(&quot; value2&quot;);<br>System.out.println(&quot;str : &quot; + str);   // value1<br>System.out.println(&quot;str2 : &quot; + str2); // value1 value2</pre><p>Now if we write <strong>String str = “value1”, </strong>then this value will be stored in a <em>“String constant pool” — which is inside the Heap memory</em>. If compiler finds a String literal, it checks if it exists in the pool. If it exists, it is reused. Else, created. So use String only when value doesn’t changes much.</p><p>Or if we write it as <strong>String str = new String(“value1”) </strong>always a new object is created.</p><pre>String str1= &quot;Value1&quot;;<br>String str2 = &quot;Value2&quot; ;<br>for (int i = 0; і &lt; 100000; ++i) {<br>  str1 = str1 + str2;<br>}<br><br>// NEVER USE &quot;concat&quot; or &quot;+&quot; on String wrapper objects inside loops, <br>// because every time a new Object is created</pre><p>Solution of above, use <strong>StringBuffer</strong>. StringBuffer allows the modification of strings without creating new objects. StringBuffer is designed to be thread-safe as its methods are <strong>synchronized</strong>, making it suitable for multi-threaded environments. <strong>StringBuffer </strong>not only saves memory, but also gives a very good performance on above issue.</p><pre>StringBuffer str = new StringBuffer(&quot;Hello&quot;);<br>str.append(&quot; World&quot;);  // Modifies the same object<br>System.out.println(str);  // Output: Hello World</pre><p>Like StringBuffer, <strong>StringBuilder</strong> is also mutable and allows string modifications. But <strong>StringBuilder</strong> is not synchronized, meaning it is <strong>not thread-safe</strong>. It’s the preferred choice for <em>single-threaded applications</em> when string modification is required.</p><pre>StringBuilder str = new StringBuilder(&quot;Hello&quot;);<br>str.append(&quot; World&quot;);  // Modifies the same object<br>System.out.println(str);  // Output: Hello World</pre><h3>Object Oriented Programming</h3><p>Class — Blueprint | Object — Instance | State of Object — Current Object data</p><blockquote>Every class in Java is a sub class of the <strong>class Object. </strong><em>That’s why we get methods like </em><strong><em>toString()</em></strong><em> or </em><strong><em>hashCode()</em></strong><em> or </em><strong><em>equals()</em></strong><em> out of the box for each class / wrapper class.</em></blockquote><p>Interesting points :-</p><ol><li>We can override the <strong><em>toString()</em></strong><em> </em>and <strong><em>other Object class methods</em></strong> to give our custom implementation.</li><li><strong><em>equals()</em></strong> is same as == while comparing two custom class objects. Note that they compare objects, not values, thus :-</li></ol><pre>Client client1 = new Client(25);<br>Client client2 = new Client(25);<br>Client client3 = client1;<br><br>//client1 and client2 are pointing to different client objects.<br>System.out.println(client1 == client2); //false<br>System.out.println(client1.equals(client2)); //false<br><br>//client3 and client1 are pointing to same client objects<br>System.out.println(client3 == client1); //true<br>System.out.println(client3.equals(client1)); //true</pre><p>We can override the equals() method to check for the content comparison.</p><h4>Inheritance</h4><p>Inheritance is a mechanism in Java where one class (subclass/child class) can acquire the properties and methods of another class (superclass/parent class). It promotes code reuse and establishes a hierarchical relationship between classes.</p><pre>public class Actor {<br>    public void performs(){<br>        System.out.println(&quot;Just Acting&quot;);<br>    };<br>}<br><br>//IS-A relationship. Hero is-a Actor<br>public class Hero extends Actor {<br>    public void performs(){<br>        System.out.println(&quot;Acting with Fighting&quot;);<br>    };<br>}<br><br>//IS-A relationship. Comedian is-a Actor<br>public class Comedian extends Actor {<br>    public void performs(){<br>        System.out.println(&quot;Acting With Comedy&quot;);<br>    };<br><br>    public void makesPeopleLaugh(){<br>        System.out.println(&quot;Yes&quot;);<br>    };<br>}<br><br>// A super class reference variable can hold object of sub class<br>Actor actor1 = new Comedian(); <br>Actor actor2 = new Hero();<br><br>actor1.performs(); // Acting With Comedy<br>actor2.performs(); // Acting with Fighting<br>// Both above is example of Polymorphism<br><br>actor1.makesPeopleLaugh(); // BUT this throws COMPILATION ERROR<br>// why?? because actor1 is created based on instance of Actor class<br><br>// How to fix?<br>// Cast actor1 to the Comedian type to access the performComedy() method<br>((Comedian) actor1).makesPeopleLaugh();</pre><blockquote>In above example, we can see <strong>a super class reference variable can hold object of sub class, like actor1 holding Comedian().</strong></blockquote><p><strong>Overloading —</strong></p><ol><li>Defining multiple methods with the <strong>same name</strong> but <strong>different parameter lists</strong> (signature).</li><li>Inheritance not required, can occur within the same class.</li><li>Works with both static and instance methods.</li><li>Static polymorphism (compile-time).</li></ol><pre>class Overloading {<br>    void print(int num) {<br>        System.out.println(&quot;Number: &quot; + num);<br>    }<br>    void print(String text) {<br>        System.out.println(&quot;Text: &quot; + text);<br>    }<br>}</pre><p><strong>Overriding —</strong></p><ol><li>Redefining a method from the superclass in the subclass to provide a specific implementation.</li><li>Inheritance is required, occurs between a superclass and subclass.</li><li>Only works with <strong>instance methods</strong>. Static methods cannot be overridden (they are hidden).</li><li>Dynamic polymorphism (runtime).</li></ol><pre>// Overriding example<br><br>class Parent {<br>    void message() {<br>        System.out.println(&quot;Message from Parent&quot;);<br>    }<br>}<br><br>class Child extends Parent {<br>    @Override<br>    void message() {<br>        System.out.println(&quot;Message from Child&quot;);<br>    }<br>}</pre><h3>Interface and Abstract Class</h3><h4>Interface</h4><p>In Java, an <strong>interface</strong> is a reference type that is somewhat similar to a class, but it is used to specify a contract that a class must follow.</p><blockquote>Interface is used for defining a contract without imposing structure. Example :-</blockquote><blockquote>java.util.Comparator -&gt; Provides a way to define custom comparison logic for objects, enabling sorting with different criteria. Classes implementing <em>Comparator</em> provide the implementation for the <strong><em>int compare(T o1, T o2);</em></strong> method.</blockquote><blockquote>java.lang.Runnable -&gt; Defines the contract for a task that can be executed in a separate thread. Classes implementing Runnable provide the implementation for the run() method.</blockquote><p><strong>Key Characteristics of Interfaces:</strong></p><ol><li><strong>Abstract Methods</strong>:<br>Interfaces can contain method declarations without any implementations (prior to Java 8, all methods in an interface were abstract). All methods are implicitly abstract.</li><li><strong>Default Methods</strong>:<br>Since Java 8, interfaces can have methods with default implementations using the default keyword. These provide a default implementation for methods that implementing classes <strong>can either use or override.</strong></li><li><strong>Static Methods</strong>:<br>Since Java 8, interfaces can also have static methods. <strong>Cannot be overridden<em> </em></strong>by implementing classes. Their implementation is fixed within the interface.<strong><em> </em></strong>Also they are called using <strong><em>InterfaceName.staticMethod()</em></strong></li><li><strong>Constant Fields</strong>:<br>All fields in an interface are implicitly public, static, and final. They behave like constants.</li><li><strong>No Constructors</strong>:<br>Interfaces cannot have constructors or instance variables.</li><li><strong>Multiple Inheritance</strong>:<br>A class can implement multiple interfaces, which allows Java to achieve multiple inheritance indirectly.</li><li><strong>Visibility</strong>:<br>A method of an abstract class can have any visibility.</li></ol><pre>// Example of a complex interface having both static and default methods<br><br>public interface Vehicle { // Implicitly public and abstract<br>    void start(); // Abstract method (Implicitly public and abstract)<br>    <br>    default void stop() {<br>        System.out.println(&quot;Stopping the vehicle...&quot;);<br>    }<br>    <br>    static void service() {<br>        System.out.println(&quot;Servicing the vehicle...&quot;);<br>    }<br>}<br><br>public class Car implements Vehicle {<br>    @Override<br>    public void start() {<br>        System.out.println(&quot;The car starts...&quot;);<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        Car myCar = new Car();<br>        myCar.start();       // Calls overridden method<br>        myCar.stop();        // Calls default method from interface<br>        Vehicle.service();   // Calls static method from interface<br>    }<br>}</pre><p>Few points on interface :-</p><ol><li>Interface can extend another Interface.</li><li>All methods have to be public.</li><li>All fields in an interface are implicitly public, static, and final. They behave like constants.</li><li>Class <strong>DON’T extend</strong> multiple interfaces (common mistake while saying,) But it can <strong>IMPLEMENT</strong> multiple interfaces.</li></ol><h4>Abstract Class</h4><p>In Java, an <strong>abstract class</strong> is a class that cannot be instantiated directly and is meant to be subclassed.</p><blockquote>Abstract Class is used for base classes with shared structure and behavior.</blockquote><blockquote>An example of an abstract class in the JDK is <strong>AbstractMap</strong>, which is part of the Collections Framework. Its subclasses (which include HashMap, TreeMap, and ConcurrentHashMap) share many methods (including get, put, isEmpty, containsKey, and containsValue) that AbstractMap defines.</blockquote><p><strong>Key Characteristics of Abstract Class:</strong></p><ol><li><strong>Cannot Be Instantiated</strong>:<br>We cannot create an instance of an abstract class directly using the new keyword.</li><li><strong>Abstract Methods</strong>:<br>Abstract methods are methods that do not have any implementation and are declared using the abstract keyword. Subclasses must override and provide implementations for abstract methods. <strong>Abstract methods should always be inside some abstract class or interface, not within normal class, which will result in compilation error.</strong></li><li><strong>Concrete Methods</strong>:<br>Abstract classes can also contain fully implemented (concrete) methods.</li><li><strong>Constructors</strong>:<br>An abstract class can have constructors, and these constructors are called when a subclass object is created.</li><li><strong>Fields</strong>:<br>Abstract classes can define fields (both static and instance variables) that subclasses can inherit.</li><li><strong>Inheritance</strong>:<br>Abstract classes are typically part of an inheritance relationship where other classes extend them.</li></ol><pre>public abstract class Animal {<br>    // Abstract method<br>    abstract void makeSound();<br><br>    // Concrete method<br>    void sleep() {<br>        System.out.println(&quot;The animal is sleeping...&quot;);<br>    }<br>}<br><br>public class Dog extends Animal {<br>    @Override<br>    void makeSound() {<br>        System.out.println(&quot;The dog barks.&quot;);<br>    }<br>}<br><br>public class Cat extends Animal {<br>    @Override<br>    void makeSound() {<br>        System.out.println(&quot;The cat meows.&quot;);<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        Animal dog = new Dog();<br>        Animal cat = new Cat();<br><br>        dog.makeSound();  // Output: The dog barks.<br>        dog.sleep();      // Output: The animal is sleeping.<br><br>        cat.makeSound();  // Output: The cat meows.<br>        cat.sleep();      // Output: The animal is sleeping.<br><br>        Animal animal = new Animal(); // COMPILATION ERROR <br>       // as abstract class cannot be directly Instantiated <br>    }<br>}</pre><blockquote><strong>When to use Interface v/s when to use Abstract Class?</strong></blockquote><blockquote><em>Abstract classes can have both abstract and concrete methods, while interfaces only define abstract methods. Abstract classes can also have fields (variables), whereas interfaces can only have constants (static final fields). Interfaces are better suited for defining contracts that unrelated classes can implement, while abstract classes are more for building inheritance hierarchies with shared functionality.</em></blockquote><h3>Constructor , super() , this()</h3><h4>Constructors</h4><p>In Java, a <strong>constructor</strong> is a special type of method that is used to create and initialize an object of a class.</p><p><strong>Key Features of Constructors :-</strong></p><ol><li>Same Name as Class</li><li>No return type , not even void</li><li>They are invoked automatically when an object is created.</li><li><strong>Default Constructor</strong>: A no-argument constructor that is <strong>automatically provided by Java</strong> if no other constructors are defined.</li><li><strong>Parameterized Constructor</strong>: A constructor that accepts arguments to initialize fields with specific values.</li><li><strong>Constructor Overloading:</strong> Having <strong>multiple constructors with the same name</strong> but <strong>different parameters</strong> in a class. This provides flexibility to create objects in multiple ways.</li><li><strong>Copy Constructor: </strong>A copy constructor creates a new object by <strong>copying the values</strong> from an existing object. Though Java does not provide a built-in copy constructor, you can define one manually in your class by accepting another object of the same class as a parameter.</li></ol><pre>public class Student {<br>    String name;<br>    int age;<br><br>    // Parameterized constructor<br>    public Student(String name, int age) {<br>        this.name = name;<br>        this.age = age;<br>    }<br><br>    // Copy constructor<br>    public Student(Student other) {<br>        this.name = other.name;<br>        this.age = other.age;<br>    }<br><br>    public void display() {<br>        System.out.println(&quot;Name: &quot; + name + &quot;, Age: &quot; + age);<br>    }<br><br>    public static void main(String[] args) {<br>        // Parameterized constructor is called<br>        Student s1 = new Student(&quot;Alice&quot;, 22);<br><br>        // Copy constructor is called<br>        Student s2 = new Student(s1); // Copy values from s1 to s2<br>    }<br>}</pre><blockquote><strong>Things to note about copy constructor (difference with C++)</strong></blockquote><blockquote>In Java, when <strong>an object is passed by value into a function, or return from a function,</strong> a copy of the reference to that object is created and passed to the function. This means that both the original variable and the parameter inside the function point to the <strong>same object in memory.</strong></blockquote><blockquote>Therefore, a copy constructor is not called in this scenario. Java does not have the concept of a “copy constructor” in the same way C++ does, where a new object is created and initialized from an existing one when passed by value.</blockquote><blockquote>In Java, <strong>objects are always passed by reference value</strong>, or <strong>object returns by returning a reference to the object</strong>, meaning the value of the reference itself is copied.</blockquote><h4>super()</h4><p>The super() keyword is used in a subclass constructor to call the constructor of its superclass. If no super(...) call is made, <strong><em>Java implicitly calls the no-argument constructor of the superclass.</em></strong></p><pre>class Animal {<br>    Animal() {<br>        System.out.println(&quot;Animal constructor is called.&quot;);<br>    }<br>}<br><br>public class Dog extends Animal {<br>    Dog() {<br>        super(); // Even not provided, Implicit call will happen to Animal constructor<br>        // also if explicitly provided, <br>        // super() should be the first line, else COMPILATION ERROR<br>        System.out.println(&quot;Dog constructor is called.&quot;);<br>    }<br><br>    public static void main(String[] args) {<br>        Dog d = new Dog();<br>    }<br>}<br><br>// OUTPUT :-<br>// Animal constructor is called.<br>// Dog constructor is called.</pre><h4>this() for Constructor Chaining</h4><p>this() can be used to call another constructor of the same class. This is useful for <strong>constructor chaining</strong> to avoid code duplication.</p><pre>public class Student {<br>    private String name;<br>    private int age;<br><br>    // Default constructor<br>    public Student() {<br>        this(&quot;Unknown&quot;, 0); // Calls parameterized constructor with default values<br>    }<br><br>    // Parameterized constructor<br>    public Student(String name, int age) {<br>        this.name = name;<br>        this.age = age;<br>    }<br><br>    public void display() {<br>        System.out.println(&quot;Name: &quot; + name + &quot;, Age: &quot; + age);<br>    }<br><br>    public static void main(String[] args) {<br>        Student s1 = new Student(); // Calls default constructor<br>        Student s2 = new Student(&quot;Alice&quot;, 22); // Calls parameterized constructor<br><br>        s1.display(); // Output: Name: Unknown, Age: 0<br>        s2.display(); // Output: Name: Alice, Age: 22<br>    }<br>}</pre><h3>Modifiers in JAVA</h3><h4>Access Modifier</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0Yez89X9wd_61RCwvqBPiA.png" /></figure><h4>Final</h4><ol><li><strong>Final Variable — </strong>A final variable is <strong>a constant</strong>. Once initialized, its value <strong>cannot be changed</strong> during the entire lifetime of the program.</li><li><strong>Final Method — </strong>A method declared as final <strong>cannot be overridden</strong> in a subclass. This ensures the method&#39;s behavior remains fixed when the class is inherited.</li><li><strong>Final Class — </strong>A final class <strong>cannot be extended (inherited)</strong>. This is useful when you want to <strong>prevent inheritance</strong> to ensure that the class remains immutable or its functionality cannot be altered.</li></ol><h4><strong>Static Variables:</strong></h4><p>A <strong>static variable</strong> is shared across all instances of a class. It belongs to the class itself, not to individual objects, meaning there is <strong>only one copy</strong> of the static variable for the class, no matter how many objects of the class are created.</p><ol><li>Shared across all instances of a class.</li><li>Memory is allocated only once, at the time of class loading.</li><li>Can be accessed using the class name (e.g., ClassName.staticVariable) without needing to create an object of the class.</li><li>Typically used for constants or values that need to be shared among all instances of a class.</li></ol><pre>public class StaticVariableExample {<br>    static int counter = 0; // Static variable shared by all instances<br><br>    public StaticVariableExample() {<br>        counter++; // Increment counter for every object created via constructor<br>    }<br><br>    public static void main(String[] args) {<br>        System.out.println(&quot;Initial counter: &quot; + StaticVariableExample.counter);<br><br>        StaticVariableExample obj1 = new StaticVariableExample();<br>        StaticVariableExample obj2 = new StaticVariableExample();<br><br>        System.out.println(&quot;Counter after creating two objects: &quot; + StaticVariableExample.counter);<br>    }<br>}</pre><h4>Static Methods:</h4><p>A <strong>static method</strong> belongs to the class, not the instances of the class. It can be called directly using the class name without creating an object of the class.</p><ol><li>Can directly access <strong>static variables</strong> but not non-static (instance) variables or methods (since instance data requires an object for access).</li><li>Cannot use this or super keywords, as they are tied to object instances.</li><li>Typically used for <strong>utility or helper methods</strong> that perform operations not tied to specific objects.</li></ol><h4>Why Use Static Variables and Methods?</h4><ol><li><strong>Memory Efficiency</strong>: Static variables are stored in a special area called the <strong>class area</strong> of memory and are initialized only once, reducing memory consumption.</li><li><strong>Shared States</strong>: Static variables act as global variables within the class, helping maintain shared states.</li><li><strong>Utility Functions</strong>: Static methods are great for utility functions that do not depend on instance data (e.g., Math.pow() or Math.sqrt()).</li></ol><h3>Some Advance Concepts in JAVA</h3><h4>InstanceOf operator</h4><p>The instanceof operator is used in programming to check if an object is an instance of a specific class or subclass, or if it implements a particular interface.</p><pre>// SYNTAX :-<br>object instanceof ClassName</pre><ol><li><strong>object</strong>: The reference to the object you want to check.</li><li><strong>ClassName</strong>: The class or interface you want to compare the object against.</li></ol><blockquote>instanceof returns a boolean value true if the <strong>object is an instance of</strong> the<strong> class </strong>or a<strong> subclass </strong>of the class, or if the object implements the<strong> specified interface.</strong></blockquote><p><strong>Use cases of InstanceOf :-</strong></p><ol><li><strong>Type Checking for Down-casting: </strong>Used to ensure that an object belongs to a particular type before performing a downcast.</li><li><strong>Polymorphism: </strong>When dealing with a hierarchy of classes, we can use instanceof to determine the actual type of an object at runtime.</li><li><strong>Implementing Custom Logic: </strong>In scenarios involving multiple object types, instanceof helps execute specific logic based on the object&#39;s type. <strong><em>Although, polymorphism or overriding methods is preferred over instanceOf</em></strong>. For example, rather than checking the object&#39;s type explicitly and then executing logic, we can design your classes such that the logic is encapsulated within overridden methods.</li></ol><h4>Coupling</h4><p><strong>Coupling</strong> is the measure of how much one module relies on another. Low coupling means modules are independent and can function without heavily depending on others, making the system more flexible and easier to maintain.</p><blockquote><strong>Low coupling is for separation</strong> (modules interact minimally).</blockquote><h4>Cohesion</h4><p><strong>Cohesion</strong> is the measure of how closely related and focused the functionalities within a single module are. High cohesion means the module performs a specific, well-defined task, improving clarity, reusability, and maintainability.</p><blockquote><strong>High cohesion is for focus</strong> (modules do one thing well).</blockquote><h4>Inner Class</h4><p>An <strong>Inner Class</strong> is a non-static class defined inside another class. It is tightly bound to the instance of its enclosing (outer) class.</p><ol><li>Requires an instance of the outer class.</li><li>Can access both static and instance members of outer class.</li><li>Tightly coupled with outer class instance.</li><li><strong>Use Cases like — </strong>When a class logically belongs within another class (e.g., iterators, helpers, etc.) Or for implementing scenarios where the inner class works on data from the outer class instance.</li></ol><pre>class OuterClass {<br>    private String outerField = &quot;Outer Field&quot;;<br><br>    class InnerClass {<br>        void displayOuterField() {<br>            System.out.println(outerField); // Access outer class&#39;s private field<br>        }<br>    }<br><br>    void createInner() {<br>        InnerClass inner = new InnerClass();<br>        inner.displayOuterField();<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        OuterClass outer = new OuterClass();<br>        outer.createInner(); // Output: Outer Field<br>    }<br>}</pre><h4>Static Inner Class</h4><p>A <strong>Static Inner Class</strong> is declared within an outer class but does not require an instance of the outer class to work. It works like an independent static member.</p><ol><li>Does <strong>NOT</strong> require an instance of outer class.</li><li>Can access <strong>only static members </strong>of outer class.</li><li><strong>Use Cases like</strong> — Independent behavior, utility functions.</li></ol><pre>class OuterClass {<br>    private static String staticField = &quot;Static Field&quot;;<br><br>    static class StaticNestedClass {<br>        void displayStaticField() {<br>            System.out.println(staticField); // Access outer class&#39;s static field<br>        }<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();<br>        nested.displayStaticField(); // Output: Static Field<br>    }<br>}</pre><h4>Generics</h4><p>Generics provide <strong>flexibility</strong> by allowing you to define a class, interface, or method without specifying an exact data type upfront, and later you can specify the type when using them, by using <strong>type parameters</strong>.</p><p>Generics ensure that the data you work with is strongly typed at <strong>compile time</strong>, reducing the chances of runtime errors.</p><p>Commonly defined using T, E, K, V, etc. (e.g., T for <strong>Type</strong>, E for <strong>Element</strong>, K for <strong>Key</strong>, V for <strong>Value</strong>) but we can use any other valid identifier.</p><pre>// TYPE 1 :- Generic class definition<br>class Box&lt;T&gt; { // &#39;T&#39; is the type parameter<br>    private T value;<br><br>    public void set(T value) {<br>        this.value = value;<br>    }<br><br>    public T get() {<br>        return value;<br>    }<br>}<br><br>// TYPE 2:- Generic method definition<br>public static &lt;T&gt; T[] returnArray(T[] data) {<br>    return data;<br>}<br><br>// TYPE 3:- Generic Interface definition<br>interface Container&lt;T&gt; {<br>    void add(T item);<br>    T remove();<br>}</pre><p>Also we can <strong>restrict</strong> a Generic to a particular subset of types <strong>by extends.</strong></p><pre>class MyListRestricted&lt;T extends Number&gt; {<br>  // <br>  // <br>}<br><br>// now if we declare T as String, throws Compilation Error.</pre><h4>Variable Arguments in Java — Varargs</h4><p><strong>Variable Arguments (varargs)</strong> in Java allows us to pass <strong>a variable number of arguments</strong> of the same type to a method. Instead of defining a method with a fixed number of parameters, we can use varargs to accept <strong>zero or more arguments</strong> of the specified type.</p><blockquote>Note : We should always define ONLY the last parameter of a method as varargs, else it will throw compilation error! <strong>Use </strong><strong>... to define varargs.</strong></blockquote><p>Real-life example — Logs / String.format()</p><pre>String formatted = String.format(&quot;Hello, %s! You have %d new messages from %s!&quot;, &quot;Peter&quot;, 5, &quot;Tony Stark&quot;);<br><br>// The String.format() method is defined with varargs<br>public static String format(String format, Object... args);</pre><h4>Garbage Collection</h4><p>Garbage Collection is a name given to <strong>automatic memory management</strong> in Java. Aim of Garbage Collection is to Keep as much of heap available (free) for the program as possible. JVM removes objects on the heap which no longer have references from the heap.</p><blockquote>Programmatically, we can request (it’s just a request — Not an order) JVM to run Garbage Collection by calling <strong>System.gc()</strong> method.</blockquote><p>JVM might throw an OutOfMemoryException when memory is full and no objects on the heap are eligible for garbage collection.</p><h4>Serialization and Deserialization</h4><p><strong>Serialization</strong> in Java is the process of converting an object into a byte stream. This byte stream can then be <strong>stored</strong> (e.g., in a file or database) or <strong>transferred</strong> (e.g., over a network). Serialization makes it possible to save and later reconstruct an object’s state.</p><p>The reverse process — converting a byte stream back into an object — is called <strong>Deserialization</strong>.</p><p>Important points —</p><ol><li><strong>Usage:</strong> Implement the java.io.Serializable interface to the class.</li><li>Use transient keyword to mark fields that should NOT be serialized.</li><li>Static fields are <strong>NOT </strong>serialized because they are class-level variables (not instance-level).</li><li><strong>Inheritance</strong>: If the superclass is not Serializable, the subclass can still be serialized, but the state of the superclass will not be serialized.</li><li><strong>Deep Serialization</strong>: Objects containing references to other objects will serialize their dependencies as well, provided all dependent objects are also Serializable.</li></ol><h4>Static and Member Initializer Blocks</h4><ol><li><strong>Static Initializer Block</strong> — Executes <strong>once</strong> at the time of class loading, <em>before any object is created.</em> Used for initialization logic for static variables or configurations.</li><li><strong>Instance Initializer Block —</strong> Executes each time we create an object, <strong>before the constructor</strong> runs.</li></ol><pre>class Example {<br>    static int staticValue;<br><br>    int instanceValue;<br><br>    // Static initializer block<br>    static {<br>        staticValue = 42;<br>        System.out.println(&quot;Static Block Executed. staticValue = &quot; + staticValue);<br>    }<br><br>    // Instance initializer block<br>    {<br>        instanceValue = 100;<br>        System.out.println(&quot;Instance Block Executed. instanceValue = &quot; + instanceValue);<br>    }<br><br>    // Constructor<br>    public Example() {<br>        System.out.println(&quot;Constructor Executed&quot;);<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        Example obj1 = new Example();<br>        Example obj2 = new Example();<br>    }<br>}<br><br>// OUTPUT :- <br>Static Block Executed. staticValue = 42<br>Instance Block Executed. instanceValue = 100<br>Constructor Executed<br>Instance Block Executed. instanceValue = 100<br>Constructor Executed</pre><h3>Exception Handling</h3><p><strong>Exception Handling</strong> in Java is a mechanism to <strong>handle runtime errors</strong> and maintain program flow.</p><blockquote><strong>Error v/s Exception</strong></blockquote><blockquote>Error is used in situations when there is nothing a programmer can do about an error. Ex: StackOverflowError, OutOfMemoryError.</blockquote><blockquote>Whereas exception is used when a programmer can handle the exception.</blockquote><p>In Java, Throwable is the superclass of all errors and exceptions. It forms the root of the exception hierarchy, meaning all other exception and error classes in Java inherit directly or indirectly from Throwable</p><p><strong>Checked Exceptions</strong>:</p><ol><li>Exceptions checked at <strong>compile-time</strong>. The compiler checks for the <strong><em>handling or declaration</em></strong> of these exceptions, and a compilation error will occur if they are not addressed.</li><li>Example: IOException, SQLException</li><li>Checked exceptions are typically subclasses of the java.lang.Exception class, but not java.lang.RuntimeException</li><li>Must be <strong>handled using try-catch</strong>, i.e, if the exception occurs, control is transferred to a corresponding catch block, where we can implement logic to recover from the error / or else log it.</li><li>Also if don’t want to handle the exception ourselves, we can take alternative actions using throws. It informs the callers of the method that they need to handle these exceptions, either via try-catch blocks or by propagating them further using throws</li></ol><blockquote>Design pattern used in Exceptions is Chain Of Responsibility pattern.</blockquote><pre>public void readFileFromDB() { <br>    try {<br>        // Code that might throw an IOException or SQLException or Exception<br>    } catch (IOException | SQLException exception) {<br>        // Code logic what to do when exception happens<br>        log.error(exception);<br>    } catch (Exception exception) {<br>        // NOTE :- Specefic exception should be above generic exceptions<br>        // That is if we reverse the order, it would give COMPILATION ERROR<br>        log.error(exception);<br>    } finally {<br>        // Final block - always runs<br>        // Only case when Final block doesn&#39;t runs if JVM crash - system.exit()<br>        // Or some exception block within Final block itself<br>    }   <br>}<br><br><br>OR<br><br><br>public void readFileFromDB() throws IOException, SQLException, Exception { <br>    // It enable us not to act on it by having a Try-Catch<br>    // Rather inform callers of the method that they need to handle these<br>}</pre><p><strong>Unchecked Exceptions</strong>:</p><p>Unchecked exception are exceptions that are not checked at the compile time. In Java, <strong>exceptions under Error and RuntimeException classes are unchecked exceptions</strong>, everything else under throwable is checked.</p><blockquote>Unchecked exceptions, are generally considered to indicate programming errors that should ideally be prevented through proper coding practices rather than caught and handled at runtime.</blockquote><p>Example like :- ArithmeticException, NullPointerException</p><p><strong>Custom Exceptions:</strong></p><p>We can define our <strong>own exception class</strong> by extending Exception or RuntimeException , to throw exceptions due to custom logic.</p><p>Also the piece of code which will throw the exception, we need to declare it as <strong><em>throws Exception if it is a Checked exception.</em></strong></p><pre>// Custom exception class<br>class CustomException extends Exception {<br>    public CustomException(String message) {<br>        super(message); // Pass the error message to the Exception class<br>    }<br>}<br><br>public class Main {<br>    public static void main(String[] args) {<br>        try {<br>            validateAge(15); // Test with invalid age (less than 18)<br>        } catch (CustomException e) {<br>            System.out.println(&quot;Caught Exception: &quot; + e.getMessage());<br>        }<br>    }<br><br>    // Method that throws the custom exception<br>    // Declare throws CustomException as it is a checked exception<br>    public static void validateAge(int age) throws CustomException {<br>        if (age &lt; 18) {<br>            throw new CustomException(&quot;Age must be 18 or above!&quot;);<br>        }<br>        System.out.println(&quot;Age is valid!&quot;);<br>    }<br>}</pre><h3>EPILOGUE</h3><p>Thanks for your patience reading. <strong><em>Clap</em></strong> if you liked the article.</p><p>Follow me <a href="https://medium.com/@sayantanbanerjee_91303">here</a>, in <a href="https://github.com/SayantanBanerjee16">GitHub</a> and <a href="https://www.linkedin.com/in/sayantan-banerjee-19040b175/">Linked-in</a>. Stay happy and safe. Keep reading and remember one thing, “If you can solve something in some way, there is always a better way to solve it.”</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5a0dcc7544cf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Java — Functional Programming]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/java-functional-programming-a989cd3e17ef?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/a989cd3e17ef</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Sun, 10 Aug 2025 14:52:29 GMT</pubDate>
            <atom:updated>2025-08-10T14:54:50.592Z</atom:updated>
            <content:encoded><![CDATA[<h3>Java — Functional Programming</h3><blockquote>When learning something new, it is actually much harder to unlearn a bad practice first!</blockquote><h3>PROLOGUE</h3><p>Before functional programming, most development relied heavily on imperative programming paradigms, where the focus was on step-by-step instructions and mutable state. This often led to complex, error-prone code, as managing state and debugging side effects in large systems became challenging. Concurrency was harder to handle due to race conditions caused by shared mutable data. Code reusability and modularity were limited, making systems harder to maintain and evolve.</p><h4>Imperative Programming</h4><ol><li><strong>Step-by-step approach</strong>: Focuses on <em>how</em> to do things.</li><li><strong>Mutability</strong>: Variables can be updated or changed during execution.</li><li><strong>Control flow</strong>: Focuses on loops, conditional statements, and explicit instructions.</li><li><strong>Advantages:</strong> Efficiency for simple tasks, Better hardware utilisation, More intuitive for newcomers</li><li><strong>Disadvantages</strong>: Error-prone, Less reusable code, Verbose</li></ol><pre>import java.util.Arrays;<br>import java.util.List;<br><br>public class ImperativeProgramming {<br>    public static void main(String[] args) {<br>        List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);<br>        for (Integer number : numbers) {<br>            if (number % 2 == 0) {<br>                System.out.println(number);<br>            }<br>        }<br>    }<br>}</pre><h4>Functional Programming</h4><ol><li><strong>Declarative approach</strong>: Focuses on <em>what</em> to do rather than <em>how</em> to do it.</li><li><strong>Immutability</strong>: Values once created cannot be changed.</li><li><strong>Control flow</strong>: Uses <strong><em>streams</em></strong></li><li><strong>Higher-order functions</strong>: Functions can take other functions as arguments or return them.</li><li><strong>Lazy evaluation</strong>: Computation is delayed until the result is required.</li><li><strong>Parallel execution:</strong> Improving performance.</li><li><strong>Advantages:</strong> Modular Code, Ease of testing, Predictable</li><li><strong>Disadvantages</strong>: Performance overhead, Steep learning curve, Limited tools</li></ol><pre>import java.util.Arrays;<br>import java.util.List;<br><br>public class FunctionalProgramming {<br>    public static void main(String[] args) {<br><br>        List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);<br><br>        numbers.stream() // Streams<br>                .filter(number -&gt; number % 2 == 0) // Lambda function<br>                .forEach(System.out::println); // Method Reference<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GeacuSf-3vbKwzeEu6vUdg.png" /></figure><h3>Deep dive within Streams</h3><h4>Streams — Intro and <strong><em>Initialization</em></strong></h4><p>Streams, introduced in Java 8, provide a powerful abstraction for processing sequences of data in a <strong>functional</strong> and <strong>declarative style</strong>. They allow operations on data without mutating the original source while supporting lazy evaluation, parallel processing, and concise code.</p><p>A stream can be initialized from a lot of data, like collections, arrays, or custom data. Below is the <strong><em>different types of Stream Initialization</em></strong></p><pre>import java.util.List;<br>import java.util.stream.Stream;<br><br>public class DifferentTypesOfStreamInitialization {<br>    public static void main(String[] args) {<br>        <br>        // Type 1 :- From a Collection like List, Set, Map - .stream()<br>        List&lt;Integer&gt; list = List.of(1, 2, 3, 4, 5);<br>        Stream&lt;Integer&gt; streamFromList = list.stream();<br><br>        // Type 2 :- From an Array - Arrays.stream()<br>        String[] names = {&quot;John&quot;, &quot;Jane&quot;, &quot;Jack&quot;};<br>        Stream&lt;String&gt; arrayStreams = Arrays.stream(names);<br><br>        // Type 3 :- Directly using Stream.of() to create stream<br>        Stream&lt;String&gt; streamFromValues = Stream.of(&quot;Java&quot;, &quot;Streams&quot;, &quot;Example&quot;);<br><br>        // Type 4 :- Infinite Stream using Stream.generate() or Stream.iterate()<br>        Stream&lt;Integer&gt; infiniteStream = Stream.iterate(0, n -&gt; n + 1);<br>        infiniteStream.limit(5).forEach(System.out::println);<br><br>        // Type 5:- Using Stream.builder() to generate dynamically / runtime<br>        Stream&lt;String&gt; builderStream = Stream.&lt;String&gt;builder()<br>                                           .add(&quot;Apple&quot;)<br>                                           .add(&quot;Banana&quot;)<br>                                           .build();<br>        builderStream.forEach(System.out::println);<br><br>        // Type 6:- Create streams from optional<br>        Optional&lt;String&gt; optional = Optional.of(&quot;Hello!&quot;);<br>        Stream&lt;String&gt; optionalStream = optional.stream();<br>        optionalStream.forEach(System.out::println); // Output: Hello!<br><br>        // Type 7:- Other initialization includes stream from files, <br>        // empty streams, and streams from primitive values<br>        <br>    }<br>}</pre><h4>Streams — Intermediate and Terminal Operations</h4><p>Intermediate operations transforms or filters a stream, and it’s output is another stream. It is lazy by nature, it doesn’t invokes until and unless it is called by a terminal operation. Below code example has all sorts of <strong><em>intermediate operations</em></strong> we can use on streams.</p><pre><br>import java.util.*;<br>import java.util.stream.*;<br><br>public class StreamWithNumbers {<br>    public static void main(String[] args) {<br>        // List of numbers to process<br>        List&lt;Integer&gt; numbers = Arrays.asList(10, 20, 30, 40, 50, 20, 60, 70, 80, 10, 90);<br><br>        // Unified stream pipeline applying all intermediate operations<br>        Stream&lt;Integer&gt; processedStream = numbers.stream()<br>            .filter(num -&gt; num % 20 == 0)                         // Filter values<br>            .map(num -&gt; num / 10)                                 // Transform each number<br>            .flatMap(num -&gt; Stream.of(num, num * 2))              // Generate two values: number and number * 2<br>            .distinct()                                           // Remove duplicates<br>            .sorted()                                             // Sort numbers in natural order<br>            .peek(num -&gt; System.out.println(&quot;Current : &quot; + num))  // Debugging<br>            .skip(2)                                              // Skip the first 2 numbers<br>            .limit(3)                                             // Limit to 3 numbers<br>            .takeWhile(num -&gt; num &lt; 10)                           // Take numbers while they are &lt; 10<br>            .dropWhile(num -&gt; num &lt; 5);                           // Drop numbers while they are &lt; 5<br><br>        System.out.println(&quot;\nFinal Output After All Intermediate Operations:&quot;);<br>        processedStream.forEach(System.out::println);<br>    }<br>}<br><br>// ACTIVITY FOR READERS -&gt; <br>// what would be stream after every intermediate step <br>// and what is the final output?</pre><p><strong><em>Terminal operations</em></strong> are used to process the elements and produce results, either by consuming the stream data or collecting it. This comprehensive code includes operations such as forEach, toArray, collect, reduce, count, min, max, findFirst, findAny, anyMatch, allMatch, and noneMatch.</p><pre>import java.util.*;<br>import java.util.stream.*;<br>import java.util.function.*;<br><br>public class StreamTerminalOperations {<br>    public static void main(String[] args) {<br><br>        // List of numbers as the base dataset<br>        List&lt;Integer&gt; numbers = Arrays.asList(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);<br><br>        // 1. forEach - Perform an action for each element in the stream<br>        numbers.stream().forEach(num -&gt; System.out.print(num + &quot; &quot;)); // Output: 10 20 30 40 50 60 70 80 90 100<br><br>        // 2. toArray - Convert the stream into an array<br>        Integer[] numberArray = numbers.stream().toArray(Integer[]::new);<br>        System.out.println(Arrays.toString(numberArray)); // Output: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]<br><br>        // 3. collect - Collect the elements to a data structure (List, Set, Map, etc.)<br>        List&lt;Integer&gt; collectedList = numbers.stream().collect(Collectors.toList());<br>        System.out.println(collectedList); // Output: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]<br><br>        // 4. reduce - Reduce the stream into a single value (sum in this case)<br>        Integer sum = numbers.stream().reduce(0, Integer::sum);<br>        System.out.println(sum); // Output: 550<br><br>        // 5. count - Count the number of elements in the stream<br>        long count = numbers.stream().count();<br>        System.out.println(count); // Output: 10<br><br>        // 6. min - Find the smallest element<br>        Optional&lt;Integer&gt; minValue = numbers.stream().min(Integer::compareTo);<br>        minValue.ifPresent(System.out::println); // Output: 10<br><br>        // 7. max - Find the largest element<br>        Optional&lt;Integer&gt; maxValue = numbers.stream().max(Integer::compareTo);<br>        maxValue.ifPresent(System.out::println); // Output: 100<br><br>        // 8. findFirst - Find the first element<br>        Optional&lt;Integer&gt; firstValue = numbers.stream().findFirst();<br>        firstValue.ifPresent(System.out::println); // Output: 10<br><br>        // 9. findAny - Find any element (useful in parallel streams)<br>        Optional&lt;Integer&gt; anyValue = numbers.stream().findAny();<br>        anyValue.ifPresent(System.out::println); // Output: (Could be any number from the list)<br><br>        // 10. anyMatch - Returns true if any element matches the condition<br>        boolean anyMatch = numbers.stream().anyMatch(num -&gt; num &gt; 50);<br>        System.out.println(anyMatch); // Output: true<br><br>        // 11. allMatch - Returns true if all elements match the condition<br>        boolean allMatch = numbers.stream().allMatch(num -&gt; num &gt; 0);<br>        System.out.println(allMatch); // Output: true<br><br>        // 12. noneMatch - Returns true if no elements match the condition<br>        boolean noneMatch = numbers.stream().noneMatch(num -&gt; num &lt; 0);<br>        System.out.println(noneMatch); // Output: true<br>    }<br>}</pre><h4>Streams — Important Notes</h4><ol><li><strong><em>Streams DO NOT store data</em></strong>, they process data on the fly, thus they are very memory efficient, making it process large dataset!</li><li><strong>Streams are lazy,</strong> they are not invoked during any intermediate operations, i.e, they are <strong><em>only executed when a terminal operation</em></strong> is called.</li><li><strong>Streams are immutable,</strong> they do not modify on the original data. Instead after all the processing, we can call collect to collect the stream into a collection. So <em>immutability ensures functional purity and avoids side effects.</em></li><li>Streams can have N number of intermediate operations, but <strong><em>only one</em></strong> terminal operation.</li><li><strong>Streams cannot be reused</strong>, if a terminal operation is called, we cannot call the same stream with another terminal operation, instead we need to<strong><em> create another stream</em></strong> from the original data.</li><li><strong>Parallel Streams</strong> -&gt; As streams are stateless, i.e, no shared data, it is easy to run streams in parallel for better performance.</li></ol><p>Either create a parallel stream, which splits the data and processes elements in parallel.</p><pre>Stream&lt;Integer&gt; parallelStream = numbers.parallelStream();</pre><p>Or if we have an existing sequential Stream, we can process the data in parallel with</p><pre>numbers.stream()<br>       .parrallel()<br>       .forEach(System.out::println);</pre><h3>Deep dive within Functional Interfaces and Lambdas</h3><p>In Java, <strong>lambda functions (or lambda expressions) enable developers to pass code as data.</strong> They are especially useful for functional programming tasks and simplifying code, often reducing boilerplate when implementing interfaces with a single abstract method (such interfaces are called <strong>functional interfaces</strong>).</p><blockquote>Functional Interfaces are the core component of the Java Functional Programming Approach</blockquote><pre>SYNTAX :- <br>(arguments comma-separated) -&gt; { body / code block }<br><br>EXAMPLE :-<br>num -&gt; num % 20 == 0</pre><h4>Functional Interfaces in Java</h4><p>A functional interface is any interface that contains <strong>only one abstract method</strong>. To ensure that an interface is a functional interface, you can explicitly annotate it with @FunctionalInterface .</p><p>Let’s have a <strong>deep dive in one of the functional interface</strong> that the Java provides out-of-the-box.</p><pre>Predicate&lt;Integer&gt; isEven = x -&gt; x % 2 == 0; <br>System.out.println(isEven.test(4)); // true</pre><p>Now the above is the lambda function that we use, the background Java Functional Interface definition of <strong>Predicate</strong> is as follows :-</p><pre>@FunctionalInterface<br>public interface Predicate&lt;T&gt; {<br>    /**<br>     * Evaluates this predicate on the given argument.<br>     *<br>     * @param t the input argument<br>     * @return true if the input argument matches the predicate, otherwise false<br>     */<br>    boolean test(T t);<br><br>    // Other default methods for composition (e.g., and(), or(), negate())<br>}</pre><p>The lambda x -&gt; x % 2 == 0 is shorthand for implementing the test() method in the Predicate interface. Behind the scenes, this is equivalent to writing:</p><pre>public class IsEven implements Predicate&lt;Integer&gt;() {<br>    @Override<br>    public boolean test(Integer x) {<br>        return x % 2 == 0;<br>    }<br>};</pre><p>In Java, the creation of a lambda expression involves both <strong>compile-time</strong> and <strong>runtime</strong> processes:</p><p><strong>Compile-Time</strong>:</p><ol><li>The compiler performs type checking and infers parameter and return types based on the target functional interface.</li><li>Instead of creating a dedicated class for the lambda, it generates an invokedynamic instruction and a metadata &quot;recipe&quot; for constructing the lambda at runtime.</li><li>The lambda’s body is placed in a private static or instance method of the enclosing class.</li></ol><p><strong>Runtime</strong>:</p><ol><li>When the invokedynamic instruction is executed, the JVM uses the LambdaMetafactory to dynamically generate a lightweight class implementing the functional interface.</li><li>The LambdaMetafactory links the invokedynamic call site to the method containing the lambda&#39;s body, creating the instance of the lambda dynamically.</li></ol><p>In short, the <strong>compiler prepares</strong> the structure for the lambda, and the <strong>JVM dynamically generates and instantiates</strong> the lambda at runtime using the LambdaMetafactory</p><h3>Common Built-in Functional Interfaces in Java</h3><p>The java.util.function package provides several built-in functional interfaces for common use cases:</p><ol><li><strong>Predicate</strong>: Represents a boolean-valued function, i.e, tests a condition. (same example as above)</li><li><strong>Consumer: </strong>Represents an operation that accepts a single input and returns no result.</li></ol><pre>Consumer&lt;String&gt; print = s -&gt; System.out.println(s);<br>print.accept(&quot;Hello, Java!&quot;); // Hello, Java!</pre><p>The functional interface of <strong>Consumer </strong>is as follows :-</p><pre>@FunctionalInterface<br>public interface Consumer&lt;T&gt; {<br>    /**<br>     * Performs this operation on the given argument.<br>     *<br>     * @param t the input argument<br>     */<br>    void accept(T t);<br><br>    // Other default methods for composition like &quot;andThen&quot;<br>}</pre><p>Thus our lambda s -&gt; System.out.println(s) would be same as below :-</p><pre>public class Print implements Consumer&lt;Integer&gt;() {<br>    @Override<br>    public void accept(String str) {<br>        System.out.println(s);<br>    }<br>};</pre><p>3. <strong>Function : </strong>Represents a function that accepts one argument and produces a result.</p><pre>Function&lt;Integer, String&gt; convertToString = x -&gt; &quot;Number: &quot; + x;<br>System.out.println(convertToString.apply(10)); // Number: 10</pre><p>The functional interface of <strong>Function </strong>is as follows :-</p><pre>@FunctionalInterface<br>public interface Function&lt;T, R&gt; {<br>    /**<br>     * Applies this function to the given argument.<br>     *<br>     * @param t the input argument<br>     * @return the function result<br>     */<br>    R apply(T t);<br><br>    // Other default methods for composition like &quot;andThen&quot; and &quot;compose&quot;<br>}</pre><p>Thus our lambda x -&gt; &quot;Number: &quot; + x would be same as below :-</p><pre>public class ConvertToString implements Function&lt;Integer, String&gt;() {<br>    @Override<br>    public String apply(Integer x) {<br>        return &quot;Number: &quot; + x;<br>    }<br>};</pre><p>4. <strong>Supplier: </strong>Represents a function that supplies a value without taking any input.</p><pre>Supplier&lt;String&gt; supplier = () -&gt; &quot;Hello, Java!&quot;;<br>System.out.println(supplier.get()); // Hello, Java!</pre><p>5. Other Functional Interfaces that comes out of the box are like <strong>BiFunction </strong>(two input, one output)<strong>, BiPredicate </strong>(two input, boolean output)<strong>, UnaryOperator (</strong>one input, specialization of function, where the input and output types are the same)<strong> , BinaryOperator (</strong>two inputs, specialization of function, where the input and output types are the same)<strong>, BiConsumer </strong>(two inputs, no output), etc.</p><p>6. While using Primitive data types, <strong><em>for better performance</em></strong>, directly use <strong>IntBinaryOperator</strong> instead of BinaryOperator, and same like <strong>IntPredicate</strong>, <strong>IntFunction</strong>, <strong>IntConsumer</strong>, etc. And similarly for <strong>Double</strong> and <strong>Long</strong>.</p><h3>Summary of Functional Programming using Streams, Lambdas with In-built Java Functional Interfaces</h3><pre>import java.util.*;<br>import java.util.function.*;<br>import java.util.stream.*;<br><br>public class FunctionalProgramming {<br>    public static void main(String[] args) {<br><br>        List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);<br><br>        // Predicate: Check if a number is even<br>        Predicate&lt;Integer&gt; isEven = n -&gt; n % 2 == 0;<br><br>        // Function: Convert a number to a formatted string<br>        Function&lt;Integer, String&gt; formatNumber = n -&gt; &quot;Number: &quot; + n;<br><br>        // Consumer: Print a string<br>        Consumer&lt;String&gt; print = System.out::println;<br><br>        // Use Predicate with filter(), Function with map(), and Consumer with forEach()<br>        numbers.stream()<br>               .filter(isEven)                // Filter even numbers<br>               .map(formatNumber)             // Convert numbers to strings<br>               .forEach(print);               // Print each formatted string<br>    }<br>}</pre><p>Or for another extended example</p><pre>import java.util.*;<br>import java.util.function.*;<br>import java.util.stream.*;<br><br>public class GenericExample {<br>    public static void main(String[] args) {<br><br>        List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);<br><br>        Predicate&lt;Integer&gt; isEven = n -&gt; n % 2 == 0;                    <br>        Function&lt;Integer, String&gt; evenFormatMapper = n -&gt; &quot;Even: &quot; + n;  <br>        Consumer&lt;String&gt; print = System.out::println;                  <br>        processStream(numbers.stream(), isEven, evenFormatMapper, print);<br><br><br>        Function&lt;Integer, String&gt; oddFormatMapper = n -&gt; &quot;Odd: &quot; + n;<br>        Predicate&lt;Integer&gt; isOdd = n -&gt; n % 2 != 0;<br>        processStream(numbers.stream(), isOdd, oddFormatMapper, print);<br>    }<br><br>    /**<br>     * Generic processing method to filter, map, and consume using higher-order functions.<br>     * <br>     * @param &lt;T&gt; Type of input elements<br>     * @param &lt;R&gt; Type of output elements after mapping<br>     * @param stream       Input stream<br>     * @param filterLogic  Predicate for filtering stream elements<br>     * @param mapLogic     Function for mapping stream elements<br>     * @param consumeLogic Consumer for consuming mapped elements<br>     */<br>    public static &lt;T, R&gt; void processStream(<br>        Stream&lt;T&gt; stream,<br>        Predicate&lt;T&gt; filterLogic,<br>        Function&lt;T, R&gt; mapLogic,<br>        Consumer&lt;R&gt; consumeLogic<br>    ) {<br>        stream.filter(filterLogic)     // Apply filtering logic<br>              .map(mapLogic)           // Apply mapping logic<br>              .forEach(consumeLogic);  // Apply consuming logic<br>    }<br>}</pre><h3><strong>EPILOGUE</strong></h3><p>Functional programming enhances modularity and maintainability with pure, reusable functions and predictable outputs, simplifying testing and debugging. Its immutability reduces race conditions, enabling efficient concurrency. Higher-order functions provide compact, expressive code for tasks like mapping and filtering, boosting productivity and scalability.</p><p>Thanks for your patience reading. <strong><em>Clap</em></strong> if you liked the article.</p><p>Follow me <a href="https://medium.com/@sayantanbanerjee_91303">here</a>, in <a href="https://github.com/SayantanBanerjee16">GitHub</a> and <a href="https://www.linkedin.com/in/sayantan-banerjee-19040b175/">Linked-in</a>. Stay happy and safe. Keep reading and remember one thing, “If you can solve something in some way, there is always a better way to solve it.”</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a989cd3e17ef" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SOLID Principles — An overview]]></title>
            <link>https://medium.com/@sayantanbanerjee_91303/solid-principles-an-overview-7ea48fab435b?source=rss-7ebcf9c6e96e------2</link>
            <guid isPermaLink="false">https://medium.com/p/7ea48fab435b</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[development]]></category>
            <dc:creator><![CDATA[Sayantan Banerjee]]></dc:creator>
            <pubDate>Sun, 20 Jul 2025 19:01:08 GMT</pubDate>
            <atom:updated>2025-07-20T19:01:37.282Z</atom:updated>
            <content:encoded><![CDATA[<h3>SOLID Principles — An overview</h3><blockquote>“A design that doesn’t take change into account, risks major redesign in the future.” ― <strong>Erich Gamma</strong></blockquote><p>The <strong>SOLID</strong> principles are a set of five design principles introduced by Robert C. Martin (popularly known as Uncle Bob) that help developers design <em>maintainable, scalable, and robust object-oriented software</em>. These principles enable the development of software systems that are easier to understand, extend, and modify without introducing bugs or causing unintended consequences.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V0QvxAWxtcsGUGffdEnv5g.png" /></figure><h3>Single Responsibility Principle (SRP)</h3><p>A class should have only one reason to change; it should have only one responsibility.</p><p>It means a class should focus on doing only one thing, and it should do it well. By adhering to SRP, you minimize dependencies and make your system easier to maintain.</p><h4><strong>Usage</strong></h4><ol><li>Helps in avoiding “overloaded classes” that perform unrelated tasks.</li><li>Improves readability and modularity.</li><li>Supports testability.</li></ol><h4>Code without SRP</h4><pre>// Violates SRP: This class is doing multiple jobs,<br>// i.e, handling both persistence and notification<br>public class Employee {<br>    public void saveToDatabase() {<br>        // Save employee details to database.<br>    }<br>    public void sendEmailNotification() {<br>        // Send email notification to the employee.<br>    }<br>}</pre><h4>Code with SRP</h4><pre>// Applying SRP<br>public class EmployeeDatabase {<br>    public void save(Employee employee) {<br>        // Save employee details to database.<br>    }<br>}<br><br>public class EmployeeNotification {<br>    public void sendEmail(Employee employee) {<br>        // Send email notification to the employee.<br>    }<br>}</pre><h3>Open/Closed Principle (OCP)</h3><p>A class should be open for extension but closed for modification.</p><p>Changes to functionality should be achieved by adding new code, not modifying existing code. This avoids breaking existing functionality when extending features.</p><h4><strong>Usage</strong></h4><ol><li>Helps in building systems loosely coupled to reduce errors due to code modification.</li><li>Encourages the use of abstraction and polymorphism.</li></ol><h4>Code without OCP</h4><pre>// Violates OCP because adding new operations <br>// requires modifying the existing Shape class<br><br>public class Shape {<br>    public String type;<br>    public double radius;<br>    public double height;<br>    public double width;<br><br>    public double calculateArea() {<br>        if (type.equals(&quot;circle&quot;)) {<br>            return Math.PI * radius * radius;<br>        } else if (type.equals(&quot;rectangle&quot;)) {<br>            return height * width;<br>        }<br>        return 0;<br>    }<br>}</pre><h4>Code with OCP</h4><pre>// Applying OCP: Use abstraction and extension instead of modification.<br>// With this setup, adding new shapes simply involves <br>// creating a new class that extends Shape<br><br>public abstract class Shape {<br>    public abstract double calculateArea();<br>}<br><br>public class Circle extends Shape {<br>    private double radius;<br><br>    public Circle(double radius) {<br>        this.radius = radius;<br>    }<br><br>    @Override<br>    public double calculateArea() {<br>        return Math.PI * radius * radius;<br>    }<br>}<br><br>public class Rectangle extends Shape {<br>    private double height;<br>    private double width;<br><br>    public Rectangle(double height, double width) {<br>        this.height = height;<br>        this.width = width;<br>    }<br><br>    @Override<br>    public double calculateArea() {<br>        return height * width;<br>    }<br>}</pre><h3>Liskov Substitution Principle (LSP)</h3><p>It ensures that a <strong>subclass does not modify the behavior of its superclas</strong>s in a way that surprises or breaks client code.</p><h4><strong>Usage</strong></h4><ol><li>Avoids unpredictable behavior in inheritance hierarchies.</li><li>Ensures proper polymorphism.</li></ol><h4>Code without LSP</h4><pre>// Example violating LSP: Rectangle and Square<br><br>public class Rectangle {<br>    protected int height;<br>    protected int width;<br><br>    public void setHeight(int height) {<br>        this.height = height;<br>    }<br><br>    public void setWidth(int width) {<br>        this.width = width;<br>    }<br><br>    public int getArea() {<br>        return height * width;<br>    }<br>}<br><br>public class Square extends Rectangle {<br>    @Override<br>    public void setHeight(int height) {<br>        super.height = height;<br>        super.width = height; // Force width = height<br>    }<br><br>    @Override<br>    public void setWidth(int width) {<br>        super.height = width; // Force height = width<br>        super.width = width;<br>    }<br>}</pre><h4>Code with LSP</h4><pre>// Fix: Follow LSP by avoiding forced assumptions<br><br>public interface Shape {<br>    int getArea();<br>}<br><br>public class Rectangle implements Shape {<br>    private int height;<br>    private int width;<br><br>    public Rectangle(int height, int width) {<br>        this.height = height;<br>        this.width = width;<br>    }<br><br>    @Override<br>    public int getArea() {<br>        return height * width;<br>    }<br>}<br><br>public class Square implements Shape {<br>    private int side;<br><br>    public Square(int side) {<br>        this.side = side;<br>    }<br><br>    @Override<br>    public int getArea() {<br>        return side * side;<br>    }<br>}</pre><h3>Interface Segregation Principle (ISP)</h3><p>A client should not be forced to depend on interfaces it does not use.</p><p>It suggests splitting large interfaces into smaller, more specific ones, so that clients only depend on the methods they actually require.</p><h4><strong>Usage</strong></h4><ol><li>Encourages creating smaller, cohesive interfaces.</li><li>Avoids unnecessary dependencies.</li></ol><h4>Code without ISP</h4><pre>// Violates ISP: Large interface forces unnecessary methods on classes<br><br>public interface Worker {<br>    void work();<br>    void eat();<br>}<br><br>public class RobotWorker implements Worker {<br>    @Override<br>    public void work() {<br>        System.out.println(&quot;Robot is working.&quot;);<br>    }<br><br>    @Override<br>    public void eat() {<br>        // Violates ISP - Robots don&#39;t eat!<br>        throw new UnsupportedOperationException(&quot;Robot doesn&#39;t eat.&quot;);<br>    }<br>}</pre><h4>Code with ISP</h4><pre>// Fix: Split larger interface into more specific ones<br>public interface Workable {<br>    void work();<br>}<br><br>public interface Eatable {<br>    void eat();<br>}<br><br>public class RobotWorker implements Workable {<br>    @Override<br>    public void work() {<br>        System.out.println(&quot;Robot is working.&quot;);<br>    }<br>}<br><br>public class HumanWorker implements Workable, Eatable {<br>    @Override<br>    public void work() {<br>        System.out.println(&quot;Human is working.&quot;);<br>    }<br><br>    @Override<br>    public void eat() {<br>        System.out.println(&quot;Human is eating.&quot;);<br>    }<br>}</pre><h3>Dependency Inversion Principle (DIP)</h3><p>High-level modules should not depend on low-level modules. Both should depend on abstractions.</p><blockquote>Abstractions should not depend on details; details should depend on abstractions.</blockquote><p>It encourages the use of interfaces or abstract classes to decouple high-level and low-level components in a system.</p><h4><strong>Usage</strong></h4><ol><li>Reduces direct coupling between classes.</li><li>Promotes flexibility through the use of dependency injection.</li></ol><h4>Code without DIP</h4><pre>// Violates DIP: Low-level classes are directly instantiated<br>public class Keyboard {<br>    // Keyboard implementation<br>}<br><br>public class Computer {<br>    private Keyboard keyboard;<br><br>    public Computer() {<br>        keyboard = new Keyboard(); // Hard dependency<br>    }<br>}</pre><h4>Code with DIP</h4><pre>// Fix: Use Dependency Injection and Abstraction<br>public interface InputDevice {<br>    void input();<br>}<br><br>public class Keyboard implements InputDevice {<br>    @Override<br>    public void input() {<br>        System.out.println(&quot;Keyboard input...&quot;);<br>    }<br>}<br><br>public class Computer {<br>    private InputDevice inputDevice;<br><br>    public Computer(InputDevice inputDevice) {<br>        this.inputDevice = inputDevice; // Dependency injected<br>    }<br>}</pre><h3>Benefits of SOLID Principles</h3><ol><li><strong>Maintainability</strong>: Code changes and extensions are less risky.</li><li><strong>Scalability</strong>: New functionalities can be added easily.</li><li><strong>Testability</strong>: Smaller, focused classes are easier to test.</li><li><strong>Reusability</strong>: Promotes reusable components.</li><li><strong>Flexibility</strong>: Reduces rigid dependencies between components.</li></ol><h3>Epilogue</h3><p>The SOLID principles are essential for writing clean, efficient, and maintainable code in object-oriented programming languages like Java. While these principles are guidelines and not hard rules, following them helps achieve a robust design that adheres to industry best practices. Using SOLID principles, combined with techniques like Dependency Injection, abstraction, and modularity, leads to scalable and developer-friendly systems.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7ea48fab435b" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>