# How to Implement a Map<string, string> in TypeScript: Best Practices for Map TypeScript

> Learn the idiomatic way to implement Map<string, string> in TypeScript using the native ES2015 Map with O(1) lookups. Discover best practices for map typescript.

- Repository: [Microsoft/TypeScript](https://github.com/microsoft/typescript)
- Tags: best-practices
- Published: 2026-02-19

---

**The most idiomatic way to implement a string-to-string map in TypeScript is to use the native ES2015 `Map<string, string>` type, which provides O(1) lookups, insertion order preservation, and a type-safe API.**

When working with key-value pairs in TypeScript, choosing the right data structure is critical for both performance and type safety. The Microsoft TypeScript repository provides comprehensive type definitions for the built-in JavaScript `Map` object, making it the standard choice for map typescript implementations. This article explores the idiomatic patterns, source code definitions, and best practices for implementing `Map<string, string>` based on the official TypeScript library files.

## Map<string, string> vs. Record<string, string>

Before implementing a map, understand the distinction between `Map` and `Record`. The `Map<string, string>` type offers significant advantages over the `Record<string, string>` alternative defined in [`src/lib/es5.d.ts`](https://github.com/microsoft/TypeScript/blob/main/src/lib/es5.d.ts).

**`Map<string, string>`** provides explicit ordering guarantees, O(1) lookup complexity, and a rich API including `keys()`, `values()`, and `entries()` methods. It is the preferred choice when you need frequent insertions and deletions.

**`Record<string, string>`** represents a simple object type useful for JSON serialization scenarios where you need a plain object structure. However, it lacks iteration order guarantees and the full `Map` API.

Use `Record` only when interacting with APIs requiring plain objects; otherwise, prefer `Map<string, string>` for map typescript implementations.

## Core Type Definitions in TypeScript

The official type definitions for `Map` reside in the TypeScript standard library files. Understanding these source files helps you leverage the full type system.

### ES2015 Collection Definitions

The primary interface definition lives in [`src/lib/es2015.collection.d.ts`](https://github.com/microsoft/TypeScript/blob/main/src/lib/es2015.collection.d.ts). This file declares the core `Map<K, V>` interface and the `ReadonlyMap<K, V>` variant used across all TypeScript targets.

```typescript
// Core interface from src/lib/es2015.collection.d.ts
interface Map<K, V> {
    clear(): void;
    delete(key: K): boolean;
    forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
    get(key: K): V | undefined;
    has(key: K): boolean;
    set(key: K, value: V): this;
    readonly size: number;
}

```

### ESNext Extensions

For modern runtimes targeting ES2025+, [`src/lib/esnext.collection.d.ts`](https://github.com/microsoft/TypeScript/blob/main/src/lib/esnext.collection.d.ts) adds convenience methods like `getOrInsert` and `getOrInsertComputed`:

```typescript
// Extended interface from src/lib/esnext.collection.d.ts
interface Map<K, V> {
    getOrInsert(key: K, defaultValue: V): V;
    getOrInsertComputed(key: K, defaultValue: (key: K) => V): V;
}

```

## Creating and Initializing Maps

Instantiate your map using explicit type parameters to prevent accidental insertion of non-string values.

**Zero-argument constructor** for empty maps:

```typescript
const userPreferences: Map<string, string> = new Map();

```

**Initializer array** constructor for pre-populated data:

```typescript
const config: Map<string, string> = new Map([
  ['apiUrl', 'https://api.example.com'],
  ['timeout', '5000']
]);

```

Always declare `Map<string, string>` explicitly rather than relying on type inference when initializing empty maps. This ensures type safety throughout your application.

## Working with Map Methods

The `Map` API provides mutating methods that return the map instance for method chaining.

```typescript
const translations = new Map<string, string>();

// Chaining set operations
translations
  .set('hello', 'bonjour')
  .set('goodbye', 'au revoir')
  .set('thank you', 'merci');

// Checking existence
const hasHello = translations.has('hello'); // true

// Retrieving values
const greeting = translations.get('hello'); // "bonjour"
const missing = translations.get('unknown'); // undefined

// Removing entries
translations.delete('goodbye');
translations.clear(); // Removes all entries

```

Note that `set`, `delete`, and `clear` mutate the map in-place. They return the map itself to enable fluent chaining patterns.

## Iteration Patterns

Maps preserve insertion order during iteration. Prefer `for...of` loops or the `forEach` method for traversing entries.

**Destructured iteration** (recommended):

```typescript
const settings = new Map<string, string>([
  ['theme', 'dark'],
  ['language', 'typescript']
]);

for (const [key, value] of settings) {
  console.log(`${key} → ${value}`);
}

```

**forEach method**:

```typescript
settings.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});

```

Both approaches maintain the original insertion order, unlike traditional object property enumeration.

## Advanced TypeScript Map Patterns

### Read-only Map Exposure

When exposing maps to consumers that should not mutate the data, return `ReadonlyMap<string, string>`:

```typescript
function getSystemConfig(): ReadonlyMap<string, string> {
  const internalMap = new Map<string, string>();
  internalMap.set('version', '2.0.0');
  return internalMap;
}

const config = getSystemConfig();
config.set('newKey', 'value'); // Error: Property 'set' does not exist on type 'ReadonlyMap<string, string>'

```

### ESNext Get-or-Insert Utilities

For cached computations or default value patterns, use the ESNext `getOrInsert` method available when targeting ES2025 or later:

```typescript
const cache = new Map<string, string>();

function fetchData(id: string): string {
  return cache.getOrInsert(id, `computed-${id}`);
}

// Equivalent to:
// if (!cache.has(id)) cache.set(id, `computed-${id}`);
// return cache.get(id)!;

```

## Performance and Serialization Considerations

**Performance characteristics**: For large datasets with frequent insertions and deletions, `Map` outperforms plain objects. The implementation uses hash table optimizations that maintain O(1) average time complexity for `set` and `get` operations.

**JSON serialization**: `Map` instances are not directly JSON-serializable. Convert to an array of entries before stringifying:

```typescript
const data = new Map([['key', 'value']]);
const serialized = JSON.stringify(Array.from(data)); // [["key","value"]]
const revived = new Map(JSON.parse(serialized) as [string, string][]);

```

This conversion pattern ensures compatibility with APIs expecting plain objects or arrays while preserving your map typescript type safety internally.

## Summary

- Use `Map<string, string>` instead of `Record<string, string>` when you need ordering guarantees, O(1) lookups, or the full Map API.
- Reference [`src/lib/es2015.collection.d.ts`](https://github.com/microsoft/TypeScript/blob/main/src/lib/es2015.collection.d.ts) for core type definitions and [`src/lib/esnext.collection.d.ts`](https://github.com/microsoft/TypeScript/blob/main/src/lib/esnext.collection.d.ts) for modern utility methods.
- Always specify type parameters explicitly: `const map: Map<string, string> = new Map()`.
- Leverage method chaining with `set()`, `delete()`, and `clear()` which return the map instance.
- Expose `ReadonlyMap<string, string>` to prevent external mutation of internal map state.
- Convert to `Array.from(map)` before `JSON.stringify()` since Maps are not directly serializable.
- Consider `getOrInsert` and `getOrInsertComputed` for caching patterns when targeting ES2025+.

## Frequently Asked Questions

### What is the difference between Map and Record in TypeScript?

`Map` is a built-in JavaScript class providing ordered key-value storage with methods like `set()`, `get()`, and `has()`, while `Record` is a TypeScript utility type representing a plain object with fixed keys. Use `Map` when you need to preserve insertion order or frequently add/remove keys; use `Record` for simple static structures that need JSON serialization.

### How do I make a Map immutable in TypeScript?

Declare the type as `ReadonlyMap<string, string>` or cast an existing map using `as ReadonlyMap<string, string>`. This removes mutating methods like `set()`, `delete()`, and `clear()` from the type definition, preventing accidental modifications while still allowing reads and iteration.

### Can I use Map with string keys in older JavaScript environments?

The `Map` constructor requires ES2015 (ES6) or later. TypeScript compiles Map usage to compatible code when targeting older environments, but you may need to include a polyfill for runtime support in browsers older than IE11. The type definitions in [`src/lib/es2015.collection.d.ts`](https://github.com/microsoft/TypeScript/blob/main/src/lib/es2015.collection.d.ts) provide the necessary TypeScript support regardless of target.

### Why does JSON.stringify return an empty object for my Map?

JavaScript's `JSON.stringify` does not enumerate Map entries because they are not stored as object properties. Convert the map to an array using `Array.from(myMap)` or `[...myMap]` before stringifying to preserve the key-value pairs as nested arrays.