Check If Two Strings Are Isomorphic

Problem and examples

Given two strings s and t, determine if they are isomorphic.

Two strings are isomorphic if the characters in s can be replaced to get t. The replacement must be consistent — every character in s must map to exactly one character in t, and no two characters from s can map to the same character in t.

The character mapping should be one-to-one and should preserve the order of characters. Return true if the strings are isomorphic, otherwise return false.

s t Output Description
"egg" "add" true 'e' → 'a', 'g' → 'd' — consistent mapping
"foo" "bar" false 'o' maps to two different characters, which breaks the rule
"paper" "title" true 'p' → 't', 'a' → 'i', 'e' → 'l', 'r' → 'e'
"ab" "aa" false 'a' → 'a' and 'b' → 'a' — two characters from 's' map to same char in 't'
"abc" "def" true Each character maps uniquely and consistently
"" "" true Both strings are empty — trivially isomorphic
"" "abc" false One string is empty, the other is not
"abc" "" false One string is empty, the other is not
"abca" "zbxz" true 'a' → 'z', 'b' → 'b', 'c' → 'x' — all mappings valid
"abca" "zbxy" false 'a' maps to both 'z' and 'y' — mapping is inconsistent

Explore Isomorphic Strings

Load an example or enter your own input. Use the player controls to follow each change.

Understanding the Problem

We are given two strings, s and t, and we need to determine whether they are isomorphic.

Two strings are isomorphic if characters in one string can be replaced to get the second string — with a consistent, one-to-one mapping.

This means:

  • Every character in s maps to exactly one character in t.
  • No two characters from s map to the same character in t.

Think of this as a character-translation problem: we’re translating each character in s to a character in t. The translation must be consistent and unique.

Step-by-Step Solution with Example

Step 1: Take a simple example

Let’s consider the example: s = "egg" and t = "add"

We’ll walk through each character pair and see how the mappings work.

Step 2: Use two hash maps

We use two maps (dictionaries):

  • s_to_t: Maps characters from s to t
  • t_to_s: Maps characters from t to s

Step 3: Iterate through the strings

We loop through each character pair:


i = 0 → s[0] = 'e', t[0] = 'a'  
→ 'e' is not in s_to_t → map 'e' → 'a'  
→ 'a' is not in t_to_s → map 'a' → 'e'

i = 1 → s[1] = 'g', t[1] = 'd'  
→ 'g' is not in s_to_t → map 'g' → 'd'  
→ 'd' is not in t_to_s → map 'd' → 'g'

i = 2 → s[2] = 'g', t[2] = 'd'  
→ 'g' is already mapped to 'd' → OK  
→ 'd' is already mapped to 'g' → OK

All characters follow consistent one-to-one mapping. So, the strings are isomorphic.

Step 4: Check for mismatches

Let’s consider another example: s = "foo", t = "bar"


i = 0 → 'f' → 'b'  
i = 1 → 'o' → 'a'  
i = 2 → 'o' → 'r' → ❌ mismatch! 'o' was already mapped to 'a'

So we return false.

Edge Cases

  • Empty Strings: If both s and t are empty, they are trivially isomorphic — nothing to map, no conflict.
  • Different Lengths: If s.length !== t.length, they can’t be isomorphic. Each character must have a match.
  • Repeated Characters: Repetition is fine as long as the mapping stays consistent. For example: s = "paper", t = "title" → Valid isomorphic pair.

Finally

To solve this problem:

  1. Understand the core rule — one-to-one and consistent character mapping.
  2. Use two dictionaries to track both directions of mapping.
  3. Validate that no character breaks the mapping rule during iteration.

This approach is simple, beginner-friendly, and ensures you check all logical mapping conditions.

  1. Initialize two HashMaps: s_to_t and t_to_s.
  2. Loop through each character in both strings.
  3. For each character pair (sc, tc) from s and t:
    • If sc is mapped in s_to_t, it must map to tc.
    • If tc is mapped in t_to_s, it must map to sc.
  4. If any mismatch is found, return false.
  5. After the loop, return true.

Implementation and efficiency

C implementation

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

bool isIsomorphic(char* s, char* t) {
    if (strlen(s) != strlen(t)) return false;

    int sToT[256] = {0};
    int tToS[256] = {0};

    for (int i = 0; s[i]; i++) {
        char sc = s[i], tc = t[i];
        if (sToT[sc] == 0 && tToS[tc] == 0) {
            sToT[sc] = tc;
            tToS[tc] = sc;
        } else if (sToT[sc] != tc || tToS[tc] != sc) {
            return false;
        }
    }
    return true;
}

int main() {
    printf("%s\n", isIsomorphic("egg", "add") ? "true" : "false");
    printf("%s\n", isIsomorphic("foo", "bar") ? "true" : "false");
    return 0;
}

C++ implementation

#include <iostream>
#include <unordered_map>
using namespace std;

bool isIsomorphic(string s, string t) {
    if (s.length() != t.length()) return false;
    unordered_map<char, char> sToT, tToS;

    for (int i = 0; i < s.length(); i++) {
        char sc = s[i], tc = t[i];
        if (sToT.count(sc)) {
            if (sToT[sc] != tc) return false;
        } else {
            sToT[sc] = tc;
        }
        if (tToS.count(tc)) {
            if (tToS[tc] != sc) return false;
        } else {
            tToS[tc] = sc;
        }
    }
    return true;
}

int main() {
    cout << boolalpha << isIsomorphic("egg", "add") << endl;
    cout << boolalpha << isIsomorphic("foo", "bar") << endl;
    return 0;
}

Python implementation

def is_isomorphic(s: str, t: str) -> bool:
    if len(s) != len(t):
        return False

    s_to_t = {}
    t_to_s = {}

    for sc, tc in zip(s, t):
        if sc in s_to_t:
            if s_to_t[sc] != tc:
                return False
        else:
            s_to_t[sc] = tc

        if tc in t_to_s:
            if t_to_s[tc] != sc:
                return False
        else:
            t_to_s[tc] = sc

    return True

print(is_isomorphic("egg", "add"))  # True
print(is_isomorphic("foo", "bar"))  # False

Java implementation

import java.util.*;

public class IsomorphicStrings {
    public static boolean isIsomorphic(String s, String t) {
        if (s.length() != t.length()) return false;

        Map<Character, Character> sToT = new HashMap<>();
        Map<Character, Character> tToS = new HashMap<>();

        for (int i = 0; i < s.length(); i++) {
            char sc = s.charAt(i);
            char tc = t.charAt(i);

            if (sToT.containsKey(sc)) {
                if (sToT.get(sc) != tc) return false;
            } else {
                sToT.put(sc, tc);
            }

            if (tToS.containsKey(tc)) {
                if (tToS.get(tc) != sc) return false;
            } else {
                tToS.put(tc, sc);
            }
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(isIsomorphic("egg", "add"));
        System.out.println(isIsomorphic("foo", "bar"));
    }
}

JavaScript implementation

function isIsomorphic(s, t) {
    if (s.length !== t.length) return false;

    const sToT = {};
    const tToS = {};

    for (let i = 0; i < s.length; i++) {
        let sc = s[i], tc = t[i];

        if (sToT[sc] !== undefined) {
            if (sToT[sc] !== tc) return false;
        } else {
            sToT[sc] = tc;
        }

        if (tToS[tc] !== undefined) {
            if (tToS[tc] !== sc) return false;
        } else {
            tToS[tc] = sc;
        }
    }
    return true;
}

console.log(isIsomorphic("egg", "add"));
console.log(isIsomorphic("foo", "bar"));

Go implementation

package main

import (
    "fmt"
)

func isIsomorphic(s string, t string) bool {
    if len(s) != len(t) {
        return false
    }

    sToT := make(map[byte]byte)
    tToS := make(map[byte]byte)

    for i := 0; i < len(s); i++ {
        sc, tc := s[i], t[i]

        if val, ok := sToT[sc]; ok {
            if val != tc {
                return false
            }
        } else {
            sToT[sc] = tc
        }

        if val, ok := tToS[tc]; ok {
            if val != sc {
                return false
            }
        } else {
            tToS[tc] = sc
        }
    }
    return true
}

func main() {
    fmt.Println(isIsomorphic("egg", "add"))
    fmt.Println(isIsomorphic("foo", "bar"))
}

Rust implementation

use std::collections::HashMap;

fn is_isomorphic(s: &str, t: &str) -> bool {
    if s.len() != t.len() {
        return false;
    }

    let (mut s_to_t, mut t_to_s) = (HashMap::new(), HashMap::new());

    for (sc, tc) in s.chars().zip(t.chars()) {
        if let Some(&mapped) = s_to_t.get(&sc) {
            if mapped != tc {
                return false;
            }
        } else {
            s_to_t.insert(sc, tc);
        }

        if let Some(&mapped) = t_to_s.get(&tc) {
            if mapped != sc {
                return false;
            }
        } else {
            t_to_s.insert(tc, sc);
        }
    }
    true
}

fn main() {
    println!("{}", is_isomorphic("egg", "add"));
    println!("{}", is_isomorphic("foo", "bar"));
}

Kotlin implementation

fun isIsomorphic(s: String, t: String): Boolean {
    if (s.length != t.length) return false

    val sToT = mutableMapOf<Char, Char>()
    val tToS = mutableMapOf<Char, Char>()

    for (i in s.indices) {
        val sc = s[i]
        val tc = t[i]

        if (sToT.containsKey(sc)) {
            if (sToT[sc] != tc) return false
        } else {
            sToT[sc] = tc
        }

        if (tToS.containsKey(tc)) {
            if (tToS[tc] != sc) return false
        } else {
            tToS[tc] = sc
        }
    }
    return true
}

fun main() {
    println(isIsomorphic("egg", "add"))
    println(isIsomorphic("foo", "bar"))
}

Swift implementation

func isIsomorphic(_ s: String, _ t: String) -> Bool {
    if s.count != t.count { return false }

    var sToT = [Character: Character]()
    var tToS = [Character: Character]()

    let sChars = Array(s), tChars = Array(t)

    for i in 0..<s.count {
        let sc = sChars[i]
        let tc = tChars[i]

        if let mapped = sToT[sc], mapped != tc {
            return false
        } else {
            sToT[sc] = tc
        }

        if let mapped = tToS[tc], mapped != sc {
            return false
        } else {
            tToS[tc] = sc
        }
    }
    return true
}

print(isIsomorphic("egg", "add"))
print(isIsomorphic("foo", "bar"))

TypeScript implementation

function isIsomorphic(s: string, t: string): boolean {
    if (s.length !== t.length) return false;

    const sToT: Record<string, string> = {};
    const tToS: Record<string, string> = {};

    for (let i = 0; i < s.length; i++) {
        const sc = s[i], tc = t[i];

        if (sToT[sc] !== undefined) {
            if (sToT[sc] !== tc) return false;
        } else {
            sToT[sc] = tc;
        }

        if (tToS[tc] !== undefined) {
            if (tToS[tc] !== sc) return false;
        } else {
            tToS[tc] = sc;
        }
    }
    return true;
}

console.log(isIsomorphic("egg", "add"));
console.log(isIsomorphic("foo", "bar"));

Time complexity

CaseTimeExplanation
best caseO(n)We traverse both strings once, checking and updating maps in constant time.
average caseO(n)Each character mapping takes constant time; total work is proportional to string length.
worst caseO(n)In the worst case, every character in both strings is unique and requires a mapping.

Space complexity

O(n)

Two hash maps are used that at most store all unique characters of the strings.