diff --git a/src/main/java/chocopy/common/analysis/SymbolTable.java b/src/main/java/chocopy/common/analysis/SymbolTable.java
index e9bc757e21e26546b2ab3371f6472ad73a536652..e5adcd4ca2af9daaf71ac15db3918a604d656eed 100644
--- a/src/main/java/chocopy/common/analysis/SymbolTable.java
+++ b/src/main/java/chocopy/common/analysis/SymbolTable.java
@@ -1,7 +1,5 @@
 package chocopy.common.analysis;
 
-import org.apache.tools.ant.taskdefs.PathConvert;
-
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
diff --git a/src/main/java/chocopy/pa2/TypeChecker.java b/src/main/java/chocopy/pa2/TypeChecker.java
index 759f1772f1ee85c2b6b8d94a5afff475a853e964..528121c9d6fc2d6003e88a56a0584daf59ee028c 100644
--- a/src/main/java/chocopy/pa2/TypeChecker.java
+++ b/src/main/java/chocopy/pa2/TypeChecker.java
@@ -70,9 +70,15 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
                 continue;
             }
 
-            // TODO: DO NOT throw on duplicate id. generate a compiler error.
-            if(isBuildingSym)
-                sym.put(name, type);
+            if(isBuildingSym) {
+                try {
+                    sym.put(name, type);
+                }
+                catch(RuntimeException e) {
+                    // duplicate id
+                    err(decl, e.getMessage());
+                }
+            }
         }
 
         if(isBuildingSym)
@@ -160,18 +166,30 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
         for(TypedVar param : funcDef.params) {
             String name = param.identifier.name;
             ValueType type = resolveTypeAnnotation(param.type);
-            //if(typeIsUserDefinedClass(type))
-            //    type = (ValueType) sym.get(((ClassValueType) type).className());
-            if(isBuildingSym)
-                sym.put(name, type);
+            if(isBuildingSym) {
+                try {
+                    sym.put(name, type);
+                }
+                catch(RuntimeException e) {
+                    // duplicate id
+                    err(param, e.getMessage());
+                }
+            }
             args.add(type);
         }
 
         for(Declaration decl : funcDef.declarations) {
             String name = decl.getIdentifier().name;
             SymbolType type = decl.dispatch(this);
-            if(isBuildingSym)
-                sym.put(name, type);
+            if(isBuildingSym) {
+                try {
+                    sym.put(name, type);
+                }
+                catch(RuntimeException e) {
+                    // duplicate id
+                    err(decl, e.getMessage());
+                }
+            }
         }
 
         if(!isBuildingSym) {
@@ -182,8 +200,17 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
         }
 
         FuncType funcT = new FuncType(args, resolveTypeAnnotation(funcDef.returnType));
-        if(isBuildingSym)
-            sym.put(funcDef.name.name, funcT); // this funcdef should be add to both parentSym and localSym to support recursive func.
+        if(isBuildingSym) {
+            try {
+                // this funcdef should be add to both parentSym and localSym to support recursive func.
+                // stmt will fill the parent sym, and I will fill the local sym.
+                sym.put(funcDef.name.name, funcT);
+            }
+            catch(RuntimeException e) {
+                // duplicate id
+                err(funcDef, e.getMessage());
+            }
+        }
 
         // TA don't like it. OK I won't dispatch this id...
         //funcDef.name.dispatch(this); // dispatch it.