🧬 Generics
Generics allow you to write reusable code that can work with different data types while still keeping type hints.
Instead of writing separate functions for int, str, etc., we can write one generic function.
🔹 Basic Example
from typing import TypeVar
T = TypeVar("T")
def get_first(items: list[T]) -> T:
return items[0]<div class="infographic-pytypevar">
<style>
.infographic-pytypevar {
--ink:#1a1a1a; --ink-soft:#5c5c5c; --paper:#fff; --paper-alt:#f0ece4;
--accent:#2563eb; --accent-soft:#e3ebfd; --line:#ddd6c9;
--mono-font:'IBM Plex Mono','SFMono-Regular',Menlo,monospace;
--display-font:'Fraunces',Georgia,serif; --body-font:'Inter',system-ui,sans-serif;
box-sizing:border-box; max-width:640px; margin:0 auto; font-family:var(--body-font); color:var(--ink); background:var(--paper);
}
.infographic-pytypevar[data-theme="dark"] {
--ink:#e8e6e1; --ink-soft:#9a968c; --paper:#121212; --paper-alt:#1e1e26;
--accent:#5b8def; --accent-soft:#202942; --line:#2c313d;
}
.infographic-pytypevar *,.infographic-pytypevar *::before,.infographic-pytypevar *::after{box-sizing:border-box;}
.infographic-pytypevar .tv-wrap{padding:0 clamp(1rem,4vw,2rem) 1.5rem;}
.infographic-pytypevar h1{font-family:var(--display-font);font-weight:600;font-size:clamp(1.35rem,5vw,1.8rem);text-align:center;margin:1.5rem 0 0.25rem;letter-spacing:-0.01em;}
.infographic-pytypevar .tv-sub{text-align:center;color:var(--ink-soft);font-size:0.85rem;margin:0 0 1.3rem;}
.infographic-pytypevar .code-block{font-family:var(--mono-font);font-size:0.8rem;line-height:1.75;white-space:pre;background:var(--paper-alt);border:1px solid var(--line);border-radius:8px;padding:0.8rem 1rem;margin-bottom:1.1rem;}
.infographic-pytypevar .code-block .kw{color:var(--accent);font-weight:700;}
.infographic-pytypevar .code-block .tvar{font-weight:700;transition:color .3s;}
.infographic-pytypevar .row{display:flex;justify-content:center;gap:0.5rem;margin-bottom:0.7rem;flex-wrap:wrap;align-items:center;}
.infographic-pytypevar .row input{font-family:var(--mono-font);font-size:0.85rem;padding:0.4rem 0.6rem;border:1.5px solid var(--line);border-radius:5px;background:var(--paper-alt);color:var(--ink);width:220px;text-align:center;}
.infographic-pytypevar .row input:focus{outline:none;border-color:var(--accent);}
.infographic-pytypevar .preset-row{display:flex;justify-content:center;gap:0.4rem;margin-bottom:0.9rem;flex-wrap:wrap;}
.infographic-pytypevar .preset-btn{font:inherit;font-family:var(--mono-font);font-size:0.72rem;font-weight:700;background:var(--paper-alt);border:1.5px dashed var(--line);color:var(--ink-soft);padding:0.3rem 0.6rem;cursor:pointer;border-radius:999px;}
.infographic-pytypevar .run-row{display:flex;justify-content:center;margin-bottom:1.1rem;}
.infographic-pytypevar .run-btn{font:inherit;font-family:var(--mono-font);font-size:0.85rem;font-weight:700;background:var(--accent);color:#fff;border:none;border-radius:999px;padding:0.55rem 1.2rem;cursor:pointer;}
.infographic-pytypevar .run-btn:disabled{opacity:0.5;cursor:default;}
.infographic-pytypevar .flow-stage{display:flex;flex-direction:column;align-items:center;gap:0.15rem;margin-bottom:0.6rem;}
.infographic-pytypevar .flow-box{font-family:var(--mono-font);font-size:0.85rem;font-weight:700;text-align:center;padding:0.55rem 0.9rem;border-radius:8px;border:2px solid var(--line);background:var(--paper-alt);color:var(--ink-soft);min-width:220px;transition:border-color .3s,background .3s,color .3s,opacity .3s;opacity:0.4;}
.infographic-pytypevar .flow-box.is-active{border-color:var(--accent);background:var(--accent-soft);color:var(--accent);opacity:1;}
.infographic-pytypevar .flow-arrow{color:var(--ink-soft);font-size:1.1rem;transition:color .3s;}
.infographic-pytypevar .flow-arrow.is-active{color:var(--accent);}
.infographic-pytypevar .warn-note{text-align:center;font-size:0.78rem;color:#b45309;background:#fdf0dc;border:1px solid #b45309;border-radius:8px;padding:0.55rem 0.8rem;margin-top:0.7rem;display:none;}
.infographic-pytypevar[data-theme="dark"] .warn-note{color:#e0a458;background:#3a2c14;border-color:#e0a458;}
.infographic-pytypevar .warn-note.is-visible{display:block;}
.infographic-pytypevar .tv-caption{text-align:center;font-size:0.85rem;color:var(--ink-soft);margin-top:0.9rem;}
@media (prefers-reduced-motion:reduce){.infographic-pytypevar *,.infographic-pytypevar *::before,.infographic-pytypevar *::after{animation-duration:.001ms!important;transition-duration:.001ms!important;}}
</style>
<div class="tv-wrap">
<h1>Generic Functions with TypeVar</h1>
<p class="tv-sub">Same T, every call — it just binds to whatever type actually comes in.</p>
<div class="code-block">from typing import TypeVar
T = TypeVar("T")
<span class="kw">def</span> get_first(items: list[<span class="tvar" id="tvarDef">T</span>]) -> <span class="tvar" id="tvarReturn">T</span>:
<span class="kw">return</span> items[0]</div>
<div class="row">
<span style="font-family:var(--mono-font); color:var(--ink-soft); font-size:0.85rem;">get_first(</span>
<input id="listInput" type="text" value="1, 2, 3" />
<span style="font-family:var(--mono-font); color:var(--ink-soft); font-size:0.85rem;">)</span>
</div>
<div class="preset-row">
<button class="preset-btn" data-v="1, 2, 3">[1, 2, 3]</button>
<button class="preset-btn" data-v="Tisha, Rahul">["Tisha", "Rahul"]</button>
<button class="preset-btn" data-v="3.5, 2.1">[3.5, 2.1]</button>
<button class="preset-btn" data-v="1, hello, 3.5">[1, "hello", 3.5] (mixed)</button>
</div>
<div class="run-row"><button class="run-btn" id="runBtn">Run: get_first(items)</button></div>
<div class="flow-stage">
<div class="flow-box" id="stageCall">get_first(...)</div>
<span class="flow-arrow" id="arrow1">↓</span>
<div class="flow-box" id="stageBind">T = ?</div>
<span class="flow-arrow" id="arrow2">↓</span>
<div class="flow-box" id="stageReturn">returns ?</div>
</div>
<div class="warn-note" id="warnNote">A type checker (like mypy) would flag this — the list mixes types, so T can't consistently mean just one of them. Python itself still runs it fine at runtime.</div>
<p class="tv-caption">T isn't one fixed type — it's a placeholder that gets filled in fresh, based on what you actually pass.</p>
</div>
<script>
(function(){
var root = document.currentScript.closest('.infographic-pytypevar') || document.querySelector('.infographic-pytypevar');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark = bodyAttr === 'true' ? true : bodyAttr === 'false' ? false : (document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/) || [])[1] === 'true';
if (dark) root.setAttribute('data-theme', 'dark');
var listInput = root.querySelector('#listInput');
var runBtn = root.querySelector('#runBtn');
var stageCall = root.querySelector('#stageCall');
var stageBind = root.querySelector('#stageBind');
var stageReturn = root.querySelector('#stageReturn');
var arrow1 = root.querySelector('#arrow1');
var arrow2 = root.querySelector('#arrow2');
var warnNote = root.querySelector('#warnNote');
var tvarDef = root.querySelector('#tvarDef');
var tvarReturn = root.querySelector('#tvarReturn');
var allStages = [stageCall, stageBind, stageReturn];
var allArrows = [arrow1, arrow2];
var running = false;
root.querySelectorAll('.preset-btn').forEach(function (btn) {
btn.addEventListener('click', function () { listInput.value = btn.dataset.v; });
});
function pyType(raw) {
var s = raw.trim();
if (/^-?\d+$/.test(s)) return 'int';
if (/^-?\d+\.\d+$/.test(s)) return 'float';
return 'str';
}
function clearStages() {
allStages.forEach(function (s) { s.classList.remove('is-active'); });
allArrows.forEach(function (a) { a.classList.remove('is-active'); });
}
runBtn.addEventListener('click', function () {
if (running) return;
running = true;
runBtn.disabled = true;
clearStages();
warnNote.classList.remove('is-visible');
tvarDef.style.color = '';
tvarReturn.style.color = '';
var items = listInput.value.split(',').map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
var types = items.map(pyType);
var firstType = types[0] || 'str';
var mixed = types.some(function (t) { return t !== firstType; });
var display = /^-?\d+(\.\d+)?$/.test(items[0]) ? items[0] : '"' + items[0] + '"';
var accentColor = getComputedStyle(root).getPropertyValue('--accent').trim();
var steps = [
function () {
stageCall.textContent = 'get_first([' + items.join(', ') + '])';
stageCall.classList.add('is-active');
},
function () {
arrow1.classList.add('is-active');
stageBind.textContent = 'T = ' + firstType;
stageBind.classList.add('is-active');
tvarDef.style.color = accentColor;
},
function () {
arrow2.classList.add('is-active');
stageReturn.textContent = 'returns ' + display + ' (' + firstType + ')';
stageReturn.classList.add('is-active');
tvarReturn.style.color = accentColor;
if (mixed) warnNote.classList.add('is-visible');
running = false;
runBtn.disabled = false;
}
];
var i = 0;
function next() { if (i < steps.length) { steps[i](); i++; setTimeout(next, 550); } }
next();
});
})();
</script>
</div>🔹 Generic Classes
Generics can also be used with classes:
from typing import Generic, TypeVar
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, value: T):
self.value = value
def get(self) -> T:
return self.valueNow:
number_box = Box[int](100)
name_box = Box[str]("Tisha")Generic = Write once, use with multiple types.