Question

Why Dictionary Is Preferred Over Hashtable in C#

csharp.netvb.netdata-structures

Question

In C#, Dictionary<TKey, TValue> is often preferred over Hashtable, and similar guidance appears in many programming languages.

What are the main reasons for preferring a dictionary-style generic collection over a hashtable-style non-generic collection?

For example, how do these two compare?

using System;
using System.Collections;
using System.Collections.Generic;

class Example
{
    static void Main()
    {
        Hashtable table = new Hashtable();
        table[1] = "Alice";

        Dictionary<int, string> dict = new Dictionary<int, string>();
        dict[1] = "Alice";
    }
}

Why is Dictionary<TKey, TValue> usually the better choice in modern C#?

Short Answer

By the end of this page, you will understand the difference between Hashtable and Dictionary<TKey, TValue> in C#, why generic collections are usually preferred, and when Hashtable might still appear in older code. You will also see how type safety, performance, readability, and maintainability make Dictionary the standard choice in modern C#.

Concept

Hashtable and Dictionary<TKey, TValue> both store data as key-value pairs. That means you use a key to quickly look up a value.

Example:

  • Key: 1
  • Value: "Alice"

The main difference is that:

  • Hashtable is non-generic and stores keys and values as object
  • Dictionary<TKey, TValue> is generic and stores strongly typed keys and values

In modern C#, generic collections are usually preferred because they are safer and clearer.

Why this matters

1. Type safety

With Dictionary<int, string>, the compiler knows:

  • keys must be int
  • values must be string

So this is allowed:

dict[1] = "Alice";

But this is rejected at compile time:

dict["wrong key type"] = "Alice";

That is good because errors are caught early.

With Hashtable, everything is stored as object, so the compiler cannot help as much:

Hashtable table = new Hashtable();
table[1] = "Alice";
table["wrong key type"] = 123;

This compiles, even if the mixed types are not what you intended.

2. Better performance in many cases

Because Hashtable stores values as object, value types such as int, bool, and double may need boxing and unboxing.

Hashtable table = new Hashtable();
table[1] = 100; // boxed
int value = (int)table[1]; // unboxed

Dictionary<int, int> avoids this overhead because it stores the real types directly.

3. Cleaner code

Dictionary<TKey, TValue> makes your intent obvious.

Dictionary<int, string> users = new Dictionary<int, string>();

A reader immediately knows the structure stores:

  • int keys
  • string values

With Hashtable, that information is missing.

4. Better tooling and maintainability

Strong typing improves:

  • IntelliSense
  • refactoring safety
  • documentation through code
  • reduced runtime casting bugs

5. Modern .NET design

Since .NET 2.0, generic collections in System.Collections.Generic have become the standard choice for most new code. Hashtable mostly remains for:

  • legacy code
  • older APIs
  • rare interoperability scenarios

So the preference is not because Hashtable is useless. It is because Dictionary<TKey, TValue> fits modern C# much better.

Mental Model

Think of Hashtable as a storage room where every box is labeled only as "object".

You can put anything into any box:

  • numbers
  • strings
  • custom objects

That sounds flexible, but later you must open the box and figure out what is actually inside.

Dictionary<TKey, TValue> is more like a shelf with clear labels:

  • key must be int
  • value must be string

Now every slot has a known shape. You cannot accidentally put the wrong thing there, and anyone reading the code understands the data immediately.

So:

  • Hashtable = flexible but vague
  • Dictionary<TKey, TValue> = structured and safer

In real projects, structured usually wins.

Take Quiz

Syntax and Examples

Core syntax

Hashtable

using System.Collections;

Hashtable table = new Hashtable();
table[1] = "Alice";
table[2] = "Bob";

string name = (string)table[1];

Notice the cast:

(string)table[1]

That is needed because the result is returned as object.

Dictionary<TKey, TValue>

using System.Collections.Generic;

Dictionary<int, string> dict = new Dictionary<int, string>();
dict[1] = "Alice";
dict[2] = "Bob";

string name = dict[1];

No cast is needed, because the dictionary already knows the value type is string.

Beginner-friendly example

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<string, int> ages = new Dictionary<string, int>();

        ages["Sam"] = 25;
        ages["Lina"] = 30;

        Console.WriteLine(ages["Sam"]);
    }
}

What this shows

  • keys are string
  • values are int
  • the compiler checks both types
  • reading a value is simple and safe

Safer lookup with TryGetValue

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<int, string> users = new Dictionary<int, string>();
        users[1] = "Alice";

        if (users.TryGetValue(1, out string name))
        {
            Console.WriteLine(name);
        }
        else
        {
            Console.WriteLine("User not found");
        }
    }
}

This is a common real-world pattern because direct indexing throws an exception if the key does not exist.

Step by Step Execution

Consider this example:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<int, string> dict = new Dictionary<int, string>();
        dict[1] = "Alice";
        dict[2] = "Bob";

        if (dict.TryGetValue(2, out string name))
        {
            Console.WriteLine(name);
        }
    }
}

Step by step

1. Create the dictionary

Dictionary<int, string> dict = new Dictionary<int, string>();

A new empty dictionary is created.

It will only accept:

  • int keys
  • string values

2. Add the first item

dict[1] = "Alice";

The key 1 is associated with the value "Alice".

3. Add the second item

dict[2] = "Bob";

The key 2 is associated with "Bob".

4. Try to read a value

if (dict.TryGetValue(2, out string name))

The dictionary checks whether key 2 exists.

  • It does exist
  • name becomes "Bob"
  • the method returns true

5. Print the value

Console.WriteLine(name);

Output:

Bob

Why this is better than Hashtable

With Hashtable, the same lookup usually needs casting:

Hashtable table = new Hashtable();
table[2] = "Bob";

string name = (string)table[2];

If the stored value is not actually a string, that cast can fail at runtime.

Real World Use Cases

Key-value collections are used everywhere in software.

Common uses for Dictionary<TKey, TValue>

1. Caching data by ID

Dictionary<int, string> productNames = new Dictionary<int, string>();

Useful when you want fast lookup of a product name by product ID.

2. Counting occurrences

Dictionary<string, int> wordCounts = new Dictionary<string, int>();

Common in text analysis, logs, and reporting.

3. Mapping configuration values

Dictionary<string, string> config = new Dictionary<string, string>();

Used for application settings, headers, environment values, or query parameters.

4. Grouping application state

Dictionary<Guid, UserSession> sessions = new Dictionary<Guid, UserSession>();

Useful in web apps, game servers, or background services.

5. Fast lookup tables

For example:

  • country code to country name
  • error code to message
  • username to profile
  • route name to handler

Why Hashtable is less common here

In real applications, mixed types usually create confusion. Most codebases want predictable types so the compiler can help prevent mistakes.

Take Quiz

Real Codebase Usage

In real C# projects, developers usually choose Dictionary<TKey, TValue> because it works well with modern coding patterns.

Common patterns

Guarded lookup with TryGetValue

if (!users.TryGetValue(userId, out var user))
{
    return "User not found";
}

return user.Name;

This avoids exceptions and makes missing data explicit.

Validation before insert

if (string.IsNullOrWhiteSpace(email))
{
    throw new ArgumentException("Email is required.");
}

emailToUserId[email] = userId;

Counting pattern

if (counts.ContainsKey(word))
{
    counts[word]++;
}
else
{
    counts[word] = 1;
}

Or more compactly:

counts[word] = counts.GetValueOrDefault(word) + 1;

Configuration lookup

if (settings.TryGetValue("Theme", out var theme))
{
    ApplyTheme(theme);
}

Error/message mapping

var messages = new Dictionary<int, string>
{
    [404] = "Not Found",
    [500] = "Server Error"
};

Why teams prefer it

  • easier to read during code review
  • safer during refactoring
  • fewer runtime cast errors
  • better fit with generic APIs and LINQ

Hashtable may still be seen in old enterprise applications or older tutorials, but it is not usually the first choice for new code.

Common Mistakes

1. Using direct indexing when the key may not exist

Broken example:

Dictionary<int, string> users = new Dictionary<int, string>();
string name = users[5];

This throws a KeyNotFoundException if key 5 is missing.

Better:

if (users.TryGetValue(5, out string name))
{
    Console.WriteLine(name);
}

2. Choosing Hashtable for new code without a reason

Broken example:

Hashtable table = new Hashtable();
table[1] = "Alice";
table[2] = 99;

This compiles, but now values have inconsistent types.

Better:

Dictionary<int, string> users = new Dictionary<int, string>();
users[1] = "Alice";

3. Forgetting casts with Hashtable

Broken example:

Hashtable table = new Hashtable();
table[1] = "Alice";

string name = table[1];

This does not compile because table[1] is object.

Correct version:

string name = (string)table[1];

But this is exactly the kind of extra work Dictionary<TKey, TValue> avoids.

4. Assuming all lookups are safe

Even with Dictionary, this can fail:

var dict = new Dictionary<int, string>();
Console.WriteLine(dict[1]);

Always ask:

  • does the key definitely exist?
  • should I use TryGetValue instead?

5. Mixing unrelated key types in a non-generic collection

Broken example:

Hashtable table = new Hashtable();
table[1] = "Alice";
table["two"] = "Bob";

This is hard to reason about and easy to misuse.

Better:

Dictionary<int, string> users = new Dictionary<int, string>();

6. Ignoring null rules

In most normal usage, dictionary keys should be valid and predictable. Make sure the key values you use are intentional and consistent, especially when building lookups from external input.

Comparisons

Dictionary<TKey, TValue> vs Hashtable

FeatureDictionary<TKey, TValue>Hashtable
GenericYesNo
Type safetyCompile-timeMostly runtime
Needs castingNo, usuallyYes, often
Boxing/unboxing for value typesAvoidedCan happen
ReadabilityHighLower
Modern C# usagePreferredMostly legacy
Works with strongly typed APIsVery wellLess naturally

Example comparison

Hashtable table = new Hashtable();
table[1] = "Alice";
string a = (string)table[1];
Dictionary<int, string> dict = new Dictionary<int, string>();
dict[1] = "Alice";
string a = dict[1];

The second version is shorter, clearer, and safer.

When might Hashtable still appear?

SituationBetter choice
New application codeDictionary<TKey, TValue>
Legacy .NET codeMaybe Hashtable if already used
API requires non-generic collectionHashtable only if necessary
Strong typing mattersDictionary<TKey, TValue>

Related comparison: generic vs non-generic collections

Collection styleExampleMain trait
Non-genericArrayList, HashtableStores object
GenericList<T>, Dictionary<TKey, TValue>Stores specific types

This is part of a larger shift in C# toward generics for safer and more maintainable code.

Take Quiz

Cheat Sheet

Quick answer

Prefer Dictionary<TKey, TValue> over Hashtable in modern C# because it is:

  • strongly typed
  • safer at compile time
  • usually faster for typed data
  • easier to read and maintain

Syntax

Dictionary<int, string> dict = new Dictionary<int, string>();
dict[1] = "Alice";
string name = dict[1];
Hashtable table = new Hashtable();
table[1] = "Alice";
string name = (string)table[1];

Main reasons to prefer Dictionary

  • No repeated casting
  • Better IntelliSense
  • Fewer runtime type errors
  • Avoids boxing/unboxing for value types
  • Clear key and value types in the code

Safe lookup

if (dict.TryGetValue(1, out var name))
{
    Console.WriteLine(name);
}

Watch out for

  • dict[key] throws if the key is missing
  • Hashtable stores values as object
  • Mixed types in Hashtable can cause runtime bugs

Rule of thumb

  • Use Dictionary<TKey, TValue> for new code
  • Use Hashtable only when working with legacy or non-generic APIs

FAQ

Is Dictionary faster than Hashtable in C#?

Often yes, especially when value types are involved, because Dictionary<TKey, TValue> avoids boxing and unboxing and uses strong typing.

Why does Hashtable require casting?

Because it stores values as object. When you read a value, you usually need to convert it back to the expected type.

Is Hashtable obsolete in C#?

Not completely, but it is generally considered a legacy-style collection for most modern code. Dictionary<TKey, TValue> is usually preferred.

When should I still use Hashtable?

Mostly when working with old code or APIs that specifically require non-generic collections.

What is the biggest advantage of Dictionary<TKey, TValue>?

Type safety. The compiler checks that keys and values are the correct types before the program runs.

Can Dictionary replace Hashtable in most code?

Yes, in most new C# code it is the better default choice.

Are both based on hash lookup ideas?

Yes. Both are hash-based key-value collections designed for fast lookup by key.

Take Quiz

Mini Project

Description

Build a small word counter that reads a sentence and counts how many times each word appears. This demonstrates why Dictionary<string, int> is a practical choice: each word maps clearly to a numeric count, and the compiler keeps the types consistent.

Goal

Create a console program that counts repeated words using Dictionary<string, int> and prints the results.

Requirements

  • Ask the user to enter a sentence
  • Split the sentence into words
  • Count how many times each word appears
  • Store the counts in a Dictionary<string, int>
  • Print each word with its total count
Take Quiz

Keep learning

Related questions

View more