diff --git a/lib/parse.js b/lib/parse.js
index bfba0fe1..28af86a5 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -187,58 +187,103 @@ var parseObject = function (chain, val, options, valuesParsed) {
     return leaf;
 };
 
-var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
-    if (!givenKey) {
-        return;
+// Split a key like "a[b][c[]]" into ['a', '[b]', '[c[]]'] with depth,
+// dot-notation, and strictDepth semantics similar to qs.dart / other ports.
+var splitKeyIntoSegments = function (originalKey, options) {
+    // depth == 0 → no splitting (keeps dots literal, per qs semantics)
+    if (options.depth <= 0) {
+        return [originalKey];
     }
 
-    // Transform dot notation to bracket notation
-    var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey;
-
-    // The regex chunks
+    // Normalize dots only when depth > 0 (matches existing behavior)
+    var key = options.allowDots ? originalKey.replace(/\.([^.[]+)/g, '[$1]') : originalKey;
 
-    var brackets = /(\[[^[\]]*])/;
-    var child = /(\[[^[\]]*])/g;
+    var segments = [];
 
-    // Get the parent
+    // parent before the first '[' (may be empty if key starts with '[')
+    var first = key.indexOf('[');
+    var parent = first >= 0 ? key.slice(0, first) : key;
+    if (parent) {
+        // prototype guard for parent
+        if (!options.plainObjects && has.call(Object.prototype, parent) && !options.allowPrototypes) {
+            return; // behave like current parseKeys: bail out
+        }
+        segments.push(parent);
+    }
 
-    var segment = options.depth > 0 && brackets.exec(key);
-    var parent = segment ? key.slice(0, segment.index) : key;
+    var n = key.length;
+    var open = first;
+    var collected = 0;
+    var lastClose = -1;
+
+    while (open >= 0 && collected < options.depth) {
+        var level = 1;
+        var i = open + 1;
+        var close = -1;
+
+        // balance nested '[' and ']' inside this bracket group without using 'break'
+        while (i < n && close < 0) {
+            var cu = key.charCodeAt(i);
+            if (cu === 0x5B) { // '['
+                level += 1;
+            } else if (cu === 0x5D) { // ']'
+                level -= 1;
+                if (level === 0) {
+                    close = i; // found matching close; loop will exit by condition
+                }
+            }
+            i += 1;
+        }
 
-    // Stash the parent if it exists
+        if (close < 0) {
+            // Unterminated group: wrap the raw remainder once as a single literal segment
+            segments.push('[' + key.slice(open) + ']');
+            return segments;
+        }
 
-    var keys = [];
-    if (parent) {
-        // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties
-        if (!options.plainObjects && has.call(Object.prototype, parent)) {
-            if (!options.allowPrototypes) {
-                return;
-            }
+        var seg = key.slice(open, close + 1);
+        // prototype guard for the content of this group
+        var content = seg.slice(1, -1);
+        if (!options.plainObjects && has.call(Object.prototype, content) && !options.allowPrototypes) {
+            return;
         }
 
-        keys.push(parent);
-    }
+        segments.push(seg);
+        lastClose = close;
+        collected += 1;
 
-    // Loop through children appending to the array until we hit depth
+        // find the next '[' after this balanced group
+        open = key.indexOf('[', close + 1);
+    }
 
-    var i = 0;
-    while (options.depth > 0 && (segment = child.exec(key)) !== null && i < options.depth) {
-        i += 1;
-        if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
-            if (!options.allowPrototypes) {
-                return;
+    // Trailing text after the last balanced group → one final bracket segment (unless it's just '.')
+    if (lastClose >= 0 && lastClose + 1 < n) {
+        var remainder = key.slice(lastClose + 1);
+        if (remainder !== '.') {
+            if (options.strictDepth && open >= 0) {
+                throw new RangeError('Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true');
             }
+            segments.push('[' + remainder + ']');
+        }
+    } else if (open >= 0) {
+        // There are more groups beyond collected depth
+        if (options.strictDepth) {
+            throw new RangeError('Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true');
         }
-        keys.push(segment[1]);
+        segments.push('[' + key.slice(open) + ']');
     }
 
-    // If there's a remainder, check strictDepth option for throw, else just add whatever is left
+    return segments;
+};
 
-    if (segment) {
-        if (options.strictDepth === true) {
-            throw new RangeError('Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true');
-        }
-        keys.push('[' + key.slice(segment.index) + ']');
+var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
+    if (!givenKey) {
+        return;
+    }
+
+    var keys = splitKeyIntoSegments(givenKey, options);
+    if (!keys) { // prototype guard early-outs
+        return;
     }
 
     return parseObject(keys, val, options, valuesParsed);
diff --git a/test/parse.js b/test/parse.js
index 7361356d..fbce85a3 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -251,6 +251,31 @@ test('parse()', function (t) {
         st.end();
     });
 
+    t.test('parses keys with literal [] inside a bracket group (#493)', function (st) {
+        // A bracket pair inside a bracket group should be treated literally as part of the key
+        st.deepEqual(
+            qs.parse('search[withbracket[]]=foobar'),
+            { search: { 'withbracket[]': 'foobar' } },
+            'treats inner [] literally when inside a bracket group'
+        );
+
+        // Single-level variant
+        st.deepEqual(
+            qs.parse('a[b[]]=c'),
+            { a: { 'b[]': 'c' } },
+            'keeps "b[]" as a literal key'
+        );
+
+        // Nested with an array push on the outer level
+        st.deepEqual(
+            qs.parse('list[][x[]]=y'),
+            { list: [{ 'x[]': 'y' }] },
+            'preserves inner [] while still treating outer [] as array push'
+        );
+
+        st.end();
+    });
+
     t.test('allows to specify array indices', function (st) {
         st.deepEqual(qs.parse('a[1]=c&a[0]=b&a[2]=d'), { a: ['b', 'c', 'd'] });
         st.deepEqual(qs.parse('a[1]=c&a[0]=b'), { a: ['b', 'c'] });
