<?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"><channel><title><![CDATA[Real Dev]]></title><description><![CDATA[Real Dev]]></description><link>https://realdev9868.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 11:04:14 GMT</lastBuildDate><atom:link href="https://realdev9868.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The Python Copy Bug That Looks Fine in Code Review]]></title><description><![CDATA[A shallow copy, a shared inner list, and a deepcopy fix, with every snippet run on Python 3.13 and the real output pasted.]]></description><link>https://realdev9868.hashnode.dev/the-python-copy-bug-that-looks-fine-in-code-review</link><guid isPermaLink="true">https://realdev9868.hashnode.dev/the-python-copy-bug-that-looks-fine-in-code-review</guid><category><![CDATA[Python]]></category><category><![CDATA[interview]]></category><category><![CDATA[beginner developers]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Real Dev]]></dc:creator><pubDate>Tue, 20 Oct 2026 03:52:16 GMT</pubDate><content:encoded><![CDATA[<p>Here is a bug that is easy to ship. A function takes a dict of default settings and hands each new user a copy. Someone adds a tag to one user. Every future user now has that tag too.</p>
<p>The code looks harmless. Each block below runs on its own, and the output under it is what Python 3.13 printed.</p>
<pre><code class="language-python">defaults = {"theme": "dark", "tags": ["new"]}
user_a = defaults.copy()
user_a["tags"].append("beta")

print("1. shallow copy, then append")
print("   defaults:", defaults)
print("   user_a  :", user_a)
print("   same inner list?", user_a["tags"] is defaults["tags"])
</code></pre>
<pre><code>1. shallow copy, then append
   defaults: {'theme': 'dark', 'tags': ['new', 'beta']}
   user_a  : {'theme': 'dark', 'tags': ['new', 'beta']}
   same inner list? True
</code></pre>
<p><code>.copy()</code> made a new dict, so <code>user_a</code> is not <code>defaults</code>. But the new dict holds the same list object under <code>"tags"</code>. You copied the outer box and kept pointing at the same contents. That is what "shallow" means. The rest of the interview answer is where it bites.</p>
<p>When you explain it out loud, draw it: two dict boxes, each with an arrow labelled <code>tags</code> going to the same list box. The drawing usually answers the follow-up questions. The <code>is</code> check in the code is the same picture in text. It asks whether two names point at one object, not whether the contents look equal.</p>
<p>The same thing happens with the other usual ways to copy. <code>list(x)</code>, <code>x[:]</code> and <code>x.copy()</code> all build a new outer list and share the inner ones:</p>
<pre><code class="language-python">nested = [["a"], ["b"]]
clones = [list(nested), nested[:], nested.copy()]
print("inner lists shared with the original:", [c[0] is nested[0] for c in clones])
</code></pre>
<pre><code>inner lists shared with the original: [True, True, True]
</code></pre>
<p>The bug is not specific to <code>.copy()</code>: it appears wherever you wanted an independent copy and wrote the cheap one.</p>
<p>Schoolabe's <a href="https://www.schoolabe.com/python-interview-questions">Python interview questions page</a> lists "What is the difference between shallow copy and deep copy in Python?" in its sample of the question bank. It describes the category as "copying vs references" gotchas that interviewers probe with short follow-ups. Interviewers rarely stop at a definition; they show five lines and ask what prints.</p>
<h2>Follow-ups worth rehearsing</h2>
<p>The fix everyone knows is <code>copy.deepcopy</code>, which walks the structure and copies everything it finds:</p>
<pre><code class="language-python">import copy

defaults = {"theme": "dark", "tags": ["new"]}
user_b = copy.deepcopy(defaults)
user_b["tags"].append("beta")

print("2. deepcopy, then append")
print("   defaults:", defaults)
print("   user_b  :", user_b)
</code></pre>
<pre><code>2. deepcopy, then append
   defaults: {'theme': 'dark', 'tags': ['new']}
   user_b  : {'theme': 'dark', 'tags': ['new', 'beta']}
</code></pre>
<p>Then there is the trap inside the trap: a shallow copy is not always broken. If you replace a value instead of mutating it, the copy is fine.</p>
<pre><code class="language-python">defaults = {"theme": "dark", "tags": ["new"]}
user_c = defaults.copy()
user_c["theme"] = "light"
user_c["tags"] = user_c["tags"] + ["beta"]

print("3. shallow copy, but rebind instead of mutate")
print("   defaults:", defaults)
print("   user_c  :", user_c)
</code></pre>
<pre><code>3. shallow copy, but rebind instead of mutate
   defaults: {'theme': 'dark', 'tags': ['new']}
   user_c  : {'theme': 'light', 'tags': ['new', 'beta']}
</code></pre>
<p>The <code>+</code> builds a brand new list and assigns it to the key in the copy only. <code>append</code> changes the list both dicts share. A small difference in how you write the line decides whether the bug exists, and that is the cause behind advice like "just always use deepcopy".</p>
<p>The list-of-lists cousin needs no call to <code>copy</code> at all:</p>
<pre><code class="language-python">grid = [[0] * 3] * 3
grid[0][0] = 9
print("repeated rows -&gt;", grid)

grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 9
print("comprehension   -&gt;", grid)
</code></pre>
<pre><code>repeated rows -&gt; [[9, 0, 0], [9, 0, 0], [9, 0, 0]]
comprehension   -&gt; [[9, 0, 0], [0, 0, 0], [0, 0, 0]]
</code></pre>
<p>Multiplying a list repeats the reference, not the row. Three rows, one list. The comprehension fixes it because <code>[0] * 3</code> is evaluated again for every row, so each row is a fresh list. If someone answers the grid question correctly but can't say why the comprehension works, keep asking; that is the part that matters. I think this is the better thing to ask in a mock interview, because it tells you more about whether someone understands references than the shallow versus deep definition does. You see it in grid and DP problems, where people build a table of zeros.</p>
<h2>Is deepcopy just the answer, then?</h2>
<p>Not by reflex. It copes with cycles, which surprised me: a list that contains itself deep-copies fine, and the last line of the next block checks it. So correctness isn't the objection. Cost is.</p>
<pre><code class="language-python">import copy
import time

rows = [list(range(100)) for _ in range(2000)]
times = []
for _ in range(10):
    start = time.perf_counter()
    copy.deepcopy(rows)
    times.append((time.perf_counter() - start) * 1000)
print(f"deepcopy of 2,000 lists of 100 ints: {min(times):.0f} to {max(times):.0f} ms over 10 runs")

loop = [1]
loop.append(loop)
clone = copy.deepcopy(loop)
print("self-referencing list deep-copied fine:", clone[1] is clone and clone is not loop)
</code></pre>
<pre><code>deepcopy of 2,000 lists of 100 ints: 26 to 35 ms over 10 runs
self-referencing list deep-copied fine: True
</code></pre>
<p>(Your timings will differ.)</p>
<p>That timing is from one machine, so run it yourself. The deepcopy figure held steady across my runs at roughly 25 to 35 ms. I won't quote a ratio against a shallow copy, because a shallow copy of that list is so quick that the timer mostly measures noise.</p>
<p>For a settings object copied once per signup, who cares. Inside a loop that runs a million times, you will care a great deal. The cheaper habit is to not share mutable things in the first place: build new lists with <code>+</code> or a comprehension, or store tags as a tuple so <code>append</code> isn't even possible. Then the shallow copy is safe and you never need the slow one.</p>
<p>My tests only used lists, dicts and ints. Objects with custom <code>__copy__</code> or <code>__deepcopy__</code> methods, or ones holding file handles or locks, behave differently, and I didn't try any of them. If an interviewer pushes in that direction, say you would check the class before assuming.</p>
<p>Write down what you think prints before you run it.</p>
<p><em>Written by the Schoolabe team, who build <a href="https://www.schoolabe.com">schoolabe.com</a>. Its homepage says tutorials, quizzes and practice are free, and that lessons and practice problems run in the browser.</em></p>
]]></content:encoded></item><item><title><![CDATA[Pandas Basics on a Real Dataset: A Hands-On Walkthrough with Palmer Penguins]]></title><description><![CDATA[Load, inspect, clean, filter, group and pivot real data with pandas. Every snippet was run and the outputs are real.]]></description><link>https://realdev9868.hashnode.dev/pandas-basics-on-a-real-dataset-a-hands-on-walkthrough-with-palmer-penguins</link><guid isPermaLink="true">https://realdev9868.hashnode.dev/pandas-basics-on-a-real-dataset-a-hands-on-walkthrough-with-palmer-penguins</guid><category><![CDATA[pandas]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Real Dev]]></dc:creator><pubDate>Mon, 19 Oct 2026 03:21:27 GMT</pubDate><content:encoded><![CDATA[<p>Many pandas tutorials build a five-row DataFrame from a dictionary, so the data is already clean. Real data has missing values, text where you expected numbers, and questions you have to turn into code yourself.</p>
<p>In this post we'll use a small but real dataset, <strong>Palmer Penguins</strong>, and go through a beginner's workflow: load, inspect, clean, filter, create columns, group, and summarise. Every snippet below runs as written, and the outputs shown are the actual outputs.</p>
<h2>The dataset</h2>
<p>Palmer Penguins has measurements for 344 penguins of three species (Adelie, Chinstrap, Gentoo) from three islands in the Palmer Archipelago, Antarctica. The data was collected by Dr. Kristen Gorman and the Palmer Station LTER, and it's released under CC0. It's small enough to understand completely and messy enough to be realistic.</p>
<p>You'll need Python 3 and pandas (<code>pip install pandas</code>). We'll read the CSV straight from a public URL.</p>
<h2>Step 1: Load and take a first look</h2>
<pre><code class="language-python">import pandas as pd

url = "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv"
df = pd.read_csv(url)

print(df.shape)
print(df.head())
</code></pre>
<pre><code>(344, 7)
  species     island  bill_length_mm  ...  flipper_length_mm  body_mass_g     sex
0  Adelie  Torgersen            39.1  ...              181.0       3750.0    MALE
1  Adelie  Torgersen            39.5  ...              186.0       3800.0  FEMALE
2  Adelie  Torgersen            40.3  ...              195.0       3250.0  FEMALE
3  Adelie  Torgersen             NaN  ...                NaN          NaN     NaN
4  Adelie  Torgersen            36.7  ...              193.0       3450.0  FEMALE
</code></pre>
<p>344 rows, 7 columns, and on row 3 we already have a fully missing record. Notice that <code>flipper_length_mm</code> and <code>body_mass_g</code> came in as floats (<code>181.0</code>). That's because pandas can't store <code>NaN</code> in a standard integer column, so a single missing value turns the whole column into floats.</p>
<h2>Step 2: Understand the columns</h2>
<pre><code class="language-python">df.info()
</code></pre>
<pre><code>RangeIndex: 344 entries, 0 to 343
Data columns (total 7 columns):
 #   Column             Non-Null Count  Dtype
---  ------             --------------  -----
 0   species            344 non-null    str
 1   island             344 non-null    str
 2   bill_length_mm     342 non-null    float64
 3   bill_depth_mm      342 non-null    float64
 4   flipper_length_mm  342 non-null    float64
 5   body_mass_g        342 non-null    float64
 6   sex                333 non-null    str
</code></pre>
<p>(On pandas 2.x, the text columns show up as <code>object</code> instead of <code>str</code>. Both mean the same thing here.)</p>
<p><code>info()</code> is the first thing to run on any new dataset. It tells you the column types and how many non-null values each column has. We can already see that the four measurements are missing 2 values each, and <code>sex</code> is missing 11.</p>
<p>Then get a quick numeric summary:</p>
<pre><code class="language-python">print(df.describe().round(1))
</code></pre>
<pre><code>       bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g
count           342.0          342.0              342.0        342.0
mean             43.9           17.2              200.9       4201.8
std               5.5            2.0               14.1        802.0
min              32.1           13.1              172.0       2700.0
...
max              59.6           21.5              231.0       6300.0
</code></pre>
<p>A body mass between 2.7 kg and 6.3 kg looks plausible for these species. Checking that the min and max make sense is a quick way to catch data-entry errors.</p>
<h2>Step 3: Find the missing values</h2>
<pre><code class="language-python">print(df.isna().sum())
</code></pre>
<pre><code>species               0
island                0
bill_length_mm        2
bill_depth_mm         2
flipper_length_mm     2
body_mass_g           2
sex                  11
</code></pre>
<p>To see the actual rows:</p>
<pre><code class="language-python">print(df[df.isna().any(axis=1)])
</code></pre>
<p>This shows 11 rows. Two of them (rows 3 and 339) have no measurements at all. The other nine have measurements but no <code>sex</code>.</p>
<p>What you do with missing data depends on your question. If you're studying body mass, rows with a recorded mass are still useful even when <code>sex</code> is unknown. So we drop rows only when the <em>measurements</em> are missing:</p>
<pre><code class="language-python">measurements = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]
clean = df.dropna(subset=measurements).copy()
print(clean.shape)   # (342, 7)
</code></pre>
<p>Two habits worth building early:</p>
<ul>
<li>Use <code>subset=</code> so you don't throw away rows you could have kept. A plain <code>df.dropna()</code> would drop all 11.</li>
<li>Use <code>.copy()</code> when you plan to add columns to a filtered DataFrame. It avoids the <code>SettingWithCopyWarning</code> you'd otherwise see on older pandas versions, and it makes clear that <code>clean</code> is a separate table.</li>
</ul>
<h2>Step 4: Count categories</h2>
<pre><code class="language-python">print(df["species"].value_counts())
</code></pre>
<pre><code>species
Adelie       152
Gentoo       124
Chinstrap     68
</code></pre>
<p>Which species lives on which island? <code>pd.crosstab</code> gives you a frequency table in one line:</p>
<pre><code class="language-python">print(pd.crosstab(df["species"], df["island"]))
</code></pre>
<pre><code>island     Biscoe  Dream  Torgersen
species
Adelie         44     56         52
Chinstrap       0     68          0
Gentoo        124      0          0
</code></pre>
<p>In this sample, Adelie penguins appear on all three islands, while Chinstrap appear only on Dream and Gentoo only on Biscoe. A table like this is useful later on, because a "difference between islands" could really be a difference between species.</p>
<h2>Step 5: Filter rows</h2>
<p>Boolean filtering is the pandas skill you'll use most. Wrap each condition in parentheses and combine them with <code>&amp;</code> (and) or <code>|</code> (or). Don't use the Python keywords <code>and</code>/<code>or</code> here.</p>
<pre><code class="language-python">heavy_gentoo = clean[(clean["species"] == "Gentoo") &amp; (clean["body_mass_g"] &gt; 5500)]
print(len(heavy_gentoo))                                     # 28
print(heavy_gentoo[["island", "sex", "body_mass_g"]].head())
</code></pre>
<pre><code>     island   sex  body_mass_g
221  Biscoe  MALE       5700.0
223  Biscoe  MALE       5700.0
231  Biscoe  MALE       5550.0
233  Biscoe  MALE       5850.0
235  Biscoe  MALE       5850.0
</code></pre>
<p><code>query()</code> does the same thing with a syntax that's often easier to read:</p>
<pre><code class="language-python">print(clean.query("island == 'Dream' and bill_length_mm &gt; 50").shape)   # (30, 7)
</code></pre>
<h2>Step 6: Create new columns</h2>
<p>Arithmetic on columns works on the whole column at once, so no loop is needed:</p>
<pre><code class="language-python">clean["body_mass_kg"] = clean["body_mass_g"] / 1000
clean["bill_ratio"] = (clean["bill_length_mm"] / clean["bill_depth_mm"]).round(2)

print(clean[["species", "body_mass_kg", "bill_ratio"]].head(3))
</code></pre>
<pre><code>  species  body_mass_kg  bill_ratio
0  Adelie          3.75        2.09
1  Adelie          3.80        2.27
2  Adelie          3.25        2.24
</code></pre>
<h2>Step 7: Group and summarise</h2>
<p>This is where pandas really helps. The question "What's the average body mass of each species?" becomes:</p>
<pre><code class="language-python">print(clean.groupby("species")["body_mass_g"].mean().round(0))
</code></pre>
<pre><code>species
Adelie       3701.0
Chinstrap    3733.0
Gentoo       5076.0
</code></pre>
<p>For several statistics at once, use <strong>named aggregation</strong>. It gives you clean column names, which helps when you export the result:</p>
<pre><code class="language-python">summary = (
    clean.groupby("species")
         .agg(count=("body_mass_g", "size"),
              avg_mass_g=("body_mass_g", "mean"),
              avg_flipper_mm=("flipper_length_mm", "mean"),
              avg_bill_ratio=("bill_ratio", "mean"))
         .round(1)
         .sort_values("avg_mass_g", ascending=False)
)
print(summary)
</code></pre>
<pre><code>           count  avg_mass_g  avg_flipper_mm  avg_bill_ratio
species
Gentoo       123      5076.0           217.2             3.2
Chinstrap     68      3733.1           195.8             2.7
Adelie       151      3700.7           190.0             2.1
</code></pre>
<p>In this data, Gentoo penguins are clearly the heaviest and have the longest flippers. Their bills are also long relative to their depth, which is why their bill ratio is the highest.</p>
<h2>Step 8: Two dimensions with a pivot table</h2>
<p>Average body mass by species <strong>and</strong> sex. Here we drop rows where sex is missing, because they can't go in either column:</p>
<pre><code class="language-python">by_sex = (
    clean.dropna(subset=["sex"])
         .pivot_table(index="species", columns="sex",
                      values="body_mass_g", aggfunc="mean")
         .round(0)
)
print(by_sex)
</code></pre>
<pre><code>sex        FEMALE    MALE
species
Adelie     3369.0  4043.0
Chinstrap  3527.0  3939.0
Gentoo     4680.0  5485.0
</code></pre>
<p>In every species in this sample, males are heavier on average. You worked that out yourself from raw data in about 30 lines of code.</p>
<h2>Step 9: Save your work</h2>
<pre><code class="language-python">summary.to_csv("penguin_summary.csv")
</code></pre>
<h2>Cheat sheet</h2>
<table>
<thead>
<tr>
<th>Task</th>
<th>Code</th>
</tr>
</thead>
<tbody><tr>
<td>Load CSV</td>
<td><code>pd.read_csv(path_or_url)</code></td>
</tr>
<tr>
<td>Size, types, nulls</td>
<td><code>df.shape</code>, <code>df.info()</code>, <code>df.isna().sum()</code></td>
</tr>
<tr>
<td>Numeric summary</td>
<td><code>df.describe()</code></td>
</tr>
<tr>
<td>Category counts</td>
<td><code>df["col"].value_counts()</code></td>
</tr>
<tr>
<td>Filter rows</td>
<td><code>df[(cond1) &amp; (cond2)]</code> or <code>df.query("...")</code></td>
</tr>
<tr>
<td>Drop missing (selectively)</td>
<td><code>df.dropna(subset=[...])</code></td>
</tr>
<tr>
<td>New column</td>
<td><code>df["new"] = df["a"] / df["b"]</code></td>
</tr>
<tr>
<td>Group stats</td>
<td><code>df.groupby("k").agg(name=("col", "mean"))</code></td>
</tr>
<tr>
<td>2-D summary</td>
<td><code>df.pivot_table(index=, columns=, values=, aggfunc=)</code></td>
</tr>
</tbody></table>
<h2>Try it in the browser</h2>
<p>If you'd rather not set anything up locally, our <a href="https://www.schoolabe.com/data-analysis-lab">Data Analysis Lab</a> has notebook-style pages for NumPy, Pandas and Matplotlib that run in the browser, using the same Palmer Penguins data. The <a href="https://www.schoolabe.com/data-analysis-lab/exercise-pandas-species-mass">mean body mass by species</a> exercise is a good way to check that Step 7 stuck.</p>
<p><strong>Your next challenge:</strong> find the island with the highest average flipper length <em>for Adelie penguins only</em> (they're the one species found on all three islands), and check whether the <code>sex</code> split looks similar on each island.</p>
<p>— Schoolabe team</p>
]]></content:encoded></item><item><title><![CDATA[Belady's Anomaly: FIFO vs LRU vs Optimal, Checked in Python]]></title><description><![CDATA[A GATE CS page-replacement walkthrough on one reference string, plus a script that shows how rare Belady's anomaly really is.]]></description><link>https://realdev9868.hashnode.dev/belady-s-anomaly-fifo-vs-lru-vs-optimal-checked-in-python</link><guid isPermaLink="true">https://realdev9868.hashnode.dev/belady-s-anomaly-fifo-vs-lru-vs-optimal-checked-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[gate]]></category><category><![CDATA[operating system]]></category><category><![CDATA[Computer Science]]></category><dc:creator><![CDATA[Real Dev]]></dc:creator><pubDate>Sat, 17 Oct 2026 14:40:59 GMT</pubDate><content:encoded><![CDATA[<p>Page replacement questions in GATE CS are mostly counting. You get a reference string, a number of frames and a policy, and you write a row of letters. The idea is easy; the marks go when you lose track of the oldest page late in the paper.</p>
<p>So the useful skill is a method you can run without thinking, plus a way to check yourself on practice problems. I wrote a small Python checker for the second part.</p>
<p>Schoolabe's GATE <a href="https://www.schoolabe.com/courses/gate-cs-operating-systems/gate-cs-operating-systems-memory-management">memory management chapter</a> uses this reference string with 3 frames as its worked example:</p>
<p><code>1 2 3 4 1 2 5 1 2</code></p>
<p>It says to count FIFO carefully with a queue, and it doesn't print the answer. Here are the three policies, then the numbers. The code blocks in this post are one file, in order; paste them together and it runs.</p>
<pre><code class="language-python">import random
from collections import OrderedDict, deque

def fifo(refs, frames):
    q, mem, faults, trace = deque(), set(), 0, []
    for p in refs:
        if p in mem:
            trace.append("hit")
        else:
            faults += 1
            if len(mem) == frames:
                mem.remove(q.popleft())
            q.append(p)
            mem.add(p)
            trace.append("F")
    return faults, trace

def lru(refs, frames):
    mem, faults, trace = OrderedDict(), 0, []
    for p in refs:
        if p in mem:
            mem.move_to_end(p)
            trace.append("hit")
        else:
            faults += 1
            if len(mem) == frames:
                mem.popitem(last=False)
            mem[p] = True
            trace.append("F")
    return faults, trace

def opt(refs, frames):
    mem, faults, trace = [], 0, []
    for i, p in enumerate(refs):
        if p in mem:
            trace.append("hit")
            continue
        faults += 1
        trace.append("F")
        if len(mem) &lt; frames:
            mem.append(p)
            continue
        rest = refs[i + 1:]
        # evict the page whose next use is furthest away (or never)
        victim = max(mem, key=lambda x: rest.index(x) if x in rest else float("inf"))
        mem.remove(victim)
        mem.append(p)
    return faults, trace

lesson = [1, 2, 3, 4, 1, 2, 5, 1, 2]
print("Lesson string with 3 frames:", lesson)
for name, policy in (("FIFO", fifo), ("LRU", lru), ("OPT", opt)):
    n, t = policy(lesson, 3)
    print(f"  {name}: {n} faults   {' '.join(t)}")
print("FIFO on the same string with 4 frames:", fifo(lesson, 4)[0], "faults")
</code></pre>
<pre><code>Lesson string with 3 frames: [1, 2, 3, 4, 1, 2, 5, 1, 2]
  FIFO: 7 faults   F F F F F F F hit hit
  LRU: 7 faults   F F F F F F F hit hit
  OPT: 5 faults   F F F F hit hit F hit hit
FIFO on the same string with 4 frames: 7 faults
</code></pre>
<p>Check the FIFO row by hand. Pages 1, 2, 3 fill the frames. Page 4 evicts 1, so the next 1 faults and evicts 2, so the next 2 faults and evicts 3. Page 5 evicts 4. Then 1 and 2 are both still in memory and hit. Seven faults.</p>
<p>For the hand trace I write the full frame contents under every reference, not just F or hit. For FIFO I underline the oldest page, and for LRU I rewrite the frames in recency order each time. It is slower, but when you go wrong you can see where.</p>
<p>LRU also gets seven here, so LRU does not always beat FIFO on a given string. Optimal gets five, and that is your floor. It needs the future, so no real system can do it. The chapter calls it the offline lower bound, and if your hand trace beats it, you have miscounted.</p>
<h2>The anomaly</h2>
<p>The chapter says FIFO can show Belady's anomaly, where more frames give more faults, and calls it a favourite trap. The lesson string above doesn't show it: FIFO with 4 frames also gives 7. The textbook string is the same one extended by three pages, <code>1 2 3 4 1 2 5 1 2 3 4 5</code>.</p>
<pre><code class="language-python">belady = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]
print("Classic string:", belady)
for name, policy in (("FIFO", fifo), ("LRU", lru)):
    for frames in (3, 4):
        print(f"  {name}, {frames} frames: {policy(belady, frames)[0]} faults")
</code></pre>
<pre><code>Classic string: [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]
  FIFO, 3 frames: 9 faults
  FIFO, 4 frames: 10 faults
  LRU, 3 frames: 10 faults
  LRU, 4 frames: 8 faults
</code></pre>
<p>FIFO goes from 9 faults to 10 when it gets a fourth frame. The anomaly was described by Belady, Nelson and Shedler in "An anomaly in space-time characteristics of certain programs running in a paging machine", <em>Communications of the ACM</em> 12(6), June 1969, pp. 349-353 (doi 10.1145/363011.363155).</p>
<p>LRU on the same string drops from 10 to 8, as you would expect. The reason is the stack property: the pages LRU holds with k frames are always a subset of what it holds with k+1 frames. FIFO keeps pages by arrival order, not by use, so it has no such property.</p>
<p>How common is this? I guessed "not very" and wanted a number, so I drew random strings of 12 references over pages 1 to 5, with a fixed seed so you can reproduce it, and compared 3 frames against 4.</p>
<pre><code class="language-python">random.seed(2026)
trials, bad_fifo, bad_lru = 1_000_000, 0, 0
for _ in range(trials):
    refs = [random.randint(1, 5) for _ in range(12)]
    if fifo(refs, 4)[0] &gt; fifo(refs, 3)[0]:
        bad_fifo += 1
    if lru(refs, 4)[0] &gt; lru(refs, 3)[0]:
        bad_lru += 1
print(f"{trials:,} random strings, 12 refs over pages 1-5, 3 frames vs 4:")
print(f"  FIFO faulted more with 4 frames: {bad_fifo}")
print(f"  LRU faulted more with 4 frames:  {bad_lru}")
</code></pre>
<pre><code>1,000,000 random strings, 12 refs over pages 1-5, 3 frames vs 4:
  FIFO faulted more with 4 frames: 1
  LRU faulted more with 4 frames:  0
</code></pre>
<p>One hit in a million. A first attempt with 100,000 strings found none, so I'd have written "never" if I'd stopped there. A single hit can't pin down a rate, though, so I also counted exhaustively with a small C program: of all 5^12 = 244,140,625 strings of 12 references over five pages, FIFO faults more with 4 frames than with 3 in exactly 120 of them. That is about 1 in 2 million. It also explains why the textbook string looks hand-built. Someone had to go looking for it.</p>
<p>Don't hunt for the anomaly in the trace. Look at the wording: of these three policies only FIFO can fault more with more frames, so a question asking which one can has its answer before you count anything. If the policy is LRU or Optimal, the anomaly can't be the point.</p>
<p>The checker is also good for something duller. Take any previous-year question with a string and a frame count, run all three policies, and compare with your hand trace. Schoolabe's GATE hub says its previous-year questions show questions and options only for now, and tells you to cross-check the official IIT answer key yourself. That is fair advice for any source, this one included.</p>
<p>CLOCK, the chapter's fourth policy, is not covered here.</p>
<p><em>Written by the Schoolabe team, who build <a href="https://www.schoolabe.com">schoolabe.com</a>. The <a href="https://www.schoolabe.com/gate">GATE CS section</a> lists free subject tutorials, topic quizzes and previous-year papers.</em></p>
]]></content:encoded></item><item><title><![CDATA[A 30-Day DSA Plan for Placements, Built Around Patterns (Not Problem Counts)]]></title><description><![CDATA[A common way to prepare for placement coding rounds is to open a list of hundreds of problems and start at number one. A month later, a new problem still…]]></description><link>https://realdev9868.hashnode.dev/a-30-day-dsa-plan-for-placements-built-around-patterns-not-problem-counts</link><guid isPermaLink="true">https://realdev9868.hashnode.dev/a-30-day-dsa-plan-for-placements-built-around-patterns-not-problem-counts</guid><category><![CDATA[DSA]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[Python]]></category><category><![CDATA[Career]]></category><dc:creator><![CDATA[Real Dev]]></dc:creator><pubDate>Thu, 15 Oct 2026 16:24:31 GMT</pubDate><content:encoded><![CDATA[<p>A common way to prepare for placement coding rounds is to open a list of hundreds of problems and start at number one. A month later, a new problem still feels like a blank page.</p>
<p>A better approach: learn a <strong>small set of patterns</strong> well enough that you recognise them in problems you've never seen. This post gives you a 30-day plan built that way, with a working Python template for each core pattern.</p>
<p>Thirty days won't make you an expert. But it can take you from "I don't know where to start" to "I can usually tell what kind of problem this is."</p>
<h2>Before Day 1: are you ready for DSA?</h2>
<p>DSA assumes you can already write loops, conditionals and functions comfortably. If a palindrome check or "second largest element" still feels slow, spend a week or two on basic logic first. Our <a href="https://www.schoolabe.com/logic-building">logic building track</a> covers conditions, loops, recursion, arrays and strings in that order, and it's a reasonable warm-up.</p>
<p>Pick <strong>one language</strong> for the whole month. The examples below use Python because it keeps templates short.</p>
<h2>How to spend each day</h2>
<p>Aim for about 2 hours:</p>
<ul>
<li><strong>20 min:</strong> learn or revise the pattern (read, or trace it by hand on paper).</li>
<li><strong>80 min:</strong> solve 2–3 problems that use it. Give yourself about 25 minutes per problem before you look at a hint.</li>
<li><strong>20 min:</strong> write a short note for each problem: what the pattern was, and what clue in the question should have pointed you to it.</li>
</ul>
<p>That last step matters most: you're learning the clues, not collecting solutions.</p>
<h2>Week 1 (Days 1–7): Arrays, strings and hashing</h2>
<p><strong>Days 1–2:</strong> Array basics, reversing, rotating, and stating the time complexity of everything you write.
<strong>Days 3–4:</strong> Strings: counting characters, anagrams, palindromes.
<strong>Days 5–6:</strong> Hash maps and sets: the "have I seen this before?" pattern.
<strong>Day 7:</strong> Revise and solve 3 mixed problems without notes.</p>
<p><strong>Pattern: hash map lookup.</strong> The clue is "find a pair or duplicate," where the brute-force answer is two nested loops.</p>
<pre><code class="language-python">def two_sum(nums, target):
    seen = {}                      # value -&gt; index
    for i, x in enumerate(nums):
        if target - x in seen:
            return [seen[target - x], i]
        seen[x] = i
    return []

assert two_sum([2, 7, 11, 15], 9) == [0, 1]
</code></pre>
<p>One pass, O(n) time, O(n) extra space. Say that trade-off out loud in an interview.</p>
<h2>Week 2 (Days 8–14): Two pointers, sliding window, prefix sums, binary search</h2>
<p><strong>Days 8–9:</strong> Two pointers on sorted arrays.
<strong>Days 10–11:</strong> Sliding window (fixed and variable size).
<strong>Day 12:</strong> Prefix sums.
<strong>Day 13:</strong> Binary search, including "binary search on the answer."
<strong>Day 14:</strong> Revision plus one timed set: 3 problems in 75 minutes.</p>
<p><strong>Pattern: two pointers.</strong> The clue is a sorted array, or "pairs, triplets, or in-place partitioning."</p>
<pre><code class="language-python">def pair_with_sum_sorted(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo &lt; hi:
        s = nums[lo] + nums[hi]
        if s == target:
            return (lo, hi)
        if s &lt; target:
            lo += 1
        else:
            hi -= 1
    return None

assert pair_with_sum_sorted([1, 3, 4, 6, 9], 10) == (0, 4)
</code></pre>
<p><strong>Pattern: variable sliding window.</strong> The clue is "longest/shortest <strong>contiguous</strong> subarray or substring such that…"</p>
<pre><code class="language-python">def longest_unique_substring(s):
    last = {}          # char -&gt; last index seen
    start = best = 0
    for i, ch in enumerate(s):
        if ch in last and last[ch] &gt;= start:
            start = last[ch] + 1     # shrink window past the repeat
        last[ch] = i
        best = max(best, i - start + 1)
    return best

assert longest_unique_substring("abcabcbb") == 3
</code></pre>
<p><strong>Pattern: prefix sum + hash map.</strong> The clue is "count subarrays whose sum equals k." Unlike a sliding window, it also works with negative numbers.</p>
<pre><code class="language-python">def count_subarrays_with_sum(nums, k):
    count, running = 0, 0
    freq = {0: 1}                  # prefix sum -&gt; how many times seen
    for x in nums:
        running += x
        count += freq.get(running - k, 0)
        freq[running] = freq.get(running, 0) + 1
    return count

assert count_subarrays_with_sum([1, 1, 1], 2) == 2
</code></pre>
<p><strong>Pattern: binary search.</strong> Write one version you trust and reuse it every time:</p>
<pre><code class="language-python">def lower_bound(nums, target):
    """First index i with nums[i] &gt;= target."""
    lo, hi = 0, len(nums)
    while lo &lt; hi:
        mid = (lo + hi) // 2
        if nums[mid] &lt; target:
            lo = mid + 1
        else:
            hi = mid
    return lo

assert lower_bound([1, 2, 4, 4, 7], 4) == 2
</code></pre>
<h2>Week 3 (Days 15–21): Linked lists, stacks, queues, recursion, trees</h2>
<p><strong>Days 15–16:</strong> Linked lists: reversal, fast/slow pointers (finding the middle, detecting a cycle).
<strong>Day 17:</strong> Stacks: valid parentheses, monotonic stack ("next greater element").
<strong>Day 18:</strong> Recursion and backtracking basics (subsets, permutations).
<strong>Days 19–20:</strong> Binary trees: DFS (pre/in/post-order) and level-order BFS.
<strong>Day 21:</strong> Revision.</p>
<p>If pointers feel abstract, <em>watch</em> them move: our <a href="https://www.schoolabe.com/dsa">DSA visualizers</a> let you step through arrays, linked lists, stacks/queues and sorting in the browser. Use them alongside problems, not instead of them.</p>
<p><strong>Pattern: monotonic stack.</strong> The clue is "next greater/smaller element" or "how many days until…"</p>
<pre><code class="language-python">def next_greater(nums):
    res = [-1] * len(nums)
    stack = []                     # indices with no answer yet
    for i, x in enumerate(nums):
        while stack and nums[stack[-1]] &lt; x:
            res[stack.pop()] = x
        stack.append(i)
    return res

assert next_greater([2, 1, 3, 2, 5]) == [3, 3, 5, 5, -1]
</code></pre>
<h2>Week 4 (Days 22–30): Graphs, heaps, intro to DP, mocks</h2>
<p><strong>Days 22–23:</strong> Graphs: adjacency lists, BFS, DFS, counting connected components.
<strong>Day 24:</strong> Heaps: top-k elements, merging sorted lists (<code>heapq</code> in Python, <code>priority_queue</code> in C++).
<strong>Days 25–27:</strong> Dynamic programming basics: climbing stairs, house robber, coin change, longest common subsequence.
<strong>Days 28–29:</strong> Two full mock rounds: 2 problems, 60 minutes, talk out loud as you go.
<strong>Day 30:</strong> Go back through your notes and redo every problem you marked "needed a hint."</p>
<p><strong>Pattern: BFS for shortest path in an unweighted graph or grid.</strong> The clue is "minimum number of steps/moves."</p>
<pre><code class="language-python">from collections import deque

def shortest_path_grid(grid):
    """Steps from top-left to bottom-right; 0 = open, 1 = wall."""
    n, m = len(grid), len(grid[0])
    if grid[0][0] or grid[n-1][m-1]:
        return -1
    q = deque([(0, 0, 0)])
    seen = {(0, 0)}
    while q:
        r, c, d = q.popleft()
        if (r, c) == (n - 1, m - 1):
            return d
        for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
            nr, nc = r + dr, c + dc
            if 0 &lt;= nr &lt; n and 0 &lt;= nc &lt; m and not grid[nr][nc] and (nr, nc) not in seen:
                seen.add((nr, nc))
                q.append((nr, nc, d + 1))
    return -1

assert shortest_path_grid([[0, 0, 0],
                           [1, 1, 0],
                           [0, 0, 0]]) == 4
</code></pre>
<p><strong>Pattern: 1-D dynamic programming.</strong> The clue is "number of ways" or "min/max cost," where each answer builds on smaller answers.</p>
<pre><code class="language-python">def coin_change(coins, amount):
    INF = float("inf")
    dp = [0] + [INF] * amount      # dp[a] = fewest coins to make a
    for a in range(1, amount + 1):
        for c in coins:
            if c &lt;= a:
                dp[a] = min(dp[a], dp[a - c] + 1)
    return dp[amount] if dp[amount] != INF else -1

assert coin_change([1, 2, 5], 11) == 3
</code></pre>
<p>For DP, write the recurrence in words first, then code it.</p>
<h2>A quick "which pattern?" cheat sheet</h2>
<table>
<thead>
<tr>
<th>If the problem says…</th>
<th>Try…</th>
</tr>
</thead>
<tbody><tr>
<td>sorted array, pair/triplet</td>
<td>Two pointers</td>
</tr>
<tr>
<td>longest/shortest contiguous…</td>
<td>Sliding window</td>
</tr>
<tr>
<td>count subarrays with sum k</td>
<td>Prefix sum + hash map</td>
</tr>
<tr>
<td>"seen before", duplicates, pairs</td>
<td>Hash map / set</td>
</tr>
<tr>
<td>minimum/maximum value that satisfies a condition</td>
<td>Binary search on the answer</td>
</tr>
<tr>
<td>next greater/smaller</td>
<td>Monotonic stack</td>
</tr>
<tr>
<td>minimum steps, levels</td>
<td>BFS</td>
</tr>
<tr>
<td>all combinations/subsets</td>
<td>Backtracking</td>
</tr>
<tr>
<td>number of ways, min cost</td>
<td>Dynamic programming</td>
</tr>
<tr>
<td>top k, k-th largest</td>
<td>Heap</td>
</tr>
</tbody></table>
<h2>Mistakes to avoid</h2>
<ul>
<li><strong>Watching solutions instead of struggling.</strong> Twenty-five honest minutes teach you more than a ten-minute video.</li>
<li><strong>Skipping complexity analysis.</strong> State it before the interviewer asks.</li>
<li><strong>Never practising under time pressure.</strong> Do one timed set a week from Week 2.</li>
</ul>
<p>Save the templates above in one file, run it (every <code>assert</code> should pass silently), and add to it as you learn new patterns. By Day 30 it's your revision sheet.</p>
<p>All the best for placements!</p>
<p>— Schoolabe team</p>
]]></content:encoded></item></channel></rss>