🧠 Unlocking TypeScript Generics (Part 2): Understanding Generic Constraints Like a Pro!

OohnohassaniOohnohassani
8 min read

“If you give TypeScript the right limits, it’ll give you superhero-like code.”
— A sleep-deprived dev at 3AM (probably me)

👋 Hey Gocoders!

Heyo! Welcome (or welcome back!) to Gocoding — where I learn in public, mess up in public, and somehow still survive in public 😅. If you're new here: I'm Hassani, a self-taught front-end developer and a TypeScript enthusiast who occasionally makes peace with his bugs after hours of fighting them 🧘‍♂️💻.

Today, we’re diving into something that sounds like a fancy TypeScript thing…
But don’t worry — we’re keeping it chill, fun, and super beginner-friendly 💅

So grab some chai 🍵, settle in, and let’s talk about…

So listen… this week at my desk, something wild happened. I was working on a reusable component. You know, just your usual “pass data into a function and do stuff with it” situation. But then TypeScript hit me with that classic:

Property 'x' does not exist on type 'T'.

Boom. 💥 My eyes rolled so hard, I swear I saw the past three commits.

Turns out, I’d used generics (💪 yay flexibility!)... but forgot about constraints (😅 oops no guardrails).

That one moment taught me something huge:

🔑 Generics are powerful — but without constraints, they’re like a GPS with no destination.

So, welcome to Part 2 of this blog series where we dig into the next superpower in TypeScript:
Generic Constraints — the part that gives your generic types actual rules, safety, and meaning!

But hold up, if you missed Part 1 on the basics of generics, don’t worry — let’s rewind just a bit and revisit what generics are first 👇

🧺 What Are Generics (Quick Refresher)?

Imagine this: you're building a function that should work on any kind of value — strings, numbers, objects, you name it.

The wrong way? You use any — and TypeScript just gives up:

function logValue(value: any): void {
  console.log(value);
}

Sure, this works. But you just threw away all type safety. 🚫 No autocomplete. No error checks. No smartness.

Now here’s the generic way — the right way:

function logValue<T>(value: T): void {
  console.log(value);
}

🧠 Boom — now T is a type variable. It’s like saying:

"I'm not sure what this is right now, but when you use it, I'll know exactly what type it is."

That’s the power of generics — flexible, type-safe, and reusable.

Here’s another one:

function wrapInArray<T>(item: T): T[] {
  return [item];
}

This works for:

  • wrapInArray(5) 👉 number[]

  • wrapInArray("hi") 👉 string[]

  • wrapInArray({ name: "Hassani" }) 👉 object[]

No more any.
Just 💯 type-safe flexibility.

🧩 But Here’s the Catch…

Sometimes, this flexibility becomes too loose.
Like, let’s say you want to use .length in your generic function:

function printLength<T>(item: T): void {
  console.log(item.length); // ❌ Uh-oh!
}

🚨 TypeScript will scream at you:

Property 'length' does not exist on type 'T'

That’s when you realize:

"Okay wait… I need a way to tell TypeScript, ‘I only want values that have a .length property.’"

And that’s exactly what Generic Constraints do.

🎯 What Are Generic Constraints?

Generic Constraints let you say:

"I still want T to be flexible... but not that flexible."

It's like putting a rule or condition on your generic type. You're saying:

“Yo TypeScript, allow only those types that have this structure.”

Here’s the syntax:

function func<T extends SomeType>(arg: T): void {
  // do something
}

That extends isn’t inheritance — it's a constraint.
It means "T must at least be shaped like SomeType".

✅ Real Example: .length Constraint

Let’s go back to that .length issue.

Here’s how we fix it using a constraint:

function printLength<T extends { length: number }>(item: T): void {
  console.log(item.length);
}

Now you're only allowed to pass values that have a .length property — like strings, arrays, or custom objects with a length.

printLength("hello");            // ✅ 5
printLength([1, 2, 3]);          // ✅ 3
printLength({ length: 100 });    // ✅ 100
printLength(42);                 // ❌ Error

🚧 Without constraints, you'd risk runtime errors.
🛡️ With constraints, you get safe, smart, editor-friendly code.

🧪 Real Dev Moment: When I Broke Stuff Without Constraints

Let me tell you a quick story.

I was building a form component in React, and I had this utility:

function getFormValue<T>(obj: T, key: string) {
  return obj[key];
}

It looked okay at first. Until I passed in a key that didn’t exist.

And TypeScript said: nothing 😑

It allowed it... and my app broke. At runtime.

So I updated the function to:

function getFormValue<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

Now, TypeScript won’t even compile if I try to use a wrong key.
Lesson learned: Constraints are real-life bug repellent 🦟

🕵️ Defining a Custom Constraint with Interface

Sometimes, you’ll want to reuse a constraint shape over and over. That’s where interfaces come in.

interface HasId {
  id: number;
}

function logItem<T extends HasId>(item: T) {
  console.log(item.id);
}

Now your function accepts anything with an id property.

logItem({ id: 123 });              // ✅
logItem({ id: 456, name: "HP" });  // ✅
logItem({ name: "Oops" });         // ❌

Clean, reusable, type-safe 🔐

🛠️ Generic Constraints with Functions

Sometimes you want your function parameters to follow a certain structure, but still keep them flexible using generics. That’s where function-level constraints shine!

Let’s say you have a function that logs an object’s id. Not all objects have an id, so you want to make sure the function only accepts objects that do.

function logById<T extends { id: number }>(item: T): void {
  console.log("Item ID:", item.id);
}

Now you're free to pass in any object — as long as it has an id:

logById({ id: 101 }); // ✅ Works
logById({ id: 42, name: "Hassani" }); // ✅ Works
logById({ name: "Oops" }); // ❌ Error: Property 'id' is missing

This is especially helpful in data-heavy apps, where you might work with various object shapes — but need that one required property to exist for your function to work safely.

🧳 Generic Constraints with Objects

Constraints with objects come in clutch when you're designing utility functions or reusable logic that interacts with properties on an object. Similar to the function we built above, but with a little twist 😁

Let’s build a simple extractor:

function getProp<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

Let’s break this down more:

  • T extends object ➜ the value passed must be an object.

  • K extends keyof T ➜ the key must be one of the actual keys from that object.

Let’s pause and really break down K extends keyof T 👇

🧠 What is keyof T?

keyof T literally means:

"Give me a union of all the keys in type T."

Here’s perhaps a raw example without using generics or constraints to make you understand it better…maybe 😂

Let’s say we want to extract the keys of an object using keyof — here is how we’ll go about it:

type User = {
  id: number;
  name: string;
  isActive: boolean;
};

type UserKeys = keyof User; // 'id' | 'name' | 'isActive'

// Example usage
const key: UserKeys = "name"; // ✅ Allowed
// const wrongKey: UserKeys = "email"; // ❌ Error: 'email' is not assignable to type 'UserKeys'

So in simple terms:

If T is { id: number; username: string },
then keyof T becomes 'id' | 'username'.

That means K must be either 'id' or 'username', and nothing else.

So when we say K extends keyof T, we’re saying:

➡️ “Only allow a key that exists on this object.”
That’s how we prevent people from doing stuff like getProp(user, "email") if the object doesn’t have an email key.

This is powerful for autocompletion, type safety, and avoiding those annoying runtime bugs when trying to access a property that doesn’t even exist. 🚫🐛

const user = { id: 1, username: "gocoder" };

getProp(user, "id");       // ✅ 1
getProp(user, "username"); // ✅ "gocoder"
getProp(user, "email");    // ❌ Error: 'email' does not exist on type

By combining generic types with keyof constraints, we’re building smarter, safer functions — that work across any object, but only in ways that make sense 💪

📦 Bonus: Constraints in React Props

If you build React components with TypeScript, you’ve definitely used constraints without realizing.

Like this:

type ButtonProps<T extends React.ElementType> = {
  as?: T;
  children: React.ReactNode;
};

function Button<T extends React.ElementType = 'button'>({ as, children }: ButtonProps<T>) {
  const Component = as || 'button';
  return <Component>{children}</Component>;
}

Yeah… that’s generics + constraints in real-world React.

Constraints give you dev-time safety and confidence.
And once you start using them... you’ll never go back.

🧠 Recap

ConceptWhat it Means
TA generic type placeholder 📦
T extends XConstrains T to types that match or include X 🔐
K extends keyof TSays the key must exist on the object T 🔑
Constraints in funcsLimit generics to ensure safety and useful autocompletion 💡

💡 Final Thoughts

I’ll be honest — when I first learned TypeScript, generics felt like math class all over again 🧮
But once I started writing real code and real bugs appeared, I saw how generics (and constraints!) saved me hours of debugging.

This isn’t just theory.
This is what makes you a 10x dev — writing flexible, powerful, bug-resistant code that scales.

Keep learning. Keep experimenting. Keep breaking things (and fixing them).

Let’s Gocode 🚀

🧵 Coming Up Next...

In the next part, we’ll explore default generic types and how to use generics in React components like a true pro. It gets even cooler 🔥

📣 Over to You!

Have you ever broken something because of loose types?
What’s your favorite use of generic constraints in your code?

Let’s chat in the comments 👇👇👇

📚 References

1
Subscribe to my newsletter

Read articles from Oohnohassani directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Oohnohassani
Oohnohassani

Hi 👋 I'm a developer with experience in building websites with beautiful designs and user friendly interfaces. I'm experienced in front-end development using languages such as Html5, Css3, TailwindCss, Javascript, Typescript, react.js & redux and NextJs 🚀