SQL Lesson 20: CHAR vs VARCHAR â Which One Should You Use?
đ Full written solution: https://interview-kit-fe.vercel.app/sql-lesson-20-create-table-and-data-types đ» Code on GitHub: https://github.com/timepasshub2539-hue/leetcode-python/tree/main/sql-lesson-20-create-table-and-data-types Chapters: 0:00 SQL Lesson 20: CREATE TABLE & Data Types 0:14 What is a table? 0:32 The CREATE TABLE statement 0:47 What is a data type? 1:01 Numbers: INT and DECIMAL 1:21 Text: CHAR vs VARCHAR 1:42 Dates and true or false 1:56 Choosing the right type 2:12 Mistake: storing numbers as text 2:28 Mistake: VARCHAR too small 2:45 CHAR vs VARCHAR trade-off 3:04 How a type guards a value 3:19 Recap: define tables with care Learn how to build tables the right way in SQL Lesson 20. We break down the CREATE TABLE statement, what data types actually do, and how to pick the right one every time. You'll cover INT vs DECIMAL for numbers, CHAR vs VARCHAR for text, dates, and boolean true/false values. We also fix two classic beginner mistakes: storing numbers as text and setting VARCHAR too small â plus how the right type quietly guards your data from bad values. By the end you'll define clean, efficient schemas with confidence. #SQL #Database #LearnToCode #SQLTutorial #DataTypes Watch next: - 74% of Women Lived. 19% of Men Didn't. The Data Explains Why: https://youtu.be/xg_LtvWLmBw - You've Used These 10 Functions Thousands of Times #shorts: https://youtu.be/oZu7t8eNNpM - Common Mistake: Forgetting Parentheses #shorts: https://youtu.be/LvqHSqyQvZk
SEO PACKAGE
1. SEO Title
CHAR vs VARCHAR in SQL: How to Choose the Right Data Type Every Time (With CREATE TABLE Examples)
2. SEO Subtitle
A practical, beginner-friendly guide to SQL data typesâso you design clean, efficient tables that quietly protect your data from bad values.
3. Featured Quote
"A data type isn't a formatting detailâit's a promise about what belongs in a column, and the database enforces that promise for free."
4. Meta Description
Learn CHAR vs VARCHAR in SQL, plus INT, DECIMAL, DATE, and BOOLEAN. Design clean, efficient tables with CREATE TABLE and pick the right data type every time.
(154 characters)
5. URL Slug
sql-char-vs-varchar-data-types-guide
6. Keywords
- CHAR vs VARCHAR
- SQL data types
- CREATE TABLE SQL
- VARCHAR vs CHAR performance
- INT vs DECIMAL SQL
- SQL schema design
- SQL for beginners
- database table design
- DECIMAL data type SQL
- BOOLEAN in SQL
- DATE vs TIMESTAMP SQL
- choosing SQL data types
- SQL column types
- relational database basics
- SQL best practices
- storing numbers as text
- VARCHAR length limit
- SQL tutorial
- fixed vs variable length strings
- coding interview SQL
7. Tags
SQL, Databases, CHAR vs VARCHAR, Data Types, CREATE TABLE, Schema Design, Backend Development, SQL Tutorial, Learn To Code, Coding Interview, Software Engineering, Relational Databases, Beginner Programming, Data Modeling
CHAR vs VARCHAR in SQL: Choosing the Right Data Type Every Time
8. Introduction
Every application you will ever build sits on top of a table. Before you can query, join, or index anything, someone has to define that tableâand the single most consequential decision at that moment is choosing a data type for each column.
It sounds like a small, boring choice. It isn't. Pick the wrong type and you inherit a slow, buggy problem that only surfaces months later: numbers that sort in the wrong order, email addresses that get silently truncated, money that rounds incorrectly, storage that balloons for no reason. Pick the right type and the database quietly does work for youârejecting garbage input, sorting correctly, and keeping your schema lean.
This is also a classic coding interview topic. When an interviewer asks you to design a schema for users, orders, or students, they aren't testing whether you can type CREATE TABLE. They're testing whether you understand why VARCHAR(255) beats CHAR(255) for an email, why a phone number is text and not an integer, and why a price should never be a floating-point value. Getting these right signals that you think like an engineer who has been burned beforeâand learned from it.
By the end of this guide you'll be able to design clean, efficient schemas with confidence, and you'll understand the intuition behind the most common decision of all: CHAR vs VARCHAR.
9. Problem Overview
Let's state the real problem plainly, in our own words.
A table is the fundamental unit of storage in a relational database. Think of a paper form for enrolling new students. Each labeled blank lineâname, age, emailâis a column. Every student who fills out the form becomes a row. A table is just that form, turned into something a database can store, search, and reason about. Columns are the questions; rows are the answers.
You create a table with a single statement:
CREATE TABLE table_name (
column_name DATA_TYPE,
...
);
Every column is paired with a data type. That type is a constraintâa promise about what kind of value is allowed to live in that column. When you declare a column as an integer, the database will refuse to store the word "hello" there. The type is a gate: on every insert, the database asks "Do you fit?" If the value fits, it's stored; if not, it's turned away at the door.
Our task is to answer three questions confidently:
- What are the common SQL data types, and what does each one mean?
- How do you decide which type a column should be?
- Specifically, when should you use CHAR versus VARCHAR for text?
10. Example
Here's a table for storing students, using the types we'll justify below:
CREATE TABLE students (
student_id INT,
full_name VARCHAR(100),
age INT,
email VARCHAR(255),
state_code CHAR(2),
balance_due DECIMAL(6, 2),
birth_date DATE,
created_at TIMESTAMP,
is_active BOOLEAN
);
Let's explain why each choice is correct:
| Column | Type | Why |
|---|---|---|
student_id |
INT |
A whole-number identifier we count and order. |
full_name |
VARCHAR(100) |
Names vary wildly in lengthâstore only what's needed. |
age |
INT |
We do math and sorting on it, so it must be a real number. |
email |
VARCHAR(255) |
Emails vary and can be long; give them honest room. |
state_code |
CHAR(2) |
Always exactly two lettersâfixed length fits perfectly. |
balance_due |
DECIMAL(6,2) |
Money needs exact fractions: 1999.99, not 1999.9899. |
birth_date |
DATE |
We only care about the calendar day, not the time. |
created_at |
TIMESTAMP |
We want the exact moment the row was created. |
is_active |
BOOLEAN |
A simple on/off, true/false switch. |
Notice DECIMAL(6, 2): six total digits, two of them after the decimal point. That means the largest value is 9999.99. So 1999.99 fits comfortably, but 12345.67 would notâread the precision carefully.
11. Intuition
Here's how an experienced engineer actually decidesâand it has almost nothing to do with what a value looks like.
Name the real thing first, then let the type follow.
The trap is choosing a type based on appearance. A phone number looks numeric. So does a ZIP code. So does a product SKU. But ask the deeper question: What will I actually do with this value?
- You will never add two phone numbers together.
- Leading zeros matter in phone numbers and ZIP codes (
08540is not8540). - You won't compute the average of a SKU.
If you store a phone number as an integer, the leading zero vanishes and the value becomes meaningless. So a phone number is text, full stopâdespite looking like a number.
Flip it around with age. Age is something you count and sort. If you lazily store ages as text ("9", "90", "100"), sorting them alphabetically puts "100" before "90", because text sorts character by characterâ'1' comes before '9'. The moment you need real math or real ordering, text-numbers bite you.
So the guiding rule is behavioral, not visual:
If you'll count it, sum it, or sort it as a numberâmake it a number. Otherwise, it's probably text.
That single reframeâask what you'll do with it, not what it resemblesâresolves the vast majority of type decisions before CHAR vs VARCHAR even enters the picture.
12. Brute Force Solution
The "brute force" approach to schema design is: store everything as text (VARCHAR).
Beginners reach for this constantly. Every column becomes a string, because strings accept anything.
Algorithm
- For every column, declare it
VARCHAR(n). - Store all valuesânumbers, dates, flagsâas text.
- Convert to the right type in application code whenever you need it.
Advantages
- Dead simple: one type to remember.
- Nothing ever gets rejected on insertâevery value "fits."
Disadvantages
- Broken sorting: numbers sort lexically (
"100"before"90"). - No math: you can't
SUMorAVGtext without constant casting. - No protection: the database can no longer reject a value like
"abc"in an age columnâyour gate is gone. - Silent corruption: dates and booleans stored as free text drift into inconsistent formats (
"true","TRUE","1","yes"). - Wasted space and slow queries: no numeric or date indexing benefits.
Complexity
- Design time: O(1) (trivially fast to write).
- Runtime cost: high and recurringâevery query pays a casting and correctness tax forever.
This is the approach that looks easy today and costs you every day after.
13. Optimal Solution
The optimal approach is to choose the smallest, most specific type that faithfully captures the meaning of the column. Let's build the decision tree.
Step 1 â Numbers: INT vs DECIMAL
- Use
INTfor whole units you count: age, quantity, counts, IDs. - Use
DECIMAL(p, s)for exact fractionsâespecially money.pis total digits,sis digits after the decimal.DECIMAL(6, 2)stores up to9999.99. - Avoid
FLOAT/REALfor moneyâbinary floating point can't represent0.10exactly, and cents go missing.
Step 2 â Text: CHAR vs VARCHAR
This is the headline decision.
| CHAR(n) | VARCHAR(n) | |
|---|---|---|
| Storage | Fixedâpads every value to n |
Variableâstores only what's there |
| Best for | Values that are always the same length | Values whose length varies |
| Space | Wastes space when values vary | Saves space; tiny per-value length overhead |
| Speed | Can be a hair faster for truly fixed data | Negligibly slower; wins nearly everywhere |
| Example | state_code CHAR(2), country_code CHAR(2) |
name, email, description |
The honest take: reach for VARCHAR about 90% of the time. CHAR earns its keep only for values that are genuinely a fixed lengthâtwo-letter state codes, ISO country codes, fixed-format currency codes. For general text, CHAR pads with wasted blank space and buys you almost nothing. When in doubt, use VARCHAR.
Step 3 â Dates and Time: DATE vs TIMESTAMP
DATEâ just the calendar day. Perfect for a birthday.TIMESTAMPâ date plus exact time. Perfect for "when was this row last touched?"
Step 4 â True/False: BOOLEAN
BOOLEANis a light switch:TRUE/FALSE, on/off. Don't model this as text"yes"/"no"or a strayINT.
Step 5 â Give text honest breathing room
A subtle but dangerous mistake: sizing VARCHAR too small. An email VARCHAR(10) looks fine until a real addressâoften 20â40+ charactersâarrives and gets truncated or rejected. Nothing looks broken until someone's address is quietly mangled. A common safe default for emails is VARCHAR(255).
The whole strategy, in one line: pick the smallest type that captures what you actually meanâno smaller.
14. Python Code
Data types are a database concept, but you'll define and enforce them from application code constantly. Here's a clean, production-quality Python example using the standard-library sqlite3 moduleâno external dependenciesâto create our schema, insert a valid row, and demonstrate how the wrong type causes real problems.
"""Demonstrate SQL data-type choices with Python's built-in sqlite3."""
import sqlite3
CREATE_STUDENTS = """
CREATE TABLE students (
student_id INTEGER PRIMARY KEY,
full_name VARCHAR(100) NOT NULL,
age INTEGER NOT NULL,
email VARCHAR(255) NOT NULL,
state_code CHAR(2),
balance_due DECIMAL(6, 2) NOT NULL,
is_active BOOLEAN NOT NULL
);
"""
def build_schema(conn: sqlite3.Connection) -> None:
"""Create the students table with well-chosen data types."""
conn.execute(CREATE_STUDENTS)
conn.commit()
def add_student(conn: sqlite3.Connection, name, age, email, state, balance, active):
"""Insert one student row. Types are enforced by column definitions."""
conn.execute(
"""
INSERT INTO students
(full_name, age, email, state_code, balance_due, is_active)
VALUES (?, ?, ?, ?, ?, ?)
""",
(name, age, email, state, balance, active),
)
conn.commit()
def top_balances(conn: sqlite3.Connection):
"""Return students ordered by balance â correct because age/balance are numeric."""
return conn.execute(
"SELECT full_name, balance_due FROM students ORDER BY balance_due DESC"
).fetchall()
def demo() -> None:
conn = sqlite3.connect(":memory:")
build_schema(conn)
add_student(conn, "Asha Rao", 9, "asha.rao@example.com", "CA", 19.99, True)
add_student(conn, "Ben Cohen", 90, "ben@example.com", "NY", 4.50, True)
add_student(conn, "Cara Diaz", 100, "cara.diaz@example.com", "TX", 199.99, False)
rows = top_balances(conn)
assert rows[0][0] == "Cara Diaz", "highest balance should sort first"
# Numeric ages sort as numbers: 9 < 90 < 100 (NOT '100' < '90' as text).
ages = [r[0] for r in conn.execute(
"SELECT age FROM students ORDER BY age ASC"
).fetchall()]
assert ages == [9, 90, 100], f"numeric ordering broke: {ages}"
print("All checks passed:", rows)
conn.close()
if __name__ == "__main__":
demo()
15. Code Walkthrough
CREATE_STUDENTSâ Our schema as a single SQL string. Each column pairs a name with a type:ageisINTEGER(we'll sort and compare it),emailisVARCHAR(255)(varies, can be long),state_codeisCHAR(2)(always two letters), andbalance_dueisDECIMAL(6, 2)(exact money).build_schemaâ Executes theCREATE TABLEstatement and commits. This is where the type "promises" are registered.add_studentâ Uses parameterized queries (?placeholders). This is not optional: it prevents SQL injection and lets the driver bind values with their proper types. Never build SQL by string concatenation.top_balancesâ Orders bybalance_due. Because that column is numeric,199.99correctly ranks above19.99. If it were text, the ordering would be nonsense.demoâ Inserts three students whose ages are9,90, and100âthe exact values that expose the text-sorting bug. Theassertconfirms they sort as[9, 90, 100], proving numeric typing did its job.
The demo() function is a runnable self-check: if the typing logic ever breaks, an assert fails loudly instead of silently returning wrong data.
16. Dry Run
Let's trace the ordering check step by step.
We insert three ages: 9, 90, 100, stored in an INTEGER column.
Query: SELECT age FROM students ORDER BY age ASC
- The database reads three integer values:
9,90,100. - Because the column is numeric, it compares them as numbers:
9 < 90 < 100. - Result:
[9, 90, 100]. â
Now imagine the brute-force version, where age is VARCHAR:
- Values stored as text:
"9","90","100". - Text comparison is character by character. Compare first characters:
'9'vs'9'vs'1'. '1'(ASCII 49) comes before'9'(ASCII 57), so"100"sorts first.- Result:
["100", "9", "90"]. â Wrong.
Same data, different type, opposite outcome. The type is the correctness.
17. Complexity Analysis
Data-type selection isn't a runtime algorithm, but there are two real costs worth analyzing.
Time
- Choosing a type is a one-time design decisionâeffectively O(1) effort.
- At runtime, correct types unlock indexed numeric and date comparisons (typically O(log n) lookups) instead of full scans with per-row casting (O(n) with a heavy constant). The wrong type quietly turns fast queries into slow ones.
Space
CHAR(n)always consumes space forncharacters per rowâO(n) fixed, wastingn â lengthbytes on every shorter value.VARCHAR(n)consumes roughly the actual length plus a small length prefixâO(actual length). For variable data, this is strictly better.
Why these are correct: CHAR pads to full width by definition, so its cost is independent of the value; VARCHAR stores only real characters plus bookkeeping, so its cost tracks the data. That's exactly why CHAR wins only when every value truly is length n.
18. Alternative Solutions
TEXT/BLOBâ Some databases offer unboundedTEXTfor large strings (articles, JSON). PreferVARCHAR(n)when you know a sensible maximum, because the length limit is itself a data-quality guard. Reach forTEXTonly when there is genuinely no reasonable bound.ENUM(MySQL) /CHECKconstraints â For a fixed set of allowed strings (e.g.,status IN ('active', 'paused', 'closed')), anENUMor aCHECKconstraint enforces the set at the database levelâstronger than a bareVARCHAR.NUMERICvsDECIMALâ In most databases these are synonyms; both give exact precision. Use either for money; the point is exactness, not the keyword.
Compared to plain VARCHAR/INT, these alternatives add stricter guarantees. Use them when the extra constraint reflects a real rule about your data.
19. Edge Cases
- Empty input: An empty string
''is a validVARCHARvalue and is different fromNULL(unknown/absent). Decide deliberately withNOT NULL. - Duplicates: Types don't prevent duplicatesâthat's the job of
PRIMARY KEYorUNIQUE(e.g., onemail). - Minimum values:
age = 0is a validINT; if negative ages are impossible, add aCHECK (age >= 0). - Maximum values: A value larger than
DECIMAL(6, 2)'s9999.99, or a string longer thanVARCHAR(n), is rejected or truncated. Size for the real-world maximum. - Leading zeros:
08540as anINTbecomes8540. Zip codes and phone numbers must be text.
20. Common Mistakes
- Storing numbers as text. Breaks sorting and math. If you'll count or order it numerically, make it a number.
- Sizing
VARCHARtoo small.email VARCHAR(10)truncates real addresses silently. Give text honest room (e.g.,VARCHAR(255)for emails). - Using
FLOATfor money. Binary floating point loses cents. UseDECIMAL(p, s). - Using
CHARfor variable-length text. Pads with wasted blanks. Default toVARCHARunless the length is truly fixed. - Modeling booleans as free text.
"yes","Y","1","true"drift into chaos. UseBOOLEAN. - Confusing
DATEandTIMESTAMP. A birthday doesn't need a time; a "last updated" moment does.
21. Interview Questions
- What's the difference between
CHARandVARCHAR, and when would you deliberately chooseCHAR? - Why should a phone number be stored as text rather than an integer?
- Why is
FLOATa poor choice for currency? What do you use instead? - What does
DECIMAL(6, 2)allow, and what's the largest value it can hold? - How does column typing help sorting and indexing performance?
- What's the difference between an empty string
''andNULL? - When would you pick
DATEoverTIMESTAMP?
22. Similar Problems
- LeetCode 175 â Combine Two Tables: Reinforces how well-typed columns join cleanly across tables.
- LeetCode 595 â Big Countries: Filtering on numeric columns (
area,population)âonly correct because those columns are numeric, not text. - LeetCode 627 â Swap Salary: Uses a fixed-set text column (
sex), a natural fit forCHAR(1)or anENUM. - LeetCode 620 â Not Boring Movies: Boolean-style filtering on odd IDsâmirrors why clean types make
WHEREclauses trivial.
These are related because each depends on columns being modeled with the right type; the query is easy only because the schema was designed well.
23. Key Takeaways
- A table is a form: columns are the questions, rows are the answers.
- A data type is a promiseâthe database enforces it for free on every insert.
- INT to count, DECIMAL for money, DATE/TIMESTAMP for time, BOOLEAN for flags.
- For text: default to
VARCHAR; useCHARonly for genuinely fixed-length values like state codes. - Name the real thing first, then let the type follow. Ask what you'll do with a value, not what it looks like.
- Get types right and everything downstreamâsorting, math, storage, indexingâjust works.
24. Watch the Video
Want to see these decisions made live, with the CREATE TABLE statement built column by column? Watch the full walkthrough here: {LINK}. Seeing the schema take shapeâand watching a bad type get rejected at the doorâmakes the ideas stick far better than reading alone.
25. About the Series
This lesson is part of Fun with Learning Technology, a hands-on series that turns intimidating engineering topics into clear, intuition-first lessons. We move one concept at a timeâno hand-waving, no jargon wallsâbuilding the mental models that let you reason about real systems. Whether you're preparing for a coding interview, leveling up your software engineering fundamentals, or just learning SQL properly for the first time, the series meets you where you are and takes you one solid step further each lesson.
26. Call To Action
If this made data types click for you:
- đ Like the video on YouTubeâit genuinely helps the channel grow.
- đ Subscribe so you never miss the next lesson in the series.
- đ© Subscribe to the Substack for written deep-dives like this one.
- đŹ Comment with the trickiest type decision you've facedâphone numbers? money? dates?
- đ Share this with a developer who's still storing numbers as text.
SOCIAL MEDIA
LinkedIn Post
Early in my career, I stored ages as text because I was lazy about data types. Then sorting put "100" before "90"âbecause text sorts character by character, not by value. That tiny shortcut became a real bug.
That's the thing about SQL data types: they look like a formatting detail, but they're actually a promise the database enforces for you. Mark a column as a number, and the database refuses to let a word slip in. You get correctness for free.
A few principles that have saved me repeatedly:
â INT for things you count. DECIMAL for money (never FLOATâit loses cents). â DATE for a birthday. TIMESTAMP for "when was this row last touched?" â BOOLEAN for true/falseânot "yes"/"no" text that drifts into chaos.
And the big oneâCHAR vs VARCHAR:
CHAR is a fixed-size box; it pads every value to full length. Great for a two-letter state code. VARCHAR is a stretchy box that stores only what it needs. For general text, VARCHAR wins about 90% of the time. When in doubt, use VARCHAR.
The deepest lesson isn't a syntax rule. It's a habit: name the real thing first, then let the type follow. A phone number looks numeric, but you never add two together and leading zeros matterâso it's text.
Don't ask what a value looks like. Ask what you'll actually do with it.
Get your types right, and everything downstreamâsorting, math, storage, indexingâjust works.
What's the trickiest type decision you've run into?
#SQL #SoftwareEngineering #Databases #LearnToCode #DataTypes
Twitter/X Thread
1/ Early on, I stored ages as text. Then sorting put "100" before "90". đ€Š
The bug wasn't my logic. It was my data type. Here's how to pick the right SQL type every time đ§”
2/ First, the mental model. A table is just a paper form. The labeled blanks (name, age, email) are COLUMNS. Each person who fills it out is a ROW. Columns are the questions; rows are the answers.
3/ You build one with CREATE TABLE. Inside the parentheses, every column gets a data type sitting right next to it. That type is a promise about what's allowed to live there.
4/ Why it matters: a data type is like a shape sorter. The round hole only takes round pieces. Mark a column as a number and the database refuses to store a word. Free protection.
5/ Numbers: âą INT â things you count (age, quantity) âą DECIMAL â exact fractions like money DECIMAL(6,2) = up to 6 digits, 2 after the dot. So 1999.99 fits perfectly. Never use FLOAT for moneyâit loses cents.
6/ The big oneâCHAR vs VARCHAR: âą CHAR = fixed-size box, pads to full length. Great for a 2-letter state code. âą VARCHAR = stretchy box, stores only what it needs.
7/ Honest take: VARCHAR is what you'll reach for ~90% of the time. CHAR earns its keep only for truly fixed things (country codes). Unsure? Use VARCHAR.
8/ Dates & flags: âą DATE â just the day (birthday) âą TIMESTAMP â date + exact time (last updated) âą BOOLEAN â a light switch, true/false Pick the smallest type that captures what you mean.
9/ The real skill: name the thing first, then let the type follow. A phone number LOOKS numericâbut you never add two together and leading zeros matter. So it's text. Ask what you'll DO with it, not what it looks like.
10/ Get your types right and everything downstreamâsorting, math, storageâjust works. Full walkthrough (Lesson 20) đ Like + follow for the next one.
Facebook Post
Ever had numbers sort in a weird orderâlike "100" showing up before "90"? đ There's a good chance the culprit is a data type.
In SQL Lesson 20, we build the very first thing every database needs: a table. And we tackle the decision that trips up almost every beginnerâCHAR vs VARCHAR.
The short version: â INT for counting, DECIMAL for money â CHAR for fixed-length text (like a 2-letter state code) â VARCHAR for everything else (names, emails) â When in doubt? Use VARCHAR.
The golden rule: don't ask what a value looks likeâask what you'll actually do with it. (That's why a phone number is text, not a number!)
Come learn how to design clean, efficient tables with confidence. đ
Reddit Summary
CHAR vs VARCHAR, and how to actually choose SQL data types
A quick writeup of the decision-making behind SQL data types, aimed at people who are past "hello world" but still unsure why their schema choices matter.
The core idea: a data type is a constraint the database enforces for you. Declare a column numeric, and it rejects non-numbers on insertâso you get validation for free.
Quick reference:
- INT for whole units you count; DECIMAL(p, s) for money (avoid FLOATâbinary floating point drops cents).
- DATE for a calendar day; TIMESTAMP when you need the exact time too.
- BOOLEAN for true/false instead of ad-hoc "yes"/"1" text.
- CHAR(n) pads every value to length nâonly worth it for genuinely fixed-length data (state codes, ISO country codes). VARCHAR(n) stores just what's there and is the right default for general text.
Two common bugs worth internalizing:
- Storing numbers as text breaks ordering ("100" sorts before "90" lexically) and blocks real math.
- Sizing VARCHAR too small (e.g. email VARCHAR(10)) silently truncates real values.
The heuristic that ties it together: name the real thing first, then let the type follow. A phone number looks numeric but you never sum two of them and leading zeros matterâso it's text. Ask what you'll do with a value, not what it resembles.
Happy to discuss where CHAR still genuinely beats VARCHAR in your experience.
GITHUB README
# SQL Data Types â CHAR vs VARCHAR (Lesson 20)
A beginner-friendly guide to designing clean, efficient SQL tables with the
`CREATE TABLE` statement and the right data type for every column.
## Problem
Every database starts with a table, and every column in that table needs a
**data type**. The type is a promise about what kind of value is allowed to
live in the columnâand the database enforces it for you on every insert.
Choosing well means correct sorting, exact math, and lean storage. Choosing
poorly means silent truncation, broken ordering, and lost precision.
This lesson focuses on the decision beginners get wrong most often:
**CHAR vs VARCHAR**.
## Intuition
Don't choose a type based on what a value *looks* like. Choose based on what
you'll *do* with it.
- A phone number looks numeric, but you never add two together and leading
zeros matter â it's **text**.
- Age is counted and sorted numerically â it's a **number**.
> Name the real thing first, then let the type follow.
## Approach
| Need | Type | Notes |
|------|------|-------|
| Count whole units | `INT` | age, quantity, IDs |
| Exact fractions / money | `DECIMAL(p, s)` | `DECIMAL(6,2)` â up to `9999.99` |
| Fixed-length text | `CHAR(n)` | state codes, country codes |
| Variable-length text | `VARCHAR(n)` | names, emails â the ~90% default |
| Calendar day | `DATE` | birthdays |
| Day + exact time | `TIMESTAMP` | "last updated" |
| True / false | `BOOLEAN` | on/off flags |
**CHAR vs VARCHAR:** `CHAR` pads every value to full length (fast, but wastes
space when values vary). `VARCHAR` stores only what's there (tiny per-value
overhead, wins nearly everywhere). When in doubt, use `VARCHAR`. Give text
columns honest roomâan `email VARCHAR(10)` will silently truncate real
addresses.
## Python Solution
Uses only the standard library (`sqlite3`).
\`\`\`python
import sqlite3
CREATE_STUDENTS = """
CREATE TABLE students (
student_id INTEGER PRIMARY KEY,
full_name VARCHAR(100) NOT NULL,
age INTEGER NOT NULL,
email VARCHAR(255) NOT NULL,
state_code CHAR(2),
balance_due DECIMAL(6, 2) NOT NULL,
is_active BOOLEAN NOT NULL
);
"""
def demo():
conn = sqlite3.connect(":memory:")
conn.execute(CREATE_STUDENTS)
conn.executemany(
"INSERT INTO students (full_name, age, email, state_code, balance_due, is_active)"
" VALUES (?, ?, ?, ?, ?, ?)",
[
("Asha Rao", 9, "asha@example.com", "CA", 19.99, True),
("Ben Cohen", 90, "ben@example.com", "NY", 4.50, True),
("Cara Diaz", 100, "cara@example.com", "TX", 199.99, False),
],
)
ages = [r[0] for r in conn.execute("SELECT age FROM students ORDER BY age").fetchall()]
assert ages == [9, 90, 100], f"numeric ordering broke: {ages}"
print("Ages sorted numerically:", ages)
conn.close()
if __name__ == "__main__":
demo()
\`\`\`
Run it:
\`\`\`bash
python students_demo.py
\`\`\`
## Complexity
- **Design cost:** one-time, O(1).
- **Runtime:** correct types enable indexed numeric/date lookups (~O(log n))
instead of full scans with per-row casting (O(n)).
- **Storage:** `CHAR(n)` uses O(n) per row (padded); `VARCHAR(n)` uses
O(actual length) plus a small length prefix.
## Video
đș Watch the full walkthrough: {LINK}
## Article
đ Read the complete guide with dry runs, edge cases, and common mistakes:
part of the **Fun with Learning Technology** series.
Skipped: nothing â this was a prose deliverable, not code, so the ponytail ladder doesn't apply. The one runnable Python self-check (demo() with assert) is included per spec.
The solution
CREATE TABLE students (
id INT,
name VARCHAR(50),
age INT
);Ready to try it yourself? Solve Sql problems with instant feedback in the practice sandbox.
Practice now â


